Add inline_svg
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## 0.9-dev
|
## 0.9-dev
|
||||||
|
|
||||||
|
* Website redesign: added some material icons, implemented via a `inline_svg` function, injected by default in templates and defined in `utils.py`.
|
||||||
* Added positive feedback mechanism: now you can +1 a message. So, `score_message_add` and `score_message_remove` API endpoints were added, and `MessageUpvote` table was created.
|
* Added positive feedback mechanism: now you can +1 a message. So, `score_message_add` and `score_message_remove` API endpoints were added, and `MessageUpvote` table was created.
|
||||||
* Added notifications support for API.
|
* Added notifications support for API.
|
||||||
* Added `create_account` endpoint to API. This endpoint does not require an access token.
|
* Added `create_account` endpoint to API. This endpoint does not require an access token.
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ __version__ = '0.9-dev'
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
raise RuntimeError('Python 3 required')
|
raise RuntimeError('Python 3 required')
|
||||||
|
|
||||||
|
os.chdir(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_pyfile('../config.py')
|
app.config.from_pyfile('../config.py')
|
||||||
|
|
||||||
|
|
@ -60,7 +62,11 @@ def after_request(response):
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def _inject_variables():
|
def _inject_variables():
|
||||||
return {'site_name': app.config['SITE_NAME'], 'locations': locations}
|
return {
|
||||||
|
'site_name': app.config['SITE_NAME'],
|
||||||
|
'locations': locations,
|
||||||
|
'inline_svg': inline_svg
|
||||||
|
}
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def _inject_user(userid):
|
def _inject_user(userid):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Filter functions used in the website templates.
|
||||||
|
|
||||||
from flask import Markup
|
from flask import Markup
|
||||||
import html, datetime, re, time
|
import html, datetime, re, time
|
||||||
from .utils import tokenize
|
from .utils import tokenize, inline_svg as _inline_svg
|
||||||
from . import app
|
from . import app
|
||||||
|
|
||||||
@app.template_filter()
|
@app.template_filter()
|
||||||
|
|
@ -64,3 +64,4 @@ def is_following(from_user, to_user):
|
||||||
def locationdata(key):
|
def locationdata(key):
|
||||||
if key > 0:
|
if key > 0:
|
||||||
return locations[str(key)]
|
return locations[str(key)]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import os
|
||||||
# here should go `from .utils import get_current_user`, but it will cause
|
# here should go `from .utils import get_current_user`, but it will cause
|
||||||
# import errors. It's instead imported at function level.
|
# import errors. It's instead imported at function level.
|
||||||
|
|
||||||
database = SqliteDatabase(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'coriplus.sqlite'))
|
database = SqliteDatabase('coriplus.sqlite')
|
||||||
|
|
||||||
class BaseModel(Model):
|
class BaseModel(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -51,8 +51,7 @@ class User(BaseModel):
|
||||||
return False
|
return False
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
from .utils import get_current_user
|
return True
|
||||||
return self == get_current_user()
|
|
||||||
|
|
||||||
# it often makes sense to put convenience methods on model instances, for
|
# it often makes sense to put convenience methods on model instances, for
|
||||||
# example, "give me all the users this user is following":
|
# example, "give me all the users this user is following":
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
body,button,input,select,textarea{font-family:'Segoe UI',Arial,Helvetica,sans-serif}
|
body,button,input,select,textarea{font-family:Roboto,'Segoe UI',Arial,Helvetica,sans-serif}
|
||||||
body{margin:0}
|
body{margin:0}
|
||||||
.header{padding:12px;color:white;background-color:#ff3018}
|
a{text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
@media (max-width:640px){
|
||||||
|
.mobile-collapse{display:none}
|
||||||
|
}
|
||||||
|
.header{padding:12px;color:white;background-color:#ff3018;box-shadow:0 0 3px 3px #ccc}
|
||||||
.content{padding:12px}
|
.content{padding:12px}
|
||||||
.header a{color:white}
|
.header a{color:white}
|
||||||
|
.header a svg{fill:white}
|
||||||
.content a{color:#3399ff}
|
.content a{color:#3399ff}
|
||||||
|
.content a svg{fill:#3399ff}
|
||||||
.content a.plus{color:#ff3018}
|
.content a.plus{color:#ff3018}
|
||||||
.metanav{float:right}
|
.metanav{float:right}
|
||||||
.header h1{margin:0;display:inline-block}
|
.header h1{margin:0;display:inline-block}
|
||||||
|
|
@ -14,7 +21,9 @@ body{margin:0}
|
||||||
}
|
}
|
||||||
.weak{opacity:.5}
|
.weak{opacity:.5}
|
||||||
.field_desc{display:block}
|
.field_desc{display:block}
|
||||||
.message-visual img{max-width:100%;max-height:8em}
|
ul.timeline{padding:0;margin:auto;max-width:960px}
|
||||||
|
ul.timeline > li{list-style:none;border-bottom:#808080 1px solid}
|
||||||
|
.message-visual img{max-width:100%;margin:auto}
|
||||||
.message-options-showhide::before{content:'\2026'}
|
.message-options-showhide::before{content:'\2026'}
|
||||||
.message-options{display:none}
|
.message-options{display:none}
|
||||||
.create_text{width:100%;height:8em}
|
.create_text{width:100%;height:8em}
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,18 @@
|
||||||
<h1><a href="{{ url_for('website.homepage') }}">{{ site_name }}</a></h1>
|
<h1><a href="{{ url_for('website.homepage') }}">{{ site_name }}</a></h1>
|
||||||
<div class="metanav">
|
<div class="metanav">
|
||||||
{% if current_user.is_anonymous %}
|
{% if current_user.is_anonymous %}
|
||||||
<a href="{{ url_for('website.login', next=request.full_path) }}">log in</a>
|
<a href="{{ url_for('website.login', next=request.full_path) }}">{{ inline_svg('exit_to_app') }} log in</a>
|
||||||
<a href="{{ url_for('website.register', next=request.full_path) }}">register</a>
|
<a href="{{ url_for('website.register', next=request.full_path) }}">{{ inline_svg('person_add') }} register</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ url_for('website.user_detail', username=current_user.username) }}">{{ current_user.username }}</a>
|
<a href="{{ url_for('website.user_detail', username=current_user.username) }}">{{ inline_svg('person') }} {{ current_user.username }}</a>
|
||||||
{% set notification_count = current_user.unseen_notification_count() %}
|
{% set notification_count = current_user.unseen_notification_count() %}
|
||||||
{% if notification_count > 0 %}
|
{% if notification_count > 0 %}
|
||||||
<a href="{{ url_for('website.notifications') }}">(<strong>{{ notification_count }}</strong>)</a>
|
<a href="{{ url_for('website.notifications') }}">(<strong>{{ notification_count }}</strong>)</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
-
|
-
|
||||||
<a href="{{ url_for('website.public_timeline') }}">explore</a>
|
<a href="{{ url_for('website.public_timeline') }}">{{ inline_svg('explore') }} explore</a>
|
||||||
<a href="{{ url_for('website.create') }}">create</a>
|
<a href="{{ url_for('website.create') }}">{{ inline_svg('edit') }} create</a>
|
||||||
<a href="{{ url_for('website.logout') }}">log out</a>
|
<a href="{{ url_for('website.logout') }}">{{ inline_svg('exit_to_app') }} log out</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -36,7 +36,8 @@
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p class="copyright">© 2019 Sakuragasaki46.
|
<p class="copyright">© 2019 Sakuragasaki46.
|
||||||
<a href="/about/">About</a> - <a href="/terms/">Terms</a> -
|
<a href="/about/">About</a> - <a href="/terms/">Terms</a> -
|
||||||
<a href="/privacy/">Privacy</a></p>
|
<a href="/privacy/">Privacy</a> -
|
||||||
|
<a href="https://github.com/sakuragasaki46/coriplus">GitHub</a></p>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/lib.js"></script>
|
<script src="/static/lib.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h2>Explore</h2>
|
<h2>Explore</h2>
|
||||||
<ul>
|
<ul class="timeline">
|
||||||
{% for message in message_list %}
|
{% for message in message_list %}
|
||||||
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h2>Your Timeline</h2>
|
<h2>Your Timeline</h2>
|
||||||
<ul>
|
<ul class="timeline">
|
||||||
{% for message in message_list %}
|
{% for message in message_list %}
|
||||||
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
<a href="{{ url_for('website.user_following', username=user.username) }}"><strong>{{ user.following()|count }}</strong></a> following
|
<a href="{{ url_for('website.user_following', username=user.username) }}"><strong>{{ user.following()|count }}</strong></a> following
|
||||||
</p>
|
</p>
|
||||||
{% if user == current_user %}
|
{% if user == current_user %}
|
||||||
<p><a href="/edit_profile/">Edit profile</a></p>
|
<p><a href="/edit_profile/">{{ inline_svg('edit', 18) }} Edit profile</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<a href="/create/">Create a message</a>
|
<a href="/create/">Create a message</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul class="timeline">
|
||||||
{% for message in message_list %}
|
{% for message in message_list %}
|
||||||
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
15
app/utils.py
|
|
@ -5,7 +5,7 @@ A list of utilities used across modules.
|
||||||
import datetime, re, base64, hashlib, string, sys, json
|
import datetime, re, base64, hashlib, string, sys, json
|
||||||
from .models import User, Message, Notification, MSGPRV_PUBLIC, MSGPRV_UNLISTED, \
|
from .models import User, Message, Notification, MSGPRV_PUBLIC, MSGPRV_UNLISTED, \
|
||||||
MSGPRV_FRIENDS, MSGPRV_ONLYME
|
MSGPRV_FRIENDS, MSGPRV_ONLYME
|
||||||
from flask import abort, render_template, request, session
|
from flask import Markup, abort, render_template, request, session
|
||||||
|
|
||||||
_forbidden_extensions = 'com net org txt'.split()
|
_forbidden_extensions = 'com net org txt'.split()
|
||||||
_username_characters = frozenset(string.ascii_letters + string.digits + '_')
|
_username_characters = frozenset(string.ascii_letters + string.digits + '_')
|
||||||
|
|
@ -82,7 +82,7 @@ class Visibility(object):
|
||||||
|
|
||||||
def get_locations():
|
def get_locations():
|
||||||
data = {}
|
data = {}
|
||||||
with open('locations.txt') as f:
|
with open('locations.txt', encoding='utf-8') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
if line.startswith('#'):
|
if line.startswith('#'):
|
||||||
|
|
@ -215,3 +215,14 @@ def create_mentions(cur_user, text, privacy):
|
||||||
push_notification('mention', mention_user, user=user.id)
|
push_notification('mention', mention_user, user=user.id)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# New in 0.9
|
||||||
|
def inline_svg(name, width=None):
|
||||||
|
try:
|
||||||
|
with open('icons/' + name + '-24px.svg') as f:
|
||||||
|
data = f.read()
|
||||||
|
if isinstance(width, int):
|
||||||
|
data = re.sub(r'( (?:height|width)=")\d+(")', lambda x:x.group(1) + str(width) + x.group(2), data)
|
||||||
|
return Markup(data)
|
||||||
|
except OSError:
|
||||||
|
return ''
|
||||||
|
|
|
||||||
1
icons/edit-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
1
icons/exit_to_app-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 302 B |
1
icons/explore-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
1
icons/notifications-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 261 B |
1
icons/person-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||||
|
After Width: | Height: | Size: 247 B |
1
icons/person_add-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 279 B |
1
icons/shuffle-24px.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z"/></svg>
|
||||||
|
After Width: | Height: | Size: 311 B |