Compare commits
70 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
cbba8fa7e4 | |
|
|
072e4b279d | |
|
|
0a700f19c8 | |
|
|
5b645b1898 | |
|
|
a4d39d38dc | |
|
|
6a82a7d90c | |
|
|
b495652eb9 | |
|
|
706b78257a | |
|
|
c1c5a9c021 | |
|
|
7f86e146a1 | |
|
|
46e79c0571 | |
|
|
f931053650 | |
|
|
f2175e41a9 | |
|
|
2d16bf0ad5 | |
|
|
69ac5f1cbf | |
|
|
125455fcb6 | |
|
|
1b8f7f86f4 | |
|
|
6ea5f2c675 | |
|
|
3b91c6782d | |
|
|
5a16beb676 | |
|
|
817fc57dd1 | |
|
|
095ba7820d | |
|
|
96cce88aed | |
|
|
baf739658e | |
|
|
0c6d3a6c6a | |
|
|
e8ea8d8abf | |
|
|
79009305a1 | |
|
|
03f5742a91 | |
|
|
4ad68109d2 | |
|
|
61f97f2f0a | |
|
|
31ebc8ffbc | |
|
|
567c74bb57 | |
|
|
c4161c9db6 | |
|
|
fd67e7033b | |
|
|
cbd287e20e | |
|
|
479caef353 | |
|
|
8fefc60f6b | |
|
|
014076dd06 | |
|
|
d408229ba9 | |
|
|
47d6c37819 | |
|
|
1eb8fa7223 | |
|
|
f9c28b3c45 | |
|
|
a26d34c2b3 | |
|
|
4d076ede3d | |
|
|
f60d0618e6 | |
|
|
b975a7c103 | |
|
|
0e9150b8f6 | |
|
|
4fdbee6df5 | |
|
|
e6a126ea0b | |
|
|
43d44a4518 | |
|
|
8bf7503c8e | |
|
|
785215bd3c | |
|
|
952e04815a | |
|
|
d6c95a536c | |
|
|
9785ef5e1e | |
|
|
3b7aa70e44 | |
|
|
c067b426a9 | |
|
|
da968c2fd4 | |
|
|
563eb7bb61 | |
|
|
b7f13bf33d | |
|
|
d73cbe9e0c | |
|
|
f582410c40 | |
|
|
f53fc80f68 | |
|
|
16335d796b | |
|
|
a7df97aae5 | |
|
|
4a7efd4016 | |
|
|
5be556a4cf | |
|
|
72894cd5cc | |
|
|
f0a1c21678 | |
|
|
807072a77f |
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
vendor
|
||||
gollum
|
||||
public
|
||||
**.sw*
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
FROM frolvlad/alpine-glibc:alpine-3.9_glibc-2.29
|
||||
RUN apk update && apk add --no-cache ca-certificates git
|
||||
|
||||
FROM golang:1.13-alpine as certs
|
||||
RUN apk update && apk add --no-cache ca-certificates
|
||||
|
||||
FROM busybox:glibc
|
||||
RUN mkdir -p /var/log
|
||||
WORKDIR /main
|
||||
COPY --from=certs /etc/ssl/certs /etc/ssl/certs
|
||||
|
||||
COPY . .
|
||||
|
||||
|
|
@ -10,3 +13,4 @@ ENV GOPATH=""
|
|||
ENV MNT="/mnt/"
|
||||
ENTRYPOINT ["/main/exec-notes-server"]
|
||||
CMD []
|
||||
|
||||
|
|
|
|||
4
TODO
4
TODO
|
|
@ -30,10 +30,10 @@ x main test -
|
|||
x TOC levels
|
||||
x delete pages
|
||||
x search
|
||||
FTS
|
||||
x FTS
|
||||
https://stackoverflow.com/questions/26709971/could-this-be-more-efficient-in-go
|
||||
x move auth as flag in router
|
||||
x . and ../** as roots cause bugs in listing and loading and linking
|
||||
x `create` at root is a 400, base= in URL (when `create` input is empty)
|
||||
x versioning
|
||||
delete top-level pages
|
||||
versioning
|
||||
|
|
|
|||
|
|
@ -2,13 +2,16 @@ package config
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"local/args"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.inhome.blapointe.com/local/args"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -18,6 +21,8 @@ var (
|
|||
Foot string
|
||||
OAuthServer string
|
||||
VersionInterval time.Duration
|
||||
ReadOnly bool
|
||||
MaxSizeMB int
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -25,35 +30,68 @@ func init() {
|
|||
}
|
||||
|
||||
func Refresh() {
|
||||
if strings.Contains(fmt.Sprint(os.Args), "-test") {
|
||||
if strings.Contains(fmt.Sprint(os.Args), " -test") {
|
||||
return
|
||||
}
|
||||
|
||||
as := args.NewArgSet()
|
||||
as.Append(args.STRING, "root", "root dir path", "./public")
|
||||
as.Append(args.STRING, "port", "port to listen on", "49909")
|
||||
as.Append(args.STRING, "wrap", "file with http header/footer", "./wrapper.html")
|
||||
as.Append(args.STRING, "wrap", "file with http header/footer", "")
|
||||
as.Append(args.STRING, "oauth", "oauth URL", "")
|
||||
as.Append(args.DURATION, "version", "duration to mark versions", "0s")
|
||||
as.Append(args.BOOL, "ro", "read-only mode", false)
|
||||
as.Append(args.INT, "max-v-size", "max size in mb for versioning", 2)
|
||||
if err := as.Parse(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
wrap := as.Get("wrap").GetString()
|
||||
wrap := as.GetString("wrap")
|
||||
var b []byte
|
||||
if len(wrap) > 0 {
|
||||
log.Printf("reading %v (%T)", wrap, wrap)
|
||||
b, err := ioutil.ReadFile(wrap)
|
||||
var err error
|
||||
b, err = ioutil.ReadFile(wrap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
b = []byte(defaultWrapper)
|
||||
}
|
||||
bs := bytes.Split(b, []byte("{{{}}}"))
|
||||
if len(bs) != 2 {
|
||||
panic(len(bs))
|
||||
}
|
||||
|
||||
Root = as.Get("root").GetString()
|
||||
Port = ":" + strings.TrimPrefix(as.Get("port").GetString(), ":")
|
||||
Root = strings.TrimSuffix(as.GetString("root"), "/")
|
||||
Root, _ = filepath.Abs(Root)
|
||||
Port = ":" + strings.TrimPrefix(as.GetString("port"), ":")
|
||||
Head = string(bs[0])
|
||||
Foot = string(bs[1])
|
||||
OAuthServer = as.Get("oauth").GetString()
|
||||
VersionInterval = as.Get("version").GetDuration()
|
||||
OAuthServer = as.GetString("oauth")
|
||||
VersionInterval = as.GetDuration("version")
|
||||
ReadOnly = as.GetBool("ro")
|
||||
MaxSizeMB = as.GetInt("max-v-size")
|
||||
}
|
||||
|
||||
//go:embed water.css.txt
|
||||
var waterCSS string
|
||||
|
||||
var defaultWrapper = `
|
||||
<html>
|
||||
<header>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css -->
|
||||
<style>
|
||||
` + waterCSS + `
|
||||
/*# sourceMappingURL=dark-legacy.standalone.min.css.map */
|
||||
</style>
|
||||
</header>
|
||||
<body>
|
||||
{{{}}}
|
||||
</body>
|
||||
<footer>
|
||||
</footer>
|
||||
</html>
|
||||
`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
#! /usr/gitea.inhome.blapointe.com/local/bin/python3
|
||||
|
||||
def main(args) :
|
||||
print("args", args)
|
||||
for i in args :
|
||||
rotate(i)
|
||||
|
||||
def rotate(x) :
|
||||
print("input: {}", x)
|
||||
rgb = hex_to_rgb(x)
|
||||
print("rgb: {}", rgb)
|
||||
hsl = rgb_to_hsl(rgb)
|
||||
print("hsl: {}", hsl)
|
||||
import os
|
||||
env = os.environ
|
||||
if "DEG" in env :
|
||||
deg = int(env["DEG"])
|
||||
else :
|
||||
deg = 110
|
||||
hsl = rotate_hsl(hsl, deg)
|
||||
print("hsl': {}", hsl)
|
||||
rgb = hsl_to_rgb(hsl)
|
||||
print("rgb': {}", rgb)
|
||||
print(rgb_to_hex(rgb))
|
||||
|
||||
def hex_to_rgb(x) :
|
||||
if x.startswith("#") :
|
||||
x = x[1:]
|
||||
r = x[0:2]
|
||||
g = x[2:4]
|
||||
b = x[4:6]
|
||||
return (
|
||||
hex_to_dec(r),
|
||||
hex_to_dec(g),
|
||||
hex_to_dec(b),
|
||||
)
|
||||
|
||||
def hex_to_dec(x) :
|
||||
s = 0
|
||||
mul = 1
|
||||
for i in range(len(x)):
|
||||
c = x[len(x)-i-1]
|
||||
c = c.upper()
|
||||
digit = ord(c) - ord('0')
|
||||
if not c.isdigit() :
|
||||
digit = ord(c) - ord('A') + 10
|
||||
s += mul * digit
|
||||
mul *= 16
|
||||
return s
|
||||
|
||||
def rgb_to_hsl(rgb) :
|
||||
return (
|
||||
compute_h(rgb),
|
||||
compute_s(rgb),
|
||||
compute_l(rgb),
|
||||
)
|
||||
|
||||
def compute_h(rgb) :
|
||||
r, g, b, cmax, cmin, delta = compute_hsl_const(rgb)
|
||||
if delta == 0 :
|
||||
return 0
|
||||
if r == cmax :
|
||||
return 60 * ( ( (g - b)/delta ) % 6)
|
||||
if g == cmax :
|
||||
return 60 * ( ( (b - r)/delta ) + 2)
|
||||
if b == cmax :
|
||||
return 60 * ( ( (r - g)/delta ) + 4)
|
||||
|
||||
def compute_s(rgb) :
|
||||
r, g, b, cmax, cmin, delta = compute_hsl_const(rgb)
|
||||
if delta == 0 :
|
||||
return 0
|
||||
return delta / ( 1 - (abs(2*compute_l(rgb)-1)) )
|
||||
|
||||
def compute_l(rgb) :
|
||||
r, g, b, cmax, cmin, delta = compute_hsl_const(rgb)
|
||||
return (cmax + cmin) / 2
|
||||
|
||||
def compute_hsl_const(rgb) :
|
||||
rgb = [ i/255.0 for i in rgb ]
|
||||
return rgb[:] + [max(rgb), min(rgb), max(rgb)-min(rgb)]
|
||||
|
||||
def rotate_hsl(hsl, deg) :
|
||||
return (
|
||||
(hsl[0] + deg) % 360,
|
||||
hsl[1],
|
||||
hsl[2],
|
||||
)
|
||||
|
||||
def hsl_to_rgb(hsl) :
|
||||
h = hsl[0]
|
||||
s = hsl[1]
|
||||
l = hsl[2]
|
||||
|
||||
c = s * (1 - abs(2 * l))
|
||||
x = c * (1 - abs((h / 60) % 2 - 1))
|
||||
m = l - (c / 2)
|
||||
|
||||
rgbp = ()
|
||||
if h < 60 :
|
||||
rgbp = (c, x, 0)
|
||||
if h < 120 :
|
||||
rgbp = (x, c, 0)
|
||||
if h < 180 :
|
||||
rgbp = (0, c, x)
|
||||
if h < 240 :
|
||||
rgbp = (0, x, c)
|
||||
if h < 300 :
|
||||
rgbp = (x, 0, c)
|
||||
else:
|
||||
rgbp = (c, 0, x)
|
||||
|
||||
r = rgbp[0]
|
||||
g = rgbp[1]
|
||||
b = rgbp[2]
|
||||
return (
|
||||
(r+m)*255,
|
||||
(g+m)*255,
|
||||
(b+m)*255,
|
||||
)
|
||||
|
||||
def rgb_to_hex(rgb) :
|
||||
r = min(max(int(rgb[0]), 0), 255)
|
||||
g = min(max(int(rgb[1]), 0), 255)
|
||||
b = min(max(int(rgb[2]), 0), 255)
|
||||
return "#{:02x}{:02x}{:02x}".format(r, g, b)
|
||||
|
||||
from sys import argv
|
||||
main(argv[1:])
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 576eee5b824294fe6fa73984251995294904539d
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
body{
|
||||
font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;word-wrap:break-word;color:#dbdbdb;background:#421531;text-rendering:optimizeLegibility
|
||||
}.button,button,input,textarea{
|
||||
transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease
|
||||
}h1{
|
||||
font-size:2.2em;margin-top:0
|
||||
}h1,h2,h3,h4,h5,h6{
|
||||
margin-bottom:12px
|
||||
}h1,h2,h3,h4,h5,h6,strong{
|
||||
color:#fff
|
||||
}b,h1,h2,h3,h4,h5,h6,strong,th{
|
||||
font-weight:600
|
||||
}q:after,q:before{
|
||||
content:none
|
||||
}blockquote,q{
|
||||
border-left:4px solid #adb4a0;margin:1.5em 0;padding:.5em 1em;font-style:italic
|
||||
}blockquote>footer{
|
||||
font-style:normal;border:0
|
||||
}address,blockquote cite{
|
||||
font-style:normal
|
||||
}a[href^=mailto\:]:before{
|
||||
content:"📧 "
|
||||
}a[href^=tel\:]:before{
|
||||
content:"📞 "
|
||||
}a[href^=sms\:]:before{
|
||||
content:"💬 "
|
||||
}mark{
|
||||
background-color:#8fae83;border-radius:2px;padding:0 2px;color:#000
|
||||
}.button,button,input[type=button],input[type=checkbox],input[type=radio],input[type=range],input[type=submit],select{
|
||||
cursor:pointer
|
||||
}input:not([type=checkbox]):not([type=radio]),select{
|
||||
display:block
|
||||
}.button,button,input,select,textarea{
|
||||
color:#fff;background-color:#300330;font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none
|
||||
}.button,button,input,select,textarea{
|
||||
-webkit-appearance:none
|
||||
}textarea{
|
||||
margin-right:0;width:100%;box-sizing:border-box;resize:vertical
|
||||
}select{
|
||||
background:#300330 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 0 0-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;padding-right:35px
|
||||
}select::-ms-expand{
|
||||
display:none
|
||||
}select[multiple]{
|
||||
padding-right:10px;background-image:none;overflow-y:auto
|
||||
}.button,button,input[type=button],input[type=submit]{
|
||||
padding-right:30px;padding-left:30px
|
||||
}.button:hover,button:hover,input[type=button]:hover,input[type=submit]:hover{
|
||||
background:#55354c
|
||||
}.button:focus,button:focus,input:focus,select:focus,textarea:focus{
|
||||
box-shadow:0 0 0 2px #adb4a0
|
||||
}input[type=checkbox],input[type=radio]{
|
||||
position:relative;width:14px;height:14px;display:inline-block;vertical-align:middle;margin:0 2px 0 0
|
||||
}input[type=radio]{
|
||||
border-radius:50%
|
||||
}input[type=checkbox]:checked,input[type=radio]:checked{
|
||||
background:#55354c
|
||||
}input[type=checkbox]:checked:before,input[type=radio]:checked:before{
|
||||
content:"•";display:block;position:absolute;left:50%;top:50%;transform:translateX(-50%) translateY(-50%)
|
||||
}input[type=checkbox]:checked:before{
|
||||
content:"✔";transform:translateY(-50%) translateY(.5px) translateX(-6px)
|
||||
}.button:active,button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=range]:active,input[type=submit]:active{
|
||||
transform:translateY(2px)
|
||||
}.button:disabled,button:disabled,input:disabled,select:disabled,textarea:disabled{
|
||||
cursor:not-allowed;opacity:.5
|
||||
}::-webkit-input-placeholder{
|
||||
color:#a9a9a9
|
||||
}:-ms-input-placeholder{
|
||||
color:#a9a9a9
|
||||
}::-ms-input-placeholder{
|
||||
color:#a9a9a9
|
||||
}::placeholder{
|
||||
color:#a9a9a9
|
||||
}fieldset{
|
||||
border:1px solid #adb4a0;border-radius:6px;margin:0 0 6px;padding:10px
|
||||
}legend{
|
||||
font-size:.9em;font-weight:600
|
||||
}input[type=range]{
|
||||
margin:10px 0;padding:10px 0;background:transparent
|
||||
}input[type=range]:focus{
|
||||
outline:none
|
||||
}input[type=range]::-webkit-slider-runnable-track{
|
||||
width:100%;height:9.5px;transition:.2s;background:#300330;border-radius:3px
|
||||
}input[type=range]::-webkit-slider-thumb{
|
||||
box-shadow:0 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;-webkit-appearance:none;margin-top:-7px
|
||||
}input[type=range]:focus::-webkit-slider-runnable-track{
|
||||
background:#300330
|
||||
}input[type=range]::-moz-range-track{
|
||||
width:100%;height:9.5px;transition:.2s;background:#300330;border-radius:3px
|
||||
}input[type=range]::-moz-range-thumb{
|
||||
box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb
|
||||
}input[type=range]::-ms-track{
|
||||
width:100%;height:9.5px;background:transparent;border-color:transparent;border-width:16px 0;color:transparent
|
||||
}input[type=range]::-ms-fill-lower,input[type=range]::-ms-fill-upper{
|
||||
background:#300330;border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d
|
||||
}input[type=range]::-ms-thumb{
|
||||
box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;border:1px solid #000;height:20px;width:20px;border-radius:50%;background:#dbdbdb
|
||||
}input[type=range]:focus::-ms-fill-lower,input[type=range]:focus::-ms-fill-upper{
|
||||
background:#300330
|
||||
}a{
|
||||
text-decoration:none;color:#7fc090
|
||||
}a:hover{
|
||||
text-decoration:underline
|
||||
}code,samp,time{
|
||||
background:#300330;color:#dcff7f;padding:2.5px 5px;border-radius:6px;font-size:1em
|
||||
}pre>code{
|
||||
padding:10px;display:block;overflow-x:auto
|
||||
}var{
|
||||
color:#8a9e84;font-style:normal;font-family:monospace
|
||||
}kbd{
|
||||
background:#300330;border:1px solid #dbdbdb;border-radius:2px;color:#dbdbdb;padding:2px 4px
|
||||
}img{
|
||||
max-width:100%;height:auto
|
||||
}hr{
|
||||
border:none;border-top:1px solid #dbdbdb
|
||||
}table{
|
||||
border-collapse:collapse;margin-bottom:10px;width:100%
|
||||
}td,th{
|
||||
padding:6px;text-align:left
|
||||
}thead{
|
||||
border-bottom:1px solid #dbdbdb
|
||||
}tfoot{
|
||||
border-top:1px solid #dbdbdb
|
||||
}tbody tr:nth-child(2n){
|
||||
background-color:#3e0a2b
|
||||
}::-webkit-scrollbar{
|
||||
height:10px;width:10px
|
||||
}::-webkit-scrollbar-track{
|
||||
background:#300330;border-radius:6px
|
||||
}::-webkit-scrollbar-thumb{
|
||||
background:#55354c;border-radius:6px
|
||||
}::-webkit-scrollbar-thumb:hover{
|
||||
background:#6e4562
|
||||
}::-moz-selection{
|
||||
background-color:#200220
|
||||
}::selection{
|
||||
background-color:#200220
|
||||
}details{
|
||||
display:flex;flex-direction:column;align-items:flex-start;background-color:#3e0a2b;padding:10px 10px 0;margin:1em 0;border-radius:6px;overflow:hidden
|
||||
}details[open]{
|
||||
padding:10px
|
||||
}details>:last-child{
|
||||
margin-bottom:0
|
||||
}details[open] summary{
|
||||
margin-bottom:10px
|
||||
}summary{
|
||||
display:list-item;background-color:#300330;padding:10px;margin:-10px -10px 0
|
||||
}details>:not(summary){
|
||||
margin-top:0
|
||||
}summary::-webkit-details-marker{
|
||||
color:#dbdbdb
|
||||
}footer{
|
||||
border-top:1px solid #300330;padding-top:10px;font-size:.8em;color:#787878
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
background-color: #300330;
|
||||
}
|
||||
thead, tr:nth-child(2n) {
|
||||
background-color:#3e0a2b;
|
||||
background-color:#300330 !important;
|
||||
}
|
||||
nav {
|
||||
display: block;
|
||||
background: #300330;
|
||||
padding: .5pt;
|
||||
border-radius: 6px;
|
||||
}
|
||||
nav li li li li {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-height: 400px;
|
||||
}
|
||||
body {
|
||||
/*font-size: 125%;*/
|
||||
background: #3b242b;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
height: calc(100vh - 1em);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
body > .form {
|
||||
flex-grow: 2;
|
||||
}
|
||||
body > .form > form {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
body > .form > form > textarea ,
|
||||
body > .form > form > div {
|
||||
flex-grow: 2;
|
||||
}
|
||||
rendered > h2:nth-child(2) {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
padding: .25em;
|
||||
width: 100%;
|
||||
background: #3b242b;
|
||||
top: 0;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, h7, h8 {
|
||||
position: relative;
|
||||
}
|
||||
.comment {
|
||||
display: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
padding-right: 1em;
|
||||
width: 10em;
|
||||
top: 0;
|
||||
text-align: right;
|
||||
font-size: 12pt;
|
||||
}
|
||||
body > h2:first-of-type {
|
||||
margin: 0;
|
||||
margin-bottom: .5em;
|
||||
padding: .3em 0 .3em 0;
|
||||
background-color: #3b242b;
|
||||
z-index: 999;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
h1:hover > .comment,
|
||||
h2:hover > .comment,
|
||||
h3:hover > .comment,
|
||||
h4:hover > .comment,
|
||||
h5:hover > .comment,
|
||||
h6:hover > .comment,
|
||||
h7:hover > .comment,
|
||||
h8:hover > .comment,
|
||||
.comment:focus,
|
||||
.comment:focus-within {
|
||||
display: inline-flex;
|
||||
}
|
||||
.attachments > details > form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
.attachments > details > form > input:first-of-type {
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package filetree
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package filetree
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package filetree
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
|
@ -20,9 +20,20 @@ func NewPathFromLocal(p string) Path {
|
|||
if strings.HasPrefix(root, "./") {
|
||||
root = root[2:]
|
||||
}
|
||||
if strings.HasSuffix(root, "/") {
|
||||
root = root[:len(root)-1]
|
||||
}
|
||||
splits := strings.SplitN(p, root, 2)
|
||||
for len(splits) > 0 && splits[0] == "" {
|
||||
splits = splits[1:]
|
||||
}
|
||||
if len(splits) == 0 {
|
||||
splits = []string{"", ""}
|
||||
}
|
||||
href := splits[0]
|
||||
if len(splits) > 1 && (splits[0] == "" || splits[0] == "/") {
|
||||
if len(splits) == 1 && (splits[0] == root || splits[0] == config.Root) {
|
||||
href = ""
|
||||
} else if splits[0] == "" || splits[0] == "/" {
|
||||
href = splits[1]
|
||||
}
|
||||
href = path.Join("/notes", href)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package filetree
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
module gitea.inhome.blapointe.com/local/notes-server
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
gitea.inhome.blapointe.com/local/args v0.0.0-20240109214601-658deda479a4
|
||||
gitea.inhome.blapointe.com/local/gziphttp v0.0.0-20240109214739-0d639b0a9eb9
|
||||
gitea.inhome.blapointe.com/local/oauth2 v0.0.0-20240109220403-8897142866f1
|
||||
gitea.inhome.blapointe.com/local/router v0.0.0-20231109150317-e6b81bf7c23c
|
||||
github.com/fairlyblank/md2min v0.0.0-20171213131418-39cd6e9904ac
|
||||
github.com/gomarkdown/markdown v0.0.0-20220607163217-45f7c050e2d1
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/yuin/goldmark v1.3.4-0.20210326114109-75d8cce5b78c
|
||||
)
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
gitea.inhome.blapointe.com/local/args v0.0.0-20240109214601-658deda479a4 h1:4qBHjKAiEwRV1A1tN1JK6PsLV1+UwESXKrjGqfCCdNk=
|
||||
gitea.inhome.blapointe.com/local/args v0.0.0-20240109214601-658deda479a4/go.mod h1:SqCOE3bE3wvrztVIQGHuyxHKfDjRKU9EWhBdkmkiwyc=
|
||||
gitea.inhome.blapointe.com/local/gziphttp v0.0.0-20240109214739-0d639b0a9eb9 h1:Vq4jPYz6pCDMx7rxySw7SbKH3FHBywt1oS1TpXx4noM=
|
||||
gitea.inhome.blapointe.com/local/gziphttp v0.0.0-20240109214739-0d639b0a9eb9/go.mod h1:YSOO/quInfKxfgqHH8exd71292hkLgqu2BO5oz6EATE=
|
||||
gitea.inhome.blapointe.com/local/logb v0.0.0-20231109150430-1221d87a6dbc/go.mod h1:KwilawX4UgD4HxSJAVFEzkuckrnHeQrd49KwUX6GpYU=
|
||||
gitea.inhome.blapointe.com/local/oauth2 v0.0.0-20240109220403-8897142866f1 h1:GO2/M+5a5i4FqiOnG6hq73nITDv2Rf8oTF973RsYQgo=
|
||||
gitea.inhome.blapointe.com/local/oauth2 v0.0.0-20240109220403-8897142866f1/go.mod h1:XvS1/YJqCL+HoLo76dSLTjRYR+4Hfg4srhEVhMK3L8E=
|
||||
gitea.inhome.blapointe.com/local/router v0.0.0-20231109150317-e6b81bf7c23c h1:+9kTZ3+ziuvmo+mC/xh/wVogFIAT1LeMzJMfSk0onsE=
|
||||
gitea.inhome.blapointe.com/local/router v0.0.0-20231109150317-e6b81bf7c23c/go.mod h1:xYz/cn236KTBSWNQ9bqCyBmy8aQ7VL95pDDJmeupmN4=
|
||||
gitea.inhome.blapointe.com/local/storage v0.0.0-20231109151605-736d446d407d/go.mod h1:TRK5z/XTT6jws++Q21Y8DQot+5vZGTNeHf+RjuY8aQk=
|
||||
github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg=
|
||||
github.com/Azure/azure-storage-blob-go v0.0.0-20181023070848-cf01652132cc/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Unknwon/goconfig v0.0.0-20181105214110-56bd8ab18619/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
||||
github.com/a8m/tree v0.0.0-20180321023834-3cf936ce15d6/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
|
||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||
github.com/anacrolix/dms v0.0.0-20180117034613-8af4925bffb5/go.mod h1:DGqLjaZ3ziKKNRt+U5Q9PLWJ52Q/4rxfaaH/b3QYKaE=
|
||||
github.com/aws/aws-sdk-go v1.15.81/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/billziss-gh/cgofuse v1.1.0/go.mod h1:LJjoaUojlVjgo5GQoEJTcJNqZJeRU0nCR84CyxKt2YM=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/coreos/bbolt v0.0.0-20180318001526-af9db2027c98/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/djherbis/times v1.1.0/go.mod h1:CGMZlo255K5r4Yw0b9RRfFQpM2y7uOmxg4jm9HsaVf8=
|
||||
github.com/dropbox/dropbox-sdk-go-unofficial v5.4.0+incompatible/go.mod h1:lr+LhMM3F6Y3lW1T9j2U5l7QeuWm87N9+PPXo3yH4qY=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fairlyblank/md2min v0.0.0-20171213131418-39cd6e9904ac h1:MufpS7OgWOVDMM5dTYwJ3bHDlAc0/dPKUTpTLcFyi50=
|
||||
github.com/fairlyblank/md2min v0.0.0-20171213131418-39cd6e9904ac/go.mod h1:QAobgT+CwT/SRphqV6Jrz5jt3wkW9Q72QNquEvh6dLk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
|
||||
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
|
||||
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
|
||||
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
|
||||
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
|
||||
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
|
||||
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
|
||||
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
|
||||
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
|
||||
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
|
||||
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
|
||||
github.com/goftp/server v0.0.0-20190111142836-88de73f463af/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20220607163217-45f7c050e2d1 h1:wAupuFkZ/yq219/mSbqDtMfUZQY0gTYEtoz3/LKtppU=
|
||||
github.com/gomarkdown/markdown v0.0.0-20220607163217-45f7c050e2d1/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jlaffaye/ftp v0.0.0-20181101112434-47f21d10f0ee/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v6 v6.0.57/go.mod h1:5+R/nM9Pwrh0vqF+HbYYDQ84wdUFPyXHkrdT4AIkifM=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/ncw/go-acd v0.0.0-20171120105400-887eb06ab6a2/go.mod h1:MLIrzg7gp/kzVBxRE1olT7CWYMCklcUWU+ekoxOD9x0=
|
||||
github.com/ncw/rclone v1.46.0/go.mod h1:+uFY4HNpat/yXXIEin5ETWXxIwEplC+eDe/vT8vlk1w=
|
||||
github.com/ncw/swift v1.0.44/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/nsf/termbox-go v0.0.0-20181027232701-60ab7e3d12ed/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd/go.mod h1:4soZNh0zW0LtYGdQ416i0jO0EIqMGcbtaspRS4BDvRQ=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pengsrc/go-shared v0.2.0/go.mod h1:jVblp62SafmidSkvWrXyxAme3gaTfEtWwRPGz5cpvHg=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.8.3/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rfjakob/eme v0.0.0-20171028163933-2222dbd4ba46/go.mod h1:U2bmx0hDj8EyDdcxmD5t3XHDnBFnyNNc22n1R4008eM=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sevlyar/go-daemon v0.1.4/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20190205172012-55a226cf41da/go.mod h1:XWL4vDyd3JKmJx+hZWUVgCNmmhZ2dTBcaNDcxH465s0=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.3.4-0.20210326114109-75d8cce5b78c h1:cPcogYxzMuZfHQkeQtauv6Pq/cwtO4TPhAnWeq0v5q8=
|
||||
github.com/yuin/goldmark v1.3.4-0.20210326114109-75d8cce5b78c/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yunify/qingstor-sdk-go v2.2.15+incompatible/go.mod h1:w6wqLDQ5bBTzxGJ55581UrSwLrsTAsdo9N6yX/8d9RY=
|
||||
go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20181120235003-faade3cbb06a/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
8
main.go
8
main.go
|
|
@ -1,9 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"local/notes-server/server"
|
||||
"local/notes-server/versions"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/notes/editor"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/server"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/versions"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -12,6 +13,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
log.Println(len(editor.CodeMirrorCSS))
|
||||
server := server.New()
|
||||
if err := server.Routes(); err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"local/notes-server/config"
|
||||
"local/notes-server/server"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/server"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (n *Notes) Comment(urlPath string, lineno int, comment string) error {
|
||||
p := filetree.NewPathFromURL(urlPath)
|
||||
if stat, err := os.Stat(p.Local); err != nil {
|
||||
return errors.New("cannot comment as it does not exist")
|
||||
} else if stat.IsDir() {
|
||||
return errors.New("cannot comment on a dir")
|
||||
}
|
||||
f, err := os.Open(p.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
f2, err := ioutil.TempFile(os.TempDir(), path.Base(p.Local)+".*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f2.Close()
|
||||
reader := bufio.NewReader(f)
|
||||
writer := io.Writer(f2)
|
||||
for i := 0; i < lineno+1; i++ {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, bytes.NewReader(line))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
formatted := fmt.Sprintf("> *%s*\n\n", comment)
|
||||
_, err = io.Copy(writer, strings.NewReader(formatted))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, reader)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
f2.Close()
|
||||
return os.Rename(f2.Name(), p.Local)
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComment(t *testing.T) {
|
||||
d, err := ioutil.TempDir(os.TempDir(), "testComment.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
f, err := ioutil.TempFile(d, "testFile.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Write([]byte(`
|
||||
hello
|
||||
world
|
||||
# i have a heading
|
||||
## i have a subber heading
|
||||
### i have a[heading with](http://google.com) a hyperlink
|
||||
## *I think this heading is in italics*
|
||||
`))
|
||||
f.Close()
|
||||
fpath := path.Join("/comment", strings.TrimPrefix(f.Name(), d))
|
||||
config.Root = d
|
||||
n := &Notes{}
|
||||
t.Logf("d=%s, fpath=%s", d, fpath)
|
||||
|
||||
if err := n.Comment("/comment/a", 5, "a"); err == nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := n.Comment(fpath, -1, "illegal line no"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := n.Comment(fpath, 10000, "big line no"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := n.Comment(fpath, 0, "first_line_OK"); err != nil {
|
||||
t.Error(err)
|
||||
} else if b, err := ioutil.ReadFile(f.Name()); err != nil {
|
||||
t.Error(err)
|
||||
} else if !bytes.Contains(b, []byte("> *first_line_OK*\n")) {
|
||||
t.Errorf("%s", b)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package notes
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"path"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"local/notes-server/filetree"
|
||||
"fmt"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (n *Notes) Delete(urlPath string) error {
|
||||
p := filetree.NewPathFromURL(urlPath)
|
||||
if p.IsDir() {
|
||||
return errors.New("path is dir")
|
||||
err := os.Remove(p.Local)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to delete %q => %q: %v", urlPath, p.Local, err)
|
||||
}
|
||||
return os.Remove(p.Local)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package notes
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -11,10 +12,43 @@ func TestDelete(t *testing.T) {
|
|||
config.Root = "/tmp"
|
||||
ioutil.WriteFile("/tmp/a", []byte("hi"), os.ModePerm)
|
||||
n := &Notes{}
|
||||
|
||||
t.Run("delete 404", func(t *testing.T) {
|
||||
if err := n.Delete("/notes/a"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err := os.Stat("/tmp/a"); err == nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("delete w. content", func(t *testing.T) {
|
||||
d, err := ioutil.TempDir(os.TempDir(), "trydel*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.Root = path.Dir(d)
|
||||
for i := 0; i < 3; i++ {
|
||||
f, err := ioutil.TempFile(d, "file*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
if err := n.Delete("/abc/" + path.Base(d)); err == nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("delete empty dir", func(t *testing.T) {
|
||||
d2p := os.TempDir()
|
||||
d2, err := ioutil.TempDir(d2p, "trydel*")
|
||||
config.Root = path.Dir(d2p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := n.Delete("/abc/" + path.Base(d2)); err != nil {
|
||||
t.Errorf("failed to del empty dir %s in %s: %v", d2, d2p, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package notes
|
|||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
)
|
||||
|
||||
func (n *Notes) Dir(urlPath string) (string, string, error) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/notes/editor"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -26,11 +27,49 @@ func editFile(p filetree.Path) string {
|
|||
}
|
||||
b, _ := ioutil.ReadFile(p.Local)
|
||||
return fmt.Sprintf(`
|
||||
<form action="/submit/%s" method="post" style="width:100%%; height: 90%%">
|
||||
<table style="width:100%%; height: 90%%">
|
||||
<textarea name="content" style="width:100%%; min-height:90%%">%s</textarea>
|
||||
</table>
|
||||
<div class="form">
|
||||
<form action="/submit/%s" method="post">
|
||||
<textarea id="mytext" name="content" style="cursor:crosshair;">%s</textarea>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
`, href, b)
|
||||
</div>
|
||||
<!--
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/codemirror.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/codemirror.min.css
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/keymap/vim.min.js
|
||||
https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.59.4/theme/dracula.min.css
|
||||
-->
|
||||
<script>%s</script>
|
||||
<style>%s</style>
|
||||
<script>%s</script>
|
||||
<style>%s</style>
|
||||
<script>
|
||||
if( ! /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
|
||||
CodeMirror.fromTextArea(document.getElementById("mytext"), {
|
||||
lineNumbers: true,
|
||||
mode: "md",
|
||||
theme: "dracula",
|
||||
keyMap: "vim",
|
||||
smartIndent: true,
|
||||
indentUnit: 3,
|
||||
tabSize: 3,
|
||||
indentWithTabs: false,
|
||||
lineWrapping: true,
|
||||
autofocus: true,
|
||||
dragDrop: false,
|
||||
spellcheck: true,
|
||||
autocorrect: false,
|
||||
autocapitalize: false,
|
||||
extraKeys: {
|
||||
Tab: (cm) => {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
|
||||
cm.replaceSelection(spaces);
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
console.log(navigator.userAgent)
|
||||
}
|
||||
</script>
|
||||
`, href, b, editor.CodeMirrorJS, editor.CodeMirrorCSS, editor.CodeMirrorVIM, editor.CodeMirrorTheme)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.cm-s-dracula .CodeMirror-gutters,.cm-s-dracula.CodeMirror{background-color:#282a36!important;color:#f8f8f2!important;border:none}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:solid thin #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:rgba(255,255,255,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:rgba(255,255,255,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:rgba(255,255,255,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-keyword{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute{color:#50fa7b}.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:rgba(255,255,255,.1)}.cm-s-dracula .CodeMirror-matchingbracket{text-decoration:underline;color:#fff!important}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package editor
|
||||
|
||||
import _ "embed"
|
||||
|
||||
var (
|
||||
//go:embed codemirror.min.js
|
||||
CodeMirrorJS string
|
||||
|
||||
//go:embed codemirror.min.css
|
||||
CodeMirrorCSS string
|
||||
|
||||
//go:embed vim.min.js
|
||||
CodeMirrorVIM string
|
||||
|
||||
//go:embed dracula.min.css
|
||||
CodeMirrorTheme string
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
126
notes/file.go
126
notes/file.go
|
|
@ -1,13 +1,23 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/notes/md"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/fairlyblank/md2min"
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
blackfriday "github.com/russross/blackfriday/v2"
|
||||
"github.com/yuin/goldmark"
|
||||
)
|
||||
|
||||
func (n *Notes) File(urlPath string) (string, error) {
|
||||
|
|
@ -16,10 +26,108 @@ func (n *Notes) File(urlPath string) (string, error) {
|
|||
return "", errors.New("path is dir")
|
||||
}
|
||||
b, _ := ioutil.ReadFile(p.Local)
|
||||
renderer := html.NewRenderer(html.RendererOptions{
|
||||
Flags: html.CommonFlags | html.TOC,
|
||||
})
|
||||
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
|
||||
content := markdown.ToHTML(b, parser, renderer)
|
||||
return string(content) + "\n", nil
|
||||
return n.gomarkdown(urlPath, b)
|
||||
return n.blackfriday(urlPath, b)
|
||||
return n.goldmark(urlPath, b)
|
||||
return n.md2min(urlPath, b)
|
||||
}
|
||||
|
||||
func (n *Notes) blackfriday(urlPath string, b []byte) (string, error) {
|
||||
renderer := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
|
||||
Flags: blackfriday.TOC | blackfriday.Smartypants | blackfriday.SmartypantsFractions | blackfriday.SmartypantsDashes | blackfriday.SmartypantsQuotesNBSP | blackfriday.CompletePage,
|
||||
})
|
||||
return string(blackfriday.Run(
|
||||
b,
|
||||
blackfriday.WithRenderer(renderer),
|
||||
)), nil
|
||||
}
|
||||
|
||||
func (n *Notes) goldmark(urlPath string, b []byte) (string, error) {
|
||||
buff := bytes.NewBuffer(nil)
|
||||
err := goldmark.Convert(b, buff)
|
||||
return string(buff.Bytes()), err
|
||||
}
|
||||
|
||||
func (n *Notes) md2min(urlPath string, b []byte) (string, error) {
|
||||
mdc := md2min.New("h1")
|
||||
buff := bytes.NewBuffer(nil)
|
||||
err := mdc.Parse(b, buff)
|
||||
return string(buff.Bytes()), err
|
||||
}
|
||||
|
||||
func (n *Notes) gomarkdown(urlPath string, b []byte) (string, error) {
|
||||
return md.Gomarkdown(b, n.commentFormer(urlPath, b))
|
||||
}
|
||||
|
||||
func (n *Notes) commentFormer(urlPath string, md []byte) html.RenderNodeFunc {
|
||||
urlPath = strings.TrimPrefix(urlPath, "/")
|
||||
urlPath = strings.TrimPrefix(urlPath, strings.Split(urlPath, "/")[0])
|
||||
lines := bytes.Split(md, []byte("\n"))
|
||||
cur := -1
|
||||
nextHeader := func() {
|
||||
cur++
|
||||
for cur < len(lines) {
|
||||
for _, opener_closer := range [][]string{{"```", "```"}, {`<summary>`, `</summary>`}} {
|
||||
if cur < len(lines) && bytes.Contains(lines[cur], []byte(opener_closer[0])) {
|
||||
cur++
|
||||
for cur < len(lines) && !bytes.Contains(lines[cur], []byte(opener_closer[1])) {
|
||||
cur++
|
||||
}
|
||||
cur++
|
||||
}
|
||||
}
|
||||
if cur >= len(lines) {
|
||||
break
|
||||
}
|
||||
line := lines[cur]
|
||||
ok, err := regexp.Match(`^\s*#+\s*.+$`, line)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if ok {
|
||||
return
|
||||
}
|
||||
cur++
|
||||
}
|
||||
}
|
||||
return func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
||||
if heading, ok := node.(*ast.Heading); !n.RO && ok && !entering {
|
||||
nextHeader()
|
||||
fmt.Fprintf(w, `
|
||||
<form method="POST" action=%q class="comment">
|
||||
<input name="lineno" type="number" style="display:none" value="%d"/>
|
||||
<input autocomplete="off" name="content" type="text"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
`, path.Join("/comment", urlPath)+"#"+heading.HeadingID, cur)
|
||||
}
|
||||
return ast.GoToNext, false
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notes) commentFormerOld() html.RenderNodeFunc {
|
||||
return func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
|
||||
if heading, ok := node.(*ast.Heading); ok {
|
||||
if !entering {
|
||||
literal := ""
|
||||
ast.WalkFunc(heading, func(n ast.Node, e bool) ast.WalkStatus {
|
||||
if leaf := n.AsLeaf(); e && leaf != nil {
|
||||
if literal != "" {
|
||||
literal += ".*"
|
||||
}
|
||||
literal += string(leaf.Literal)
|
||||
}
|
||||
return ast.GoToNext
|
||||
})
|
||||
level := heading.Level
|
||||
id := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(`^[ \t]*%s\s*%s(].*)?\s*$`, strings.Repeat("#", level), literal)))
|
||||
fmt.Fprintf(w, `
|
||||
<form method="POST" target="/comment" name="%s" class="comment">
|
||||
<input autocomplete="off" name="content" type="text"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
`, id)
|
||||
}
|
||||
}
|
||||
return ast.GoToNext, false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package notes
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package md
|
||||
|
||||
import (
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
)
|
||||
|
||||
func Gomarkdown(b []byte, renderHook html.RenderNodeFunc) (string, error) {
|
||||
renderer := html.NewRenderer(html.RendererOptions{
|
||||
Flags: html.CommonFlags | html.TOC,
|
||||
RenderNodeHook: renderHook,
|
||||
})
|
||||
ext := parser.NoExtensions
|
||||
for _, extension := range []parser.Extensions{
|
||||
parser.NoIntraEmphasis,
|
||||
parser.Tables,
|
||||
parser.FencedCode,
|
||||
parser.Autolink,
|
||||
parser.Strikethrough,
|
||||
parser.SpaceHeadings,
|
||||
parser.HeadingIDs,
|
||||
parser.BackslashLineBreak,
|
||||
parser.DefinitionLists,
|
||||
parser.MathJax,
|
||||
parser.Titleblock,
|
||||
parser.AutoHeadingIDs,
|
||||
parser.Includes,
|
||||
} {
|
||||
ext |= extension
|
||||
}
|
||||
parser := parser.NewWithExtensions(ext)
|
||||
content := markdown.ToHTML(b, parser, renderer)
|
||||
return string(content) + "\n", nil
|
||||
}
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
package notes
|
||||
|
||||
import "local/notes-server/config"
|
||||
import "gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
|
||||
type Notes struct {
|
||||
root string
|
||||
RO bool
|
||||
}
|
||||
|
||||
func New() *Notes {
|
||||
return &Notes{
|
||||
root: config.Root,
|
||||
RO: config.ReadOnly,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,51 +2,111 @@ package notes
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"local/notes-server/filetree"
|
||||
"errors"
|
||||
"io"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type searcher struct {
|
||||
patterns []*regexp.Regexp
|
||||
}
|
||||
|
||||
func newSearcher(phrase string) (*searcher, error) {
|
||||
phrases := strings.Split(phrase, " ")
|
||||
patterns := make([]*regexp.Regexp, 0)
|
||||
for _, phrase := range phrases {
|
||||
if len(phrase) == 0 {
|
||||
continue
|
||||
}
|
||||
pattern, err := regexp.Compile("(?i)" + phrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patterns = append(patterns, pattern)
|
||||
}
|
||||
if len(patterns) < 1 {
|
||||
return nil, errors.New("no search specified")
|
||||
}
|
||||
return &searcher{
|
||||
patterns: patterns,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *searcher) matches(input []byte) bool {
|
||||
for _, pattern := range s.patterns {
|
||||
if !pattern.Match(input) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *Notes) Search(phrase string) (string, error) {
|
||||
searcher, err := newSearcher(phrase)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
perffiles := filetree.NewFiles()
|
||||
files := filetree.NewFiles()
|
||||
err := filepath.Walk(n.root,
|
||||
err = filepath.Walk(n.root,
|
||||
func(walked string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
if splits := strings.Split(info.Name(), "."); len(splits) > 1 && !(strings.HasSuffix(info.Name(), ".md") || strings.HasSuffix(info.Name(), ".txt")) {
|
||||
return nil
|
||||
}
|
||||
ok, err := grepFile(walked, []byte(phrase))
|
||||
if err == nil && ok {
|
||||
p := filetree.NewPathFromLocal(path.Dir(walked))
|
||||
files.Push(p, info)
|
||||
if ok, _ := searcher.stream(strings.NewReader(info.Name())); ok {
|
||||
perffiles.Push(p, info)
|
||||
return nil
|
||||
}
|
||||
if size := info.Size(); size < 1 || size > (5*1024*1024) {
|
||||
return nil
|
||||
}
|
||||
ok, err := searcher.file(walked)
|
||||
if err != nil && err.Error() == "bufio.Scanner: token too long" {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("failed to scan %v: %v", walked, err)
|
||||
} else if ok {
|
||||
files.Push(p, info)
|
||||
}
|
||||
return err
|
||||
},
|
||||
)
|
||||
return filetree.Paths(*files).List(true), err
|
||||
for _, file := range *files {
|
||||
*perffiles = append(*perffiles, file)
|
||||
}
|
||||
return filetree.Paths(*perffiles).List(true), err
|
||||
}
|
||||
|
||||
func grepFile(file string, phrase []byte) (bool, error) {
|
||||
func (searcher *searcher) file(file string) (bool, error) {
|
||||
if d := path.Base(path.Dir(file)); strings.HasPrefix(d, ".") && strings.HasSuffix(d, ".attachments") {
|
||||
return false, nil
|
||||
}
|
||||
if strings.Contains(file, "/.git/") {
|
||||
return false, nil
|
||||
}
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
return searcher.stream(f)
|
||||
}
|
||||
|
||||
func (searcher *searcher) stream(r io.Reader) (bool, error) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
if bytes.Contains(scanner.Bytes(), phrase) {
|
||||
if searcher.matches(scanner.Bytes()) {
|
||||
return true, scanner.Err()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func TestSearch(t *testing.T) {
|
|||
t.Fatal(v, result)
|
||||
}
|
||||
|
||||
result, err = n.Search("4")
|
||||
result, err = n.Search("number.4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -42,3 +42,13 @@ func TestSearch(t *testing.T) {
|
|||
t.Fatal(v, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchBigFiles(t *testing.T) {
|
||||
n := New()
|
||||
n.root = "/usr/gitea.inhome.blapointe.com/local/bin"
|
||||
|
||||
_, err := n.Search("this file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package notes
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package notes
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
asdf
|
||||
|
||||
this contains my search string
|
||||
|
|
@ -1,5 +1,18 @@
|
|||
asdf
|
||||
searchString
|
||||
# h1
|
||||
|
||||
hi
|
||||
|
||||
here is my new line
|
||||
## h2
|
||||
|
||||
hi
|
||||
|
||||
### h3
|
||||
|
||||
hi
|
||||
|
||||
#### h4
|
||||
|
||||
hi
|
||||
|
||||
* bullet
|
||||
* 1
|
||||
|
|
@ -1 +1,3 @@
|
|||
hi
|
||||
# Hello
|
||||
|
||||
## World
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"html"
|
||||
"local/notes-server/filetree"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (n *Notes) Create(w http.ResponseWriter, r *http.Request) {
|
||||
content := r.FormValue("base")
|
||||
content = html.UnescapeString(content)
|
||||
content = strings.ReplaceAll(content, "\r", "")
|
||||
urlPath := path.Join(r.URL.Path, content)
|
||||
p := filetree.NewPathFromURL(urlPath)
|
||||
if p.IsDir() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
url := *r.URL
|
||||
url.Path = path.Join("/edit/", p.BaseHREF)
|
||||
http.Redirect(w, r, url.String(), http.StatusSeeOther)
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package notes
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
func notesDir(p Path, w http.ResponseWriter, r *http.Request) {
|
||||
dirs, files := lsDir(p)
|
||||
content := dirs.List()
|
||||
notesDirHead(p, w)
|
||||
block(content, w)
|
||||
fmt.Fprintln(w, files.List())
|
||||
}
|
||||
|
||||
func notesDirHead(p Path, w http.ResponseWriter) {
|
||||
fmt.Fprintf(w, `
|
||||
<form action=%q method="get">
|
||||
<input type="text" name="base"></input>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
`, path.Join("/create/", p.BaseHREF))
|
||||
}
|
||||
|
||||
func lsDir(path Path) (Paths, Paths) {
|
||||
dirs := newDirs()
|
||||
files := newFiles()
|
||||
|
||||
found, _ := ioutil.ReadDir(path.Local)
|
||||
for _, f := range found {
|
||||
dirs.Push(path, f)
|
||||
files.Push(path, f)
|
||||
}
|
||||
return Paths(*dirs), Paths(*files)
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLsDir(t *testing.T) {
|
||||
p := Path{Local: "/usr/local"}
|
||||
dirs, files := lsDir(p)
|
||||
if len(dirs) == 0 {
|
||||
t.Fatal(len(dirs))
|
||||
}
|
||||
if len(files) == 0 {
|
||||
t.Fatal(len(files))
|
||||
}
|
||||
t.Log(dirs)
|
||||
t.Log(files)
|
||||
}
|
||||
|
||||
func TestNotesDir(t *testing.T) {
|
||||
path := Path{Local: "/usr/local"}
|
||||
w := httptest.NewRecorder()
|
||||
notesDir(path, w, nil)
|
||||
t.Logf("%s", w.Body.Bytes())
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) edit(w http.ResponseWriter, r *http.Request) {
|
||||
p := NewPathFromURL(r.URL.Path)
|
||||
if p.IsDir() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
head(w, r)
|
||||
editHead(w, p)
|
||||
editFile(w, p)
|
||||
foot(w, r)
|
||||
}
|
||||
|
||||
func editHead(w http.ResponseWriter, p Path) {
|
||||
fmt.Fprintln(w, h2(p.MultiLink()))
|
||||
}
|
||||
|
||||
func editFile(w http.ResponseWriter, p Path) {
|
||||
href := p.HREF
|
||||
href = strings.TrimPrefix(href, "/")
|
||||
hrefs := strings.SplitN(href, "/", 2)
|
||||
href = hrefs[0]
|
||||
if len(hrefs) > 1 {
|
||||
href = hrefs[1]
|
||||
}
|
||||
b, _ := ioutil.ReadFile(p.Local)
|
||||
fmt.Fprintf(w, `
|
||||
<form action="/submit/%s" method="post" style="width:100%%; height: 90%%">
|
||||
<table style="width:100%%; height: 90%%">
|
||||
<textarea name="content" style="width:100%%; min-height:90%%">%s</textarea>
|
||||
</table>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
`, href, b)
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package notes
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
)
|
||||
|
||||
func notesFile(p Path, w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := ioutil.ReadFile(p.Local)
|
||||
notesFileHead(p, w)
|
||||
renderer := html.NewRenderer(html.RendererOptions{
|
||||
Flags: html.CommonFlags | html.TOC,
|
||||
})
|
||||
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
|
||||
content := markdown.ToHTML(b, parser, renderer)
|
||||
fmt.Fprintf(w, "%s\n", content)
|
||||
}
|
||||
|
||||
func notesFileHead(p Path, w http.ResponseWriter) {
|
||||
fmt.Fprintf(w, `
|
||||
<a href=%q><input type="button" value="Edit"></input></a>
|
||||
`, path.Join("/edit/", p.BaseHREF))
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotesFile(t *testing.T) {
|
||||
f, err := ioutil.TempFile(os.TempDir(), "until*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
fmt.Fprintln(f, `
|
||||
# Hello
|
||||
## World
|
||||
* This
|
||||
* is
|
||||
* bullets
|
||||
|
||||
| My | table | goes |
|
||||
|----|-------|------|
|
||||
| h | e | n |
|
||||
|
||||
`)
|
||||
f.Close()
|
||||
w := httptest.NewRecorder()
|
||||
p := Path{Local: f.Name()}
|
||||
notesFile(p, w, nil)
|
||||
s := string(w.Body.Bytes())
|
||||
shouldContain := []string{
|
||||
"tbody",
|
||||
"h1",
|
||||
"h2",
|
||||
}
|
||||
for _, should := range shouldContain {
|
||||
if !strings.Contains(s, should) {
|
||||
t.Fatalf("%s: %s", should, s)
|
||||
}
|
||||
}
|
||||
t.Logf("%s", s)
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package notes
|
||||
|
||||
import "local/notes-server/config"
|
||||
|
||||
type Notes struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func New() *Notes {
|
||||
return &Notes{
|
||||
root: config.Root,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) notes(w http.ResponseWriter, r *http.Request) {
|
||||
p := NewPathFromURL(r.URL.Path)
|
||||
if p.IsDir() {
|
||||
head(w, r)
|
||||
notesHead(w, p)
|
||||
notesDir(p, w, r)
|
||||
foot(w, r)
|
||||
} else if p.IsFile() {
|
||||
head(w, r)
|
||||
notesHead(w, p)
|
||||
notesFile(p, w, r)
|
||||
foot(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func notesHead(w http.ResponseWriter, p Path) {
|
||||
fmt.Fprintln(w, h2(p.MultiLink()))
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package notes
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package notes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) submit(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
content := r.FormValue("content")
|
||||
content = html.UnescapeString(content)
|
||||
content = strings.ReplaceAll(content, "\r", "")
|
||||
p := NewPathFromURL(r.URL.Path)
|
||||
os.MkdirAll(path.Dir(p.Local), os.ModePerm)
|
||||
if err := ioutil.WriteFile(p.Local, []byte(content), os.ModePerm); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintln(w, err)
|
||||
} else {
|
||||
url := *r.URL
|
||||
url.Path = path.Join("/notes/", p.BaseHREF)
|
||||
http.Redirect(w, r, url.String(), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package notes
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) attach(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s._attach(w, r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) _attach(w http.ResponseWriter, r *http.Request) error {
|
||||
r.ParseMultipartForm(100 << 20)
|
||||
file, handler, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
ft := filetree.NewPathFromURL(r.URL.Path)
|
||||
local := ft.Local
|
||||
target := path.Join(path.Dir(local), "."+path.Base(local)+".attachments", handler.Filename)
|
||||
|
||||
os.MkdirAll(path.Dir(target), os.ModePerm)
|
||||
if fi, err := os.Stat(target); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
} else if err == nil && fi.IsDir() {
|
||||
return errors.New("invalid path")
|
||||
}
|
||||
f, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/notes"+strings.TrimPrefix(ft.HREF, "/attach"), http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"html"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) comment(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
linenos := r.FormValue("lineno")
|
||||
lineno, err := strconv.Atoi(linenos)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
comment := r.FormValue("content")
|
||||
comment = html.UnescapeString(comment)
|
||||
comment = strings.ReplaceAll(comment, "\r", "")
|
||||
|
||||
err = s.Notes.Comment(r.URL.Path, lineno, comment)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
url := *r.URL
|
||||
url.Path = path.Join("/notes/", filetree.NewPathFromURL(r.URL.Path).BaseHREF)
|
||||
http.Redirect(w, r, url.String(), http.StatusSeeOther)
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
@ -22,8 +22,8 @@ func h1(content string) string {
|
|||
return h("1", content)
|
||||
}
|
||||
|
||||
func h2(content string) string {
|
||||
return h("2", content)
|
||||
func h2(content string, style ...string) string {
|
||||
return h("2", content, style...)
|
||||
}
|
||||
|
||||
func h3(content string) string {
|
||||
|
|
@ -38,6 +38,10 @@ func h5(content string) string {
|
|||
return h("5", content)
|
||||
}
|
||||
|
||||
func h(level, content string) string {
|
||||
return fmt.Sprintf("\n<h%s>\n%s\n</h%s>\n", level, content, level)
|
||||
func h(level, content string, style ...string) string {
|
||||
s := ""
|
||||
if len(style) > 0 {
|
||||
s = fmt.Sprintf("style=%q", style[0])
|
||||
}
|
||||
return fmt.Sprintf("\n<h%s %s>\n%s\n</h%s>\n", level, s, content, level)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
|
@ -39,7 +39,7 @@ func TestBlock(t *testing.T) {
|
|||
|
||||
func TestH(t *testing.T) {
|
||||
s := strings.ReplaceAll(strings.TrimSpace(h2("hi")), "\n", ".")
|
||||
if ok, err := regexp.MatchString("<h2>.*hi.*<.h2>", s); err != nil {
|
||||
if ok, err := regexp.MatchString("<h2[ ]*>.*hi.*<.h2>", s); err != nil {
|
||||
t.Fatal(err, s)
|
||||
} else if !ok {
|
||||
t.Fatal(ok, s)
|
||||
|
|
|
|||
106
server/notes.go
106
server/notes.go
|
|
@ -2,9 +2,12 @@ package server
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) notes(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -23,12 +26,7 @@ func (s *Server) notes(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func notesHead(w http.ResponseWriter, p filetree.Path) {
|
||||
fmt.Fprintln(w, h2(p.MultiLink()))
|
||||
fmt.Fprintf(w, `
|
||||
<form action=%q method="post">
|
||||
<input type="text" name="keywords"></input>
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
`, "/search")
|
||||
htmlSearch(w)
|
||||
}
|
||||
|
||||
func (s *Server) dir(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -43,12 +41,8 @@ func (s *Server) dir(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func dirHead(w http.ResponseWriter, baseHREF string) {
|
||||
fmt.Fprintf(w, `
|
||||
<form action=%q method="get">
|
||||
<input type="text" name="base"></input>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
`, path.Join("/create/", baseHREF))
|
||||
htmlCreate(w, baseHREF)
|
||||
htmlDelete(w, baseHREF)
|
||||
}
|
||||
|
||||
func (s *Server) file(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -57,15 +51,85 @@ func (s *Server) file(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
fileHead(w, filetree.NewPathFromURL(r.URL.Path).BaseHREF)
|
||||
s.fileHead(w, r.URL.Path)
|
||||
fmt.Fprintln(w, file)
|
||||
}
|
||||
|
||||
func fileHead(w http.ResponseWriter, baseHREF string) {
|
||||
fmt.Fprintf(w, `
|
||||
<a href=%q><input type="button" value="Edit"></input></a>
|
||||
`, path.Join("/edit/", baseHREF))
|
||||
fmt.Fprintf(w, `
|
||||
<a href=%q><input type="button" value="Delete"></input></a>
|
||||
`, path.Join("/delete/", baseHREF))
|
||||
func (s *Server) fileHead(w http.ResponseWriter, path string) {
|
||||
baseHREF := filetree.NewPathFromURL(path).BaseHREF
|
||||
htmlEdit(w, baseHREF)
|
||||
htmlDelete(w, baseHREF)
|
||||
s.htmlAttachments(w, path)
|
||||
}
|
||||
|
||||
func htmlEdit(w http.ResponseWriter, baseHREF string) {
|
||||
if config.ReadOnly {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, `<div style='display:inline-block; margin-top: .75em; margin-bottom: .75em;'>
|
||||
<a href=%q class="button">Edit</a>
|
||||
</div><br>`, path.Join("/edit/", baseHREF))
|
||||
}
|
||||
|
||||
func htmlDelete(w http.ResponseWriter, baseHREF string) {
|
||||
if config.ReadOnly {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, `<div style='display:inline-block; margin-top: .75em; margin-bottom: .75em;'>
|
||||
<a href=%q class="button" onclick="return confirm('Delete?');">Delete</a>
|
||||
</div><br>`, path.Join("/delete/", baseHREF))
|
||||
}
|
||||
|
||||
func (s *Server) htmlAttachments(w http.ResponseWriter, urlPath string) {
|
||||
dir := path.Dir(urlPath)
|
||||
f := "." + path.Base(urlPath) + ".attachments"
|
||||
_, files, _ := s.Notes.Dir(path.Join(dir, f))
|
||||
form := fmt.Sprintf(`
|
||||
<form enctype="multipart/form-data" action="/attach/%s" method="post">
|
||||
<input type="file" name="file" required/>
|
||||
<input type="submit" value="+"/>
|
||||
</form>
|
||||
`, strings.TrimPrefix(urlPath, "/notes/"))
|
||||
if config.ReadOnly {
|
||||
form = ""
|
||||
}
|
||||
lines := strings.Split(files, "\n")
|
||||
for i := range lines {
|
||||
pattern := regexp.MustCompile(`\.(png|jpg|jpeg|gif)">`)
|
||||
if !pattern.MatchString(lines[i]) {
|
||||
lines[i] = strings.ReplaceAll(lines[i], "<a", "<a download")
|
||||
}
|
||||
lines[i] = strings.ReplaceAll(lines[i], `href="/notes`, `href="/raw`)
|
||||
}
|
||||
files = strings.Join(lines, "\n")
|
||||
fmt.Fprintf(w, `<div style='display:inline-block' class="attachments">
|
||||
<details>
|
||||
<summary style="display: flex">
|
||||
<span style="flex-grow: 2">Attachments</span>
|
||||
</summary>
|
||||
%s
|
||||
%s
|
||||
</details>
|
||||
</div><br>`, form, files)
|
||||
}
|
||||
|
||||
func htmlCreate(w http.ResponseWriter, baseHREF string) {
|
||||
if config.ReadOnly {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, `
|
||||
<form action=%q method="get">
|
||||
<input type="text" name="base"></input>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
`, path.Join("/create/", baseHREF))
|
||||
}
|
||||
|
||||
func htmlSearch(w http.ResponseWriter) {
|
||||
fmt.Fprintf(w, `
|
||||
<form action=%q method="post" >
|
||||
<input type="text" name="keywords"></input>
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
`, "/search")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.inhome.blapointe.com/local/gziphttp"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
)
|
||||
|
||||
func (s *Server) raw(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s._raw(w, r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) _raw(w http.ResponseWriter, r *http.Request) error {
|
||||
gziphttp.SetContentTypeIfMedia(w, r)
|
||||
p := filetree.NewPathFromURL(r.URL.Path)
|
||||
if !p.IsFile() {
|
||||
http.NotFound(w, r)
|
||||
return nil
|
||||
}
|
||||
f, err := os.Open(p.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -2,48 +2,67 @@ package server
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"local/router"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gitea.inhome.blapointe.com/local/gziphttp"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/router"
|
||||
)
|
||||
|
||||
func (s *Server) Routes() error {
|
||||
wildcard := router.Wildcard
|
||||
endpoints := []struct {
|
||||
path string
|
||||
endpoints := map[string]struct {
|
||||
handler http.HandlerFunc
|
||||
}{
|
||||
{
|
||||
path: "/",
|
||||
"/": {
|
||||
handler: s.root,
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("notes/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.notes),
|
||||
fmt.Sprintf("raw/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.cached(s.raw))),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("edit/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.edit),
|
||||
fmt.Sprintf("notes/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.notes)),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("delete/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.delete),
|
||||
fmt.Sprintf("edit/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.edit)),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("submit/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.submit),
|
||||
fmt.Sprintf("delete/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.delete)),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("create/%s%s", wildcard, wildcard),
|
||||
handler: s.authenticate(s.create),
|
||||
fmt.Sprintf("submit/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.submit)),
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("search"),
|
||||
handler: s.authenticate(s.search),
|
||||
fmt.Sprintf("create/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.create)),
|
||||
},
|
||||
fmt.Sprintf("attach/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.attach)),
|
||||
},
|
||||
fmt.Sprintf("comment/%s%s", wildcard, wildcard): {
|
||||
handler: s.gzip(s.authenticate(s.comment)),
|
||||
},
|
||||
fmt.Sprintf("search"): {
|
||||
handler: s.gzip(s.authenticate(s.search)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if err := s.Add(endpoint.path, endpoint.handler); err != nil {
|
||||
for path, endpoint := range endpoints {
|
||||
if config.ReadOnly {
|
||||
for _, prefix := range []string{
|
||||
"edit/",
|
||||
"delete/",
|
||||
"create/",
|
||||
"submit/",
|
||||
"attach/",
|
||||
} {
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
endpoint.handler = http.NotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.Add(path, endpoint.handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -54,3 +73,17 @@ func (s *Server) root(w http.ResponseWriter, r *http.Request) {
|
|||
r.URL.Path = "/notes"
|
||||
http.Redirect(w, r, r.URL.String(), http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) gzip(h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if gziphttp.Can(r) {
|
||||
gz := gziphttp.New(w)
|
||||
defer gz.Close()
|
||||
w = gz
|
||||
}
|
||||
if filepath.Ext(r.URL.Path) == ".css" {
|
||||
w.Header().Set("Content-Type", "text/css; charset=utf-8")
|
||||
}
|
||||
h(w, r)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package server
|
|||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ func (s *Server) search(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
head(w, r)
|
||||
fmt.Fprintln(w, h2(filetree.NewPathFromURL("/notes").MultiLink()))
|
||||
htmlSearch(w)
|
||||
fmt.Fprintln(w, h1(keywords), results)
|
||||
foot(w, r)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"local/notes-server/notes"
|
||||
"local/oauth2/oauth2client"
|
||||
"local/router"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/notes"
|
||||
"gitea.inhome.blapointe.com/local/oauth2/oauth2client"
|
||||
"gitea.inhome.blapointe.com/local/router"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
|
@ -21,6 +22,13 @@ func New() *Server {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Server) cached(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "private, max-age=7776000")
|
||||
foo(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) authenticate(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if config.OAuthServer != "" {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"local/notes-server/filetree"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/filetree"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
|
@ -16,6 +18,7 @@ func (s *Server) submit(w http.ResponseWriter, r *http.Request) {
|
|||
content := r.FormValue("content")
|
||||
content = html.UnescapeString(content)
|
||||
content = strings.ReplaceAll(content, "\r", "")
|
||||
content = trimLines(content)
|
||||
err := s.Notes.Submit(r.URL.Path, content)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
|
@ -25,3 +28,12 @@ func (s *Server) submit(w http.ResponseWriter, r *http.Request) {
|
|||
url.Path = path.Join("/notes/", filetree.NewPathFromURL(r.URL.Path).BaseHREF)
|
||||
http.Redirect(w, r, url.String(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func trimLines(s string) string {
|
||||
buff := bytes.NewBuffer(nil)
|
||||
for _, line := range strings.Split(s, "\n") {
|
||||
fmt.Fprintln(buff, strings.TrimRight(line, "\n \r\t"))
|
||||
}
|
||||
fixed := string(buff.Bytes())
|
||||
return fixed[:len(fixed)-1]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package server
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTrimLines(t *testing.T) {
|
||||
t.Run("no newline at end", func(t *testing.T) {
|
||||
s := `
|
||||
hello
|
||||
world`
|
||||
s += " "
|
||||
s += " \t"
|
||||
got := trimLines(s)
|
||||
want := `
|
||||
hello
|
||||
world`
|
||||
if got != want {
|
||||
t.Fatalf("want %q, got %q", want, got)
|
||||
}
|
||||
})
|
||||
t.Run("noop", func(t *testing.T) {
|
||||
s := `hi`
|
||||
got := trimLines(s)
|
||||
want := `hi`
|
||||
if got != want {
|
||||
t.Fatalf("want %q, got %q", want, got)
|
||||
}
|
||||
})
|
||||
t.Run("newline", func(t *testing.T) {
|
||||
s := "hi\n"
|
||||
got := trimLines(s)
|
||||
want := "hi\n"
|
||||
if got != want {
|
||||
t.Fatalf("want %q, got %q", want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package versions
|
||||
|
||||
import (
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"time"
|
||||
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package versions
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package versions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getScript() string {
|
||||
maxSizeMB := config.MaxSizeMB
|
||||
if maxSizeMB == 0 {
|
||||
maxSizeMB = 1
|
||||
}
|
||||
return strings.ReplaceAll(script, "{{{MAXSIZE}}}", fmt.Sprint(maxSizeMB<<20))
|
||||
}
|
||||
|
||||
const script = `#!/bin/bash
|
||||
function main() {
|
||||
local maxsize={{{MAXSIZE}}}
|
||||
if [[ "$maxsize" == 0 ]]; then
|
||||
return
|
||||
fi
|
||||
(
|
||||
git diff --name-only --cached
|
||||
git diff --name-only
|
||||
git ls-files --others --exclude-standard
|
||||
) 2>&1 \
|
||||
| sort -u \
|
||||
| while read -r file; do
|
||||
local size="$(du -sk "$file" | awk '{print $1}')000"
|
||||
if [ "$size" -gt "$maxsize" ]; then
|
||||
echo "file=$file, size=$size, max=$maxsize" >&2
|
||||
git reset HEAD -- "$file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
main
|
||||
`
|
||||
|
|
@ -2,8 +2,13 @@ package versions
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"local/notes-server/config"
|
||||
"io/ioutil"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -15,7 +20,9 @@ func New() (*Versions, error) {
|
|||
v.cmd("git", "init")
|
||||
v.cmd("git", "config", "user.email", "user@user.user")
|
||||
v.cmd("git", "config", "user.name", "user")
|
||||
return v, nil
|
||||
s := getScript()
|
||||
err := ioutil.WriteFile(path.Join(config.Root, "./.git/hooks/pre-commit"), []byte(s), os.ModePerm)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (v *Versions) Gitmmit() error {
|
||||
|
|
@ -39,6 +46,9 @@ func (v *Versions) Commit() error {
|
|||
func (v *Versions) cmd(cmd string, args ...string) error {
|
||||
command := exec.Command(cmd, args...)
|
||||
command.Dir = config.Root
|
||||
_, err := command.CombinedOutput()
|
||||
out, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(cmd, args, ":", strings.TrimSpace(string(out)))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package versions
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/notes-server/config"
|
||||
"gitea.inhome.blapointe.com/local/notes-server/config"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
|
|
|||
28
wrapper.html
28
wrapper.html
|
|
@ -1,28 +0,0 @@
|
|||
<html>
|
||||
<header>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<!-- https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css -->
|
||||
<style>
|
||||
@charset "UTF-8";body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;color:#dbdbdb;background:#202b38;text-rendering:optimizeLegibility}button,input,textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px}h1,h2,h3,h4,h5,h6,strong{color:#fff}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}blockquote{border-left:4px solid rgba(0,150,191,.67);margin:1.5em 0;padding:.5em 1em;font-style:italic}blockquote>footer{margin-top:10px;font-style:normal}address,blockquote cite{font-style:normal}a[href^=mailto]:before{content:"📧 "}a[href^=tel]:before{content:"📞 "}a[href^=sms]:before{content:"💬 "}button,input[type=button],input[type=checkbox],input[type=submit]{cursor:pointer}input:not([type=checkbox]):not([type=radio]),select{display:block}button,input,select,textarea{color:#fff;background-color:#161f27;font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}button,input:not([type=checkbox]):not([type=radio]),select,textarea{-webkit-appearance:none}textarea{margin-right:0;width:100%;box-sizing:border-box;resize:vertical}button,input[type=button],input[type=submit]{padding-right:30px;padding-left:30px}button:hover,input[type=button]:hover,input[type=submit]:hover{background:#324759}button:focus,input:focus,select:focus,textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67)}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-webkit-input-placeholder{color:#a9a9a9}:-ms-input-placeholder{color:#a9a9a9}::-ms-input-placeholder{color:#a9a9a9}::placeholder{color:#a9a9a9}a{text-decoration:none;color:#41adff}a:hover{text-decoration:underline}code,kbd{background:#161f27;color:#ffbe85;padding:5px;border-radius:6px}pre>code{padding:10px;display:block;overflow-x:auto}img{max-width:100%}hr{border:none;border-top:1px solid #dbdbdb}table{border-collapse:collapse;margin-bottom:10px;width:100%}td,th{padding:6px;text-align:left}th{border-bottom:1px solid #dbdbdb}tbody tr:nth-child(2n){background-color:#161f27}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#161f27;border-radius:6px}::-webkit-scrollbar-thumb{background:#324759;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#415c73}
|
||||
</style>
|
||||
<style>
|
||||
nav {
|
||||
display: block;
|
||||
background: #161f27;
|
||||
padding: .5pt;
|
||||
border-radius: 6px;
|
||||
}
|
||||
nav li li li li {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-height: 400px;
|
||||
}
|
||||
</style>
|
||||
</header>
|
||||
<body height="100%">
|
||||
{{{}}}
|
||||
</body>
|
||||
<footer>
|
||||
</footer>
|
||||
</html>
|
||||
Loading…
Reference in New Issue