mirror of
https://github.com/tiyn/amphora-wiki.git
synced 2025-10-17 05:11:16 +02:00
Compare commits
8 Commits
4dbd75a1a9
...
master
Author | SHA1 | Date | |
---|---|---|---|
d5cf8d6d13 | |||
ff35dbe3ae | |||
ff169d2795 | |||
6287111899 | |||
cdf6660cae | |||
0bc1a955a6 | |||
563ac37e5e | |||
f766803f3b |
@@ -10,6 +10,8 @@ RUN pip3 install -r requirements.txt
|
||||
|
||||
VOLUME /wiki/templates/entry
|
||||
|
||||
VOLUME /wiki/static/graphics
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
ENTRYPOINT [ "python3" ]
|
||||
|
15
README.md
15
README.md
@@ -15,7 +15,7 @@ I however just want to put my markdown files in a directory and get a working wi
|
||||
- [ ] Option to get plain text file
|
||||
- [ ] Optimize CSS for code
|
||||
- [x] Start page
|
||||
- [ ] Overview of pages and namespaces
|
||||
- [x] Overview of pages and namespaces
|
||||
- [x] Search page
|
||||
- [x] Full-text search
|
||||
- [x] Show first few lines of each match (preview)
|
||||
@@ -60,12 +60,13 @@ The `config.py` can be found in the `src` folder.
|
||||
|
||||
Set the following volumes with the -v tag.
|
||||
|
||||
| Volume-Name | Container mount | Description |
|
||||
| ----------- | ------------------------- | ------------------------------------------------------------ |
|
||||
| config-file | /wiki/src/config.py | Config file |
|
||||
| entries | /wiki/src/templates/entry | Directory for wiki entries |
|
||||
| css | /wiki/src/static/css | (optional) Directory for css files |
|
||||
| html | /wiki/src/templates | (optional) Directory for templates (entry-volume not needed) |
|
||||
| Volume-Name | Container mount | Description |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------ |
|
||||
| `config-file` | `/wiki/config.py` | Config file |
|
||||
| `entries` | `/wiki/templates/entry` | Directory for wiki entries |
|
||||
| `graphics` | `/wiki/static/graphics` | Directory for images needed for entries |
|
||||
| `css` | `/wiki/static/css` | (optional) Directory for css files |
|
||||
| `html` | `/wiki/templates` | (optional) Directory for templates (entry-volume not needed) |
|
||||
|
||||
#### Ports
|
||||
|
||||
|
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
docker-compose up -d --build
|
||||
|
38
src/app.py
38
src/app.py
@@ -18,38 +18,38 @@ STYLE = config.STYLE
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('error.html', title=TITLE, errorcode='404', style=STYLE), 404
|
||||
return render_template("error.html", title=TITLE, errorcode="404", style=STYLE), 404
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/index.html')
|
||||
@app.route("/")
|
||||
@app.route("/index.html")
|
||||
def index():
|
||||
return render_template('index.html', title=TITLE, style=STYLE), 200
|
||||
return render_template("index.html", title=TITLE, style=STYLE), 200
|
||||
|
||||
|
||||
@app.route('/search', methods=['GET', 'POST'])
|
||||
@app.route('/search.html', methods=['GET', 'POST'])
|
||||
@app.route("/search", methods=["GET", "POST"])
|
||||
@app.route("/search.html", methods=["GET", "POST"])
|
||||
def search():
|
||||
form = SearchForm()
|
||||
if request.method == 'POST':
|
||||
query_str = request.form['query_str']
|
||||
if request.method == "POST":
|
||||
query_str = request.form["query_str"]
|
||||
content = cont.gen_query_res_string(query_str)
|
||||
return render_template('search.html', title=TITLE, style=STYLE, form=form, content=content), 200
|
||||
return render_template('search.html', title=TITLE, style=STYLE, form=form, content=''), 200
|
||||
return render_template("search.html", title=TITLE, style=STYLE, form=form, content=content), 200
|
||||
return render_template("search.html", title=TITLE, style=STYLE, form=form, content=""), 200
|
||||
|
||||
|
||||
@app.route('/entry/<path:fullurl>')
|
||||
@app.route("/entry/<path:fullurl>")
|
||||
def entry(fullurl):
|
||||
content = cont.gen_stand_string(fullurl)
|
||||
return render_template('entry.html', title=TITLE, style=STYLE, content=content), 200
|
||||
return render_template("entry.html", title=TITLE, style=STYLE, content=content), 200
|
||||
|
||||
@app.route('/namespace/', defaults={'fullurl': ''})
|
||||
@app.route('/namespace/<path:fullurl>')
|
||||
def namespace(fullurl=''):
|
||||
@app.route("/namespace/", defaults={"fullurl": ""})
|
||||
@app.route("/namespace/<path:fullurl>")
|
||||
def namespace(fullurl=""):
|
||||
content = cont.gen_arch_string(fullurl)
|
||||
if content == None:
|
||||
return render_template('error.html', title=TITLE, errorcode='404', style=STYLE), 404
|
||||
return render_template('entry.html', title=TITLE, style=STYLE, content=content), 200
|
||||
return render_template("error.html", title=TITLE, errorcode="404", style=STYLE), 404
|
||||
return render_template("entry.html", title=TITLE, style=STYLE, content=content), 200
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0')
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0")
|
||||
|
@@ -1,13 +1,13 @@
|
||||
# Name/title of your blog
|
||||
TITLE = 'Amphora Wiki'
|
||||
TITLE = "Amphora Wiki"
|
||||
|
||||
# URL for your website: e.g. https://domain.tld
|
||||
WEBSITE = 'localhost:5000'
|
||||
WEBSITE = "localhost:5000"
|
||||
|
||||
# Theme for the blog: dark, light
|
||||
STYLE = 'dark'
|
||||
STYLE = "dark"
|
||||
|
||||
###############################################
|
||||
## NO CHANGES RECOMMENDED ##
|
||||
###############################################
|
||||
ENTRY_DIR = 'templates/entry'
|
||||
ENTRY_DIR = "templates/entry"
|
||||
|
@@ -26,13 +26,13 @@ def gen_stand_string(path_ex):
|
||||
string: html-formatted string string equivalent to the file
|
||||
"""
|
||||
filename = os.path.join(ENTRY_DIR, path_ex)
|
||||
result = ''
|
||||
result = ""
|
||||
if path.exists(filename):
|
||||
title = open(filename,encoding='utf-8').readline().rstrip('\n').lstrip('#').lstrip(' ')
|
||||
text = open(filename,encoding='utf-8').readlines()[1:]
|
||||
filename_no_end = filename.split('.', 1)[0]
|
||||
result += '<h1>' + title + '</h1>\n'
|
||||
if filename.endswith('.md'):
|
||||
title = open(filename,encoding="utf-8").readline().rstrip("\n").lstrip("#").lstrip(" ")
|
||||
text = open(filename,encoding="utf-8").readlines()[1:]
|
||||
filename_no_end = filename.split(".", 1)[0]
|
||||
result += "<h1>" + title + "</h1>\n"
|
||||
if filename.endswith(".md"):
|
||||
result += gen_md_content(filename, 1)
|
||||
return result
|
||||
|
||||
@@ -48,18 +48,18 @@ def gen_md_content(path_ex, depth):
|
||||
Returns:
|
||||
string: html-formatted string string equivalent to the markdown file
|
||||
"""
|
||||
result = ''
|
||||
result = ""
|
||||
if path.exists(path_ex):
|
||||
filename = path_ex.split('.', 1)
|
||||
filename = path_ex.split(".", 1)
|
||||
fileend = filename[len(filename) - 1]
|
||||
header = '#'
|
||||
header = "#"
|
||||
for i in range(depth):
|
||||
header += '#'
|
||||
header += ' '
|
||||
markdown_lines = open(path_ex, 'r',encoding='utf-8').readlines()[1:]
|
||||
markdown_text = ''
|
||||
header += "#"
|
||||
header += " "
|
||||
markdown_lines = open(path_ex, "r",encoding="utf-8").readlines()[1:]
|
||||
markdown_text = ""
|
||||
for line in markdown_lines:
|
||||
markdown_text += line.replace('# ', header)
|
||||
markdown_text += line.replace("# ", header)
|
||||
result = markdown.markdown(
|
||||
markdown_text, extensions=["fenced_code", "tables", "nl2br"])
|
||||
return result
|
||||
@@ -80,28 +80,27 @@ def gen_arch_string(path_ex):
|
||||
name_list = os.listdir(full_path)
|
||||
full_list = [os.path.join(full_path, i) for i in name_list]
|
||||
contents = sorted(full_list, key=os.path.getctime)
|
||||
content_string = ''
|
||||
last_month = ''
|
||||
content_string = ""
|
||||
last_month = ""
|
||||
for file in reversed(contents):
|
||||
filename = pathlib.PurePath(file)
|
||||
if os.path.isfile(filename):
|
||||
title = open(filename).readline().rstrip('\n').lstrip('#').lstrip(' ')
|
||||
entry_or_namespace = 'entry'
|
||||
title = open(filename).readline().rstrip("\n").lstrip("#").lstrip(" ")
|
||||
entry_or_namespace = "entry"
|
||||
elif os.path.isdir(filename):
|
||||
title = filename.name
|
||||
entry_or_namespace = 'namespace'
|
||||
entry_or_namespace = "namespace"
|
||||
else:
|
||||
continue
|
||||
filename = filename.name
|
||||
if filename[0] != '.' and filename.__contains__('.'):
|
||||
filename = filename.split('.', 1)[0]
|
||||
content_string += '<li>'
|
||||
content_string += title + ' ['
|
||||
content_string += '<a href="' + '/'+ entry_or_namespace +'/' + \
|
||||
path_ex.rstrip("/") + '/' + pathlib.PurePath(file).name + '">' + 'standalone' + '</a>'
|
||||
content_string += '] <br>'
|
||||
content_string += '</li>\n'
|
||||
content_string += '</ul>\n'
|
||||
if filename[0] != "." and filename.__contains__("."):
|
||||
filename = filename.split(".", 1)[0]
|
||||
content_string += "<li>"
|
||||
content_string += "<a href=\"" + "/"+ entry_or_namespace +"/" + \
|
||||
path_ex.rstrip("/") + "/" + pathlib.PurePath(file).name + "\">" + title + "</a>"
|
||||
content_string += "<br>"
|
||||
content_string += "</li>\n"
|
||||
content_string += "</ul>\n"
|
||||
return content_string
|
||||
|
||||
|
||||
@@ -117,17 +116,17 @@ def gen_query_res_string(query_str):
|
||||
string: html-formated search result
|
||||
"""
|
||||
src_results = search.search(query_str)
|
||||
res_string = ''
|
||||
res_string += '<ul>\n'
|
||||
res_string = ""
|
||||
res_string += "<ul>\n"
|
||||
for result in src_results:
|
||||
title = result['title']
|
||||
path = result['path']
|
||||
title = result["title"]
|
||||
path = result["path"]
|
||||
preview = create_preview(path)
|
||||
path = '/entry/' + path.split('/', 2)[2]
|
||||
res_string += '<li><a href="' + path + '">' + markdown.markdown(title) + '</a><br>'
|
||||
res_string += '<div class="description">' + preview + '</div>'
|
||||
res_string += '</li>'
|
||||
res_string += '</ul>\n'
|
||||
path = "/entry/" + path.split("/", 2)[2]
|
||||
res_string += "<li><a href=\"" + path + "\">" + markdown.markdown(title) + "</a><br>"
|
||||
res_string += "<div class=\"description\">" + preview + "</div>"
|
||||
res_string += "</li>"
|
||||
res_string += "</ul>\n"
|
||||
return res_string
|
||||
|
||||
|
||||
@@ -141,9 +140,9 @@ def create_preview(path):
|
||||
Returns:
|
||||
string: html-formated preview
|
||||
"""
|
||||
file = open(path, 'r',encoding='utf-8')
|
||||
file = open(path, "r",encoding="utf-8")
|
||||
first_lines = file.readlines()
|
||||
preview = ''
|
||||
preview = ""
|
||||
preview_length = 3
|
||||
for i, line in enumerate(first_lines):
|
||||
if i == 0:
|
||||
@@ -151,10 +150,10 @@ def create_preview(path):
|
||||
if i > preview_length:
|
||||
break
|
||||
if not line.isspace():
|
||||
preview += markdown.markdown(line) + '<br>'
|
||||
preview += markdown.markdown(line) + "<br>"
|
||||
else:
|
||||
preview_length += 1
|
||||
preview += '...<br>'
|
||||
preview += "...<br>"
|
||||
return preview
|
||||
|
||||
print(gen_arch_string('system-software'))
|
||||
print(gen_arch_string("system-software"))
|
||||
|
10
src/forms.py
10
src/forms.py
@@ -1,11 +1,9 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf import CSRFProtect
|
||||
from wtforms import TextField, SubmitField, ValidationError, validators
|
||||
from flask_wtf import CSRFProtect, FlaskForm
|
||||
from wtforms import StringField, SubmitField, ValidationError, validators
|
||||
|
||||
csrf = CSRFProtect()
|
||||
|
||||
|
||||
class SearchForm(FlaskForm):
|
||||
query_str = TextField(
|
||||
"Query", [validators.Required("Please enter the search term")])
|
||||
submit = SubmitField("Search")
|
||||
query_str = StringField("Query", [validators.DataRequired("Please enter the search term")])
|
||||
submit = SubmitField("Search")
|
||||
|
@@ -1,5 +1,5 @@
|
||||
Markdown==3.1.1
|
||||
WTForms==2.2.1
|
||||
Flask==1.1.2
|
||||
Flask_WTF==0.14.3
|
||||
Whoosh==2.7.4
|
||||
Markdown
|
||||
WTForms
|
||||
Flask
|
||||
Flask_WTF
|
||||
Whoosh
|
||||
|
@@ -29,7 +29,7 @@ def createSearchableData(root):
|
||||
for r, d, f in os.walk(root):
|
||||
for file in f:
|
||||
path = os.path.join(r, file)
|
||||
fp = open(path,encoding='utf-8')
|
||||
fp = open(path,encoding="utf-8")
|
||||
title = fp.readline()
|
||||
text = title + fp.read()
|
||||
writer.add_document(title=title, path=path, content=text)
|
||||
@@ -55,7 +55,7 @@ def search_times(query_str, topN):
|
||||
matches = s.search(query, limit=topN)
|
||||
for match in matches:
|
||||
results.append(
|
||||
{'title': match['title'], 'path': match['path'], 'match': match.score})
|
||||
{"title": match["title"], "path": match["path"], "match": match.score})
|
||||
return results
|
||||
|
||||
|
||||
|
@@ -6,7 +6,7 @@
|
||||
--error: rgb(255,0,0);
|
||||
--footerbg0: rgb(192,192,192);
|
||||
--link0: rgb(0,0,120);
|
||||
--link1: rgb(255,255,255);
|
||||
--link1: rgb(0,0,0);
|
||||
--menulink0: rgb(0,0,120);
|
||||
--menulink1: rgb(255,255,255);
|
||||
--menubg0: rgb(192,192,192);
|
||||
|
@@ -29,16 +29,6 @@ html {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
border-radius: 25px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
page-break-inside: avoid;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
footer {
|
||||
height: 100px;
|
||||
padding-top: 20px;
|
||||
@@ -48,10 +38,6 @@ footer .center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-height: 100%;
|
||||
padding-bottom: 50px;
|
||||
@@ -90,6 +76,10 @@ ul {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.main-menu-dropdown img {
|
||||
float: left;
|
||||
}
|
||||
.main-menu-dropdown span {
|
||||
float: left;
|
||||
font-family: monospace;
|
||||
@@ -165,3 +155,40 @@ ul {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.entry {
|
||||
border-radius: 0 10px 30px 0;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.entry h1,
|
||||
.entry h2 {
|
||||
margin: 5px auto 2px auto;
|
||||
}
|
||||
|
||||
.entry ul {
|
||||
padding-left: 20;
|
||||
}
|
||||
|
||||
figure {
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left:20px;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left:20px;
|
||||
}
|
||||
|
||||
code {
|
||||
border-radius: 25px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
page-break-inside: avoid;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
display: inline-block
|
||||
}
|
||||
|
BIN
src/static/graphics/logo.png
Normal file
BIN
src/static/graphics/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
@@ -2,20 +2,23 @@
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<link href="{{ url_for('static', filename='css/' + style + '.css') }}" rel="stylesheet" type="text/css">
|
||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='graphics/logo.png') }}">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width" initial-scale=1.0>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Menu -->
|
||||
<div class="main-menu-dropdown">
|
||||
<!-- <img class="logo" src="/static/images/logo.png"> -->
|
||||
<span>{{ title }}</span>
|
||||
<a href="{{ url_for('index') }}">
|
||||
<img class="logo" src="{{ url_for('static', filename='graphics/logo.png') }}">
|
||||
<span>{{ title }}</span>
|
||||
</a>
|
||||
<input type="checkbox" id="main-menu-check">
|
||||
<label for="main-menu-check" class="show-menu">☰</label>
|
||||
<div class="main-menu">
|
||||
<a href="/">Startpage</a>
|
||||
<a href="/namespace">Namespaces</a>
|
||||
<a href="/search">Search</a>
|
||||
<a href="{{ url_for('index') }}">Startpage</a>
|
||||
<a href="{{ url_for('namespace') }}">Namespaces</a>
|
||||
<a href="{{ url_for('search') }}">Search</a>
|
||||
<label for="main-menu-check" class="hide-menu">X</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,7 +29,7 @@
|
||||
<!-- Content -->
|
||||
<footer>
|
||||
<div class="center">
|
||||
Made with <a href="https://github.com/tiyn/tiyny-wiki">Amphora Wiki</a>.
|
||||
Made with <a href="https://github.com/tiyn/amphora-wiki">Amphora Wiki</a>.
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
Reference in New Issue
Block a user