Compare commits

...

19 Commits

Author SHA1 Message Date
d00645853b „README.md“ ändern 2019-09-25 22:38:58 +00:00
eb43108aa6 Merge branch 'master' of https://git.stuve-bamberg.de/mgoetz/codimd_note_overview 2019-07-10 23:21:41 +02:00
40ff55c97d Add beschd deletion script ever :)! 2019-07-10 23:19:59 +02:00
f24feb8f4d Close #7 2019-02-09 01:53:23 +01:00
Michael Götz
b3272693d1 Merge branch 'master' of https://git.wiai.de/mgoetz/codimd_note_overview 2019-02-07 17:09:56 +01:00
Michael Götz
9681b3d92e Add repo link 2019-02-07 17:08:37 +01:00
Michael Götz
99be38db25 Add dev docker-compose and dev Dockerfile wip 2019-02-07 16:41:00 +01:00
Michael Götz
9bceb0d4dd Add example env 2019-02-07 16:38:49 +01:00
Michael Götz
a5d0c977bf Ignore env files 2019-02-07 16:38:01 +01:00
Götz
1097526f09 Merge branch 'master' of jboockmann/codimd_note_overview into master
date now iso 8601 conform
2019-02-07 14:47:58 +00:00
1f0294f4ac adapted date format to Ymd such that its string representation is properly sortable 2019-02-06 19:49:31 +01:00
f7ddd93179 adapted date format to Ymd such that its string representation is properly sortable 2019-02-06 19:43:27 +01:00
e85a2fdda4 Fix no date bug 2019-02-05 17:57:41 +01:00
Götz
aaaa4d4716 Merge branch 'master' of jboockmann/codimd_note_overview into master 2019-02-05 16:22:34 +00:00
4ce6118681 refined README 2019-01-31 17:52:09 +01:00
bc73cd1e90 added demo picture with less JPEG artefacts 2019-01-31 17:48:06 +01:00
9abb15de66 refined README 2019-01-31 17:45:47 +01:00
170fb6c3ae refined README, specify testing mode via ENV variable 2019-01-31 17:44:49 +01:00
bd20839335 table with search and sortable columns 2019-01-31 16:42:10 +01:00
14 changed files with 184 additions and 149 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ __pycache__/
config.py
*.pyc
.idea/
*.env

14
Dockerfile.dev Normal file
View File

@ -0,0 +1,14 @@
FROM alpine:3.8
ADD ["./requirements.txt", "/requirements.txt"]
RUN apk add --update --no-cache python3 postgresql-dev py3-psycopg2 && \
pip3 install -r /requirements.txt && \
rm /requirements.txt && \
adduser -D app
WORKDIR /app
CMD ["python3", "-u", "/app/main.py"]
EXPOSE 5000
USER app

View File

@ -0,0 +1,23 @@
# MOVED PERMANENTLY TO GITHUB
https://github.com/michigg/codimd_note_overview
# CodiMD note overview
a searchable list of existing hackmd pads [https://padlist.wiai.de/](https://padlist.wiai.de/)
![demo.jpg](demo.jpg)
## Testing locally
Execute `docker build . --tag codimd; docker run -p 5000:5000 --env TESTING=True codimd` to run the app in testing mode locally. No database credentials are required during testing mode; test data is read from a local file.
## Open issues
* make the layout responsive
* either read the DB credentials and URL from the environment or the config file
* load test data from a local file when run in testing mode
* add more/real testing data
* the up and down arrows in the table head fields are not invisible
* add more emojis ヘ( ^o^)(^_^ )

BIN
demo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View File

@ -1,16 +1,15 @@
version: "3"
networks:
db_net:
external: true
services:
codimd_note_overview:
build: .
build:
context: .
dockerfile: Dockerfile.dev
image: docker.wiai.de/fswiai/codimd_notes_overview:0.1
ports:
- 8080:5000
networks:
- db_net
env_file:
- /home/michigg/Nextcloud/Documents/OFU_Fachschaft/Server/hackmd_overview_addon/hackmd/docker.env
- ./docker.env
volumes:
- ./src:/app

7
docker_example.env Normal file
View File

@ -0,0 +1,7 @@
DB_HOST=<hostname>
POSTGRES_DB=<db name>
POSTGRES_USER=<user name>
POSTGRES_PASSWORD=<user password>
# Padlist
TESTING=<True|False>

View File

@ -0,0 +1,19 @@
import os
import psycopg2
DB_HOST = os.environ.get('DB_HOST')
DB_NAME = os.environ.get('POSTGRES_DB')
DB_USER = os.environ.get('POSTGRES_USER')
DB_PASSWORD = os.environ.get('POSTGRES_PASSWORD')
deletion_statement = 'DELETE FROM "Notes" WHERE "content" = \'\';'
try:
conn = psycopg2.connect(host=DB_HOST, database=DB_NAME, user=DB_USER, password=DB_PASSWORD)
cur = conn.cursor()
cur.execute(deletion_statement)
conn.commit()
cur.close()
conn.close()
except Exception as e:
print(e)

View File

@ -1,16 +1,32 @@
import psycopg2
from flask import Flask, render_template
import os
import config
from datetime import datetime
app = Flask(__name__)
app.debug = True
sql_statement = 'SELECT "id","title","alias","shortid","viewcount","lastchangeAt","permission","content" FROM "Notes" ORDER BY "lastchangeAt" DESC;'
SQL_FETCH_NOTES = 'SELECT "id","title","alias","shortid","viewcount","lastchangeAt","permission","content" FROM "Notes" ORDER BY "lastchangeAt" DESC;'
# if the environment contains a variable TESTING that is set to "True" then run the application in testing mode
if os.environ.get('TESTING') == "True":
ENV_TESTING = True
else:
ENV_TESTING = False
@app.route("/")
def main():
if ENV_TESTING:
return testing()
else:
return production()
def production():
import config
print('PRODUKTION')
DB_HOST = os.environ.get('DB_HOST')
DB_NAME = os.environ.get('POSTGRES_DB')
DB_USER = os.environ.get('POSTGRES_USER')
@ -20,7 +36,7 @@ def main():
conn = psycopg2.connect(host=DB_HOST, database=DB_NAME, user=DB_USER,
password=DB_PASSWORD)
cur = conn.cursor()
cur.execute(sql_statement)
cur.execute(SQL_FETCH_NOTES)
notes = cur.fetchall()
cur.close()
conn.close()
@ -31,8 +47,39 @@ def main():
'lastchangeAt': note[5], 'permission': note[6], 'content': note[7]})
return render_template('index.html', notes=notes_arr, host=config.CODI_URL)
except Exception as e:
print(e)
return render_template('index.html')
def testing():
notes_arr = []
notes_arr.append(
{
'id': "SJBWsApiX",
'title': "None",
'alias': "35c3",
'shortid': "SJBWsApiX",
'viewcount': "0",
'lastchangeAt': datetime(2013, 10, 31, 18, 23, 29, 227),
'permission': "freely",
'content': "xxx"
}
)
x = {
'id': "SJBWsApiX",
'title': "Protokoll für die Fachschaftssitzung vom 6. Februar 2019",
'alias': "35c3",
'shortid': "SJBWsApiX",
'viewcount': "0",
'lastchangeAt': datetime(2013, 10, 31, 18, 23, 29, 227),
'permission': "freely"
}
for _ in range(20):
notes_arr.append(x)
return render_template('index.html', notes=notes_arr, host="localhost")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

View File

@ -1,47 +0,0 @@
/*.extra-content {*/
/*display: none;*/
/*text-decoration: none !important;*/
/*color: #0b2e13;*/
/*}*/
/*.sortable a {*/
/*display: inline-block;*/
/*}*/
/*.sortable a:hover .extra-content {*/
/*display: block;*/
/*text-decoration: none !important;*/
/*}*/
/*.sortable a:hover .extra-content p {*/
/*display: block;*/
/*text-decoration: none !important;*/
/*}*/
.fancy-tooltip {
position: relative;
display: inline-block;
}
.fancy-tooltip .tooltiptext {
visibility: hidden;
width: 300px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: -170%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
line-height: 1em;
}
.fancy-tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}

11
src/static/css/style.css Normal file
View File

@ -0,0 +1,11 @@
/* Hide stuff from dataTables that we do not neeed */
.dataTables_length, .dataTables_info, .dataTables_paginate {
visibility: hidden
}
/* Add some spacing above and below the search field */
.dataTables_filter {
padding-top: 10%;
padding-bottom: 10%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

7
src/static/js/script.js Normal file
View File

@ -0,0 +1,7 @@
// make the table a dataTable and show all entries by default
$(document).ready(function () {
$('#example').DataTable({
"lengthMenu": [[-1], ["All"]]
});
});

View File

@ -1,61 +0,0 @@
function updateParent(notesArr) {
let parent = document.getElementById('sortable-wrapper');
parent.innerHTML = '';
for (let elem of notesArr) {
parent.appendChild(elem);
}
}
function convertToArray() {
var notes = document.getElementsByClassName("sortable");
var notesArr = [];
for (const node of notes) {
notesArr.push(node);
}
return notesArr;
}
function sortByTitle() {
var sort_by_title = function (a, b) {
let x = a.getElementsByClassName('note-title')[0];
let y = b.getElementsByClassName('note-title')[0];
return x.innerHTML.toLowerCase().localeCompare(y.innerHTML.toLowerCase());
};
var notesArr = convertToArray();
notesArr.sort(sort_by_title);
updateParent(notesArr);
}
function sortByDate() {
console.log('Sort Date');
var sort_by_date = function (a, b) {
a = a.getElementsByClassName('note-date')[0];
b = b.getElementsByClassName('note-date')[0];
if (a && b) {
a = a.innerHTML.split(' ');
let a_date = a[0].split('.');
let a_time = a[1].split(':');
b = b.innerHTML.split('\ ');
let b_date = b[0].split('.');
let b_time = b[1].split(':');
let x = new Date(a_date[2], a_date[1], a_date[0], a_time[0], a_time[1]).getTime();
let y = new Date(b_date[2], b_date[1], b_date[0], b_time[0], b_time[1]).getTime();
if (x === y) {
return 0;
} else if (x > y) {
return -1;
} else {
return 1;
}
} else {
return 0;
}
// return x.innerHTML.toLowerCase().localeCompare(y.innerHTML.toLowerCase());
};
var notesArr = convertToArray();
notesArr.sort(sort_by_date);
updateParent(notesArr);
}

View File

@ -1,45 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodiMD Notes</title>
<title>CodiMD notes</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.12/js/jquery.dataTables.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.13/js/dataTables.bootstrap4.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='libs/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
<script src="{{ url_for('static', filename='js/sort.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</head>
<body class="bg-light">
<body>
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">
CodiMD Notes
<h1>CodiMD notes</h1>
</a>
<div navbar-nav flex-row ml-md-auto d-none d-md-flex>
<a class="nav-item nav-link" href="https://git.wiai.de/mgoetz/codimd_note_overview" target="_blank">Fork me <img
src="{{ url_for('static', filename='images/gitea-192.png') }}" width="35px" heigt="35px"></a>
</div>
</nav>
<div class="container-fluid">
<div class="row mb-2">
<div class="col">
<button class="btn btn-success" onclick="sortByTitle()">Sort By Title</button>
<button class="btn btn-success" onclick="sortByDate()">Sort By Date</button>
</div>
</div>
<div id="sortable-wrapper" class="row">
<div class="container">
<table id="example" class="table table-striped table-inverse table-bordered table-hover" cellspacing="0"
width="100%">
<thead>
<tr>
<th>id</th>
<th>title</th>
<th>alias</th>
<th>last change at</th>
<th>permission</th>
<th>content</th>
</tr>
</thead>
<tbody>
{% for note in notes %}
<div class="fancy-tooltip sortable col-12">
<a class="" href="{{host}}/{{note['shortid']}}">
<h5 class="note-title">{{note['title']}}</h5>
<div class="tooltiptext">
{% if note['lastchangeAt'] %}
<h6 class="mb-2">LastChanged: <span class="note-date">{{note['lastchangeAt'].strftime('%d.%m.%Y %H:%M')}}</span>
</h6>
{% endif %}
<p>ViewCount: {{note['viewcount']}}</p>
<p>Permission: {{note['permission']}}</p>
<p>Alias: {{note['alias']}}</p>
<p>Short ID: {{note['shortid']}}</p>
<p>Content: {%if note['content']%}Ja{%else%}Nein{%endif%}</p>
</div>
</a>
</div>
<tr>
<td scope="row"><a href="{{host}}/{{note['shortid']}}" target="_blank">{{note['shortid']}}</a></td>
<td>{{note['title']}}</td>
<td>{{note['alias']}}</td>
<td><span class="note-date">{% if note['lastchangeAt'] %}{{note['lastchangeAt'].strftime('%Y-%m-%d %H:%M')}}{% endif %}</span>
</td>
<td>{{note['permission']}}</td>
<td>{%if note['content']%}💬{%else%}❌{%endif%}</td>
</tr>
{% endfor %}
</div>
</tbody>
</table>
</div>
</body>
</html>