Merge branch 'frost' into new-user-filtering

This commit is contained in:
Julian Rota 2022-05-16 23:55:50 -04:00
commit 6d68993a8d
25 changed files with 514 additions and 62 deletions

View file

@ -83,3 +83,157 @@ blockquote {
#frontpage .post-title a:visited, .visited { #frontpage .post-title a:visited, .visited {
color: #7a7a7a !important; color: #7a7a7a !important;
} }
.usernote-link {
color: var(--primary);
text-decoration: none;
background-color: transparent;
cursor: pointer;
}
.usernote-link:hover {
text-decoration: underline;
}
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
display: flex;
justify-content: center;
align-items: center;
z-index:1033 !important;
}
.modal__container {
background-color: #fff;
padding: 30px;
max-width: 500px;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #00449e;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before { content: "\2715"; }
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0,0,0,.8);
}
.modal__content > .content__wrapper {
display: flex;
flex-direction: column;
min-width: 400px;
}
.modal__content > .content__wrapper > .table_content,
.modal__content > .content__wrapper > .table_content > .table_note,
.modal__content > .content__wrapper > .table_headers {
display: flex;
}
.modal__content > .content__wrapper > .table_content > .table_note {
margin-top: 10px;
}
.modal__content > .content__wrapper > .table_headers > *,
.modal__content > .content__wrapper > .table_content > .table_note > * {
flex-basis: 20%;
}
.modal__content > .content__wrapper > .table_headers > *:nth-child(2),
.modal__content > .content__wrapper > .table_content > .table_note > *:nth-child(2) {
flex-basis: 35%;
flex-grow: 1;
}
.modal__content > .content__wrapper > .table_content .table_note_message {
padding: 5px;
box-sizing: border-box;
}
.modal__content > .content__wrapper > .table_content > .table_note .table_note_date,
.modal__content > .content__wrapper > .table_content > .table_note > .table_note_delete > span {
color: var(--primary);
border: none !important;
text-decoration: none;
background-color: transparent;
cursor: pointer;
}
.modal__content > .content__wrapper > .table_content > .table_note .table_note_date:hover,
.modal__content > .content__wrapper > .table_content > .table_note > .table_note_delete > span:hover {
text-decoration: underline;
}
.modal__content > form {
display: flex;
flex-direction: column;
}
.modal__content > .content__wrapper > .table_content {
flex-direction: column;
}
.modal__content textarea {
resize: vertical !important;
min-height: 50px;
margin-top:20px;
}
.modal__content select {
display: table-cell;
padding: 5px;
box-sizing: border-box;
margin-top:5px;
vertical-align: middle;
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0,0,0,.8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}

View file

@ -31,6 +31,106 @@ function popclick(author) {
; }, 1); ; }, 1);
} }
function fillnote(user,post,comment) {
let dialog = document.getElementById("modal-1");
let table = "";
for(let i = 0; i < user.notes.length; ++i){
let note_id = "note_" + i;
let note = user.notes[i];
let date = new Date(parseInt(note.created) * 1000);
let date_str = date.toLocaleDateString();
let time_str = date.toLocaleTimeString();
let tag = "None";
switch(note.tag){
case 0: tag = "Quality"; break;
case 1: tag = "Good" ; break;
case 2: tag = "Comment"; break;
case 3: tag = "Warning"; break;
case 4: tag = "Tempban"; break;
case 5: tag = "Permban"; break;
case 6: tag = "Spam" ; break;
case 7: tag = "Bot" ; break;
}
table += "" +
"<div class=\"table_note\" id=\"" + note_id + "\" data-id=\"" + note.id + "\">" +
"<div class=\"table_note_author\">" +
note.author_name + "<br/>" +
"<a class=\"table_note_date\" href=\"" + note.reference + "\">" + date_str + ", " + time_str + "</a>" +
"</div>" +
"<div class=\"table_note_message\">" + note.note + "</div>" +
"<div class=\"table_note_tag\">" + tag + "</div>" +
"<div class=\"table_note_delete\"><span onclick=\"delete_note('" + note_id + "','" + user.url + "')\">Delete</span></div>" +
"</div>\n"
}
dialog.getElementsByClassName('notes_target')[0].innerText = user.username;
dialog.getElementsByClassName('table_content')[0].innerHTML = table;
dialog.dataset.context = JSON.stringify({
'url': user.url,
'post': post,
'comment': comment,
'user': user.id,
});
}
function delete_note(element,url) {
let note = document.getElementById(element);
let id = note.dataset.id;
const xhr = new XMLHttpRequest();
xhr.open("POST", url + "/delete_note/" + id);
xhr.setRequestHeader('xhr', 'xhr');
xhr.responseType = 'json';
xhr.onload = function() {
if(xhr.status === 200) {
console.log(xhr.response);
location.reload();
}
}
var form = new FormData()
form.append("formkey", formkey());
xhr.send(form);
}
function send_note() {
let dialog = document.getElementById("modal-1");
let context = JSON.parse(dialog.dataset.context);
let note = document.querySelector("#modal-1 textarea").value;
let tag = document.querySelector("#modal-1 #usernote_tag").value;
const xhr = new XMLHttpRequest();
xhr.open("POST", context.url + "/create_note");
xhr.setRequestHeader('xhr', 'xhr');
xhr.responseType = 'json';
var form = new FormData()
form.append("formkey", formkey());
form.append("data", JSON.stringify({
'note': note,
'post': context.post,
'comment': context.comment,
'user': context.user,
'tag': tag
}));
xhr.onload = function() {
if(xhr.status === 200) {
console.log(xhr.response);
location.reload();
}
}
xhr.send(form);
}
document.addEventListener("click", function(){ document.addEventListener("click", function(){
active = document.activeElement.getAttributeNode("class"); active = document.activeElement.getAttributeNode("class");
if (active && active.nodeValue == "user-name text-decoration-none"){ if (active && active.nodeValue == "user-name text-decoration-none"){

File diff suppressed because one or more lines are too long

View file

@ -6,6 +6,7 @@ from .domains import *
from .flags import * from .flags import *
from .user import * from .user import *
from .userblock import * from .userblock import *
from .usernotes import *
from .submission import * from .submission import *
from .votes import * from .votes import *
from .domains import * from .domains import *

View file

@ -57,6 +57,7 @@ class Comment(Base):
child_comments = relationship("Comment", lazy="dynamic", remote_side=[parent_comment_id], viewonly=True) child_comments = relationship("Comment", lazy="dynamic", remote_side=[parent_comment_id], viewonly=True)
awards = relationship("AwardRelationship", viewonly=True) awards = relationship("AwardRelationship", viewonly=True)
reports = relationship("CommentFlag", viewonly=True) reports = relationship("CommentFlag", viewonly=True)
notes = relationship("UserNote", back_populates="comment")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: if "created_utc" not in kwargs:

View file

@ -60,6 +60,7 @@ class Submission(Base):
reports = relationship("Flag", viewonly=True) reports = relationship("Flag", viewonly=True)
comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id") comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id")
subr = relationship("Sub", primaryjoin="foreign(Submission.sub)==remote(Sub.name)", viewonly=True) subr = relationship("Sub", primaryjoin="foreign(Submission.sub)==remote(Sub.name)", viewonly=True)
notes = relationship("UserNote", back_populates="post")
bump_utc = deferred(Column(Integer, server_default=FetchedValue())) bump_utc = deferred(Column(Integer, server_default=FetchedValue()))

View file

@ -130,6 +130,7 @@ class User(Base):
authorizations = relationship("ClientAuth", viewonly=True) authorizations = relationship("ClientAuth", viewonly=True)
awards = relationship("AwardRelationship", primaryjoin="User.id==AwardRelationship.user_id", viewonly=True) awards = relationship("AwardRelationship", primaryjoin="User.id==AwardRelationship.user_id", viewonly=True)
referrals = relationship("User", viewonly=True) referrals = relationship("User", viewonly=True)
notes = relationship("UserNote", foreign_keys='UserNote.reference_user', back_populates="user")
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -509,6 +510,7 @@ class User(Base):
'post_count': 0 if self.shadowbanned and not (v and (v.shadowbanned or v.admin_level > 2)) else self.post_count, 'post_count': 0 if self.shadowbanned and not (v and (v.shadowbanned or v.admin_level > 2)) else self.post_count,
'comment_count': 0 if self.shadowbanned and not (v and (v.shadowbanned or v.admin_level > 2)) else self.comment_count, 'comment_count': 0 if self.shadowbanned and not (v and (v.shadowbanned or v.admin_level > 2)) else self.comment_count,
'badges': [x.path for x in self.badges], 'badges': [x.path for x in self.badges],
'notes': [x.json() for x in self.notes]
} }
return data return data

View file

@ -0,0 +1,66 @@
import time
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.const import *
from enum import Enum
from sqlalchemy import Enum as EnumType
class UserTag(Enum):
Quality = 0
Good = 1
Comment = 2
Warning = 3
Tempban = 4
Permban = 5
Spam = 6
Bot = 7
class UserNote(Base):
__tablename__ = "usernotes"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
created_utc = Column(Integer, nullable=False)
reference_user = Column(Integer, ForeignKey("users.id", ondelete='CASCADE'), nullable=False)
reference_comment = Column(Integer, ForeignKey("comments.id", ondelete='SET NULL'))
reference_post = Column(Integer, ForeignKey("submissions.id", ondelete='SET NULL'))
note = Column(String, nullable=False)
tag = Column(EnumType(UserTag), nullable=False)
author = relationship("User", foreign_keys='UserNote.author_id')
user = relationship("User", foreign_keys='UserNote.reference_user', back_populates="notes")
comment = relationship("Comment", back_populates="notes")
post = relationship("Submission", back_populates="notes")
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs:
kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<UserNote(id={self.id})>"
def json(self):
reference = None
if self.comment:
reference = self.comment.permalink
elif self.post:
reference = self.post.permalink
data = {'id': self.id,
'author_name': self.author.username,
'author_id': self.author.id,
'user_name': self.user.username,
'user_id': self.user.id,
'created': self.created_utc,
'reference': reference,
'note': self.note,
'tag': self.tag.value
}
return data

View file

@ -217,6 +217,56 @@ def distribute(v, comment):
g.db.commit() g.db.commit()
return {"message": f"Each winner has received {coinsperperson} coins!"} return {"message": f"Each winner has received {coinsperperson} coins!"}
@app.post("/@<username>/delete_note/<id>")
@admin_level_required(3)
def delete_note(v,username,id):
g.db.query(UserNote).filter_by(id=id).delete()
g.db.commit()
return make_response(jsonify({
'success':True, 'message': 'Note deleted', 'note': id
}), 200)
@app.post("/@<username>/create_note")
@admin_level_required(3)
def create_note(v,username):
def result(msg,succ,note):
return make_response(jsonify({
'success':succ, 'message': msg, 'note': note
}), 200)
data = json.loads(request.values.get('data'))
user = g.db.query(User).filter_by(username=username).one_or_none()
if not user:
return result('User not found',False,None)
author_id = v.id
reference_user = user.id
reference_comment = data.get('comment',None)
reference_post = data.get('post',None)
note = data['note']
tag = UserTag(int(data['tag']))
if reference_comment:
reference_post = None
elif reference_post:
reference_comment = None
note = UserNote(
author_id=author_id,
reference_user=reference_user,
reference_comment=reference_comment,
reference_post=reference_post,
note=note,
tag=tag)
g.db.add(note)
g.db.commit()
return result('Note saved',True,note.json())
@app.post("/@<username>/revert_actions") @app.post("/@<username>/revert_actions")
@limiter.limit("1/second;30/minute;200/hour;1000/day") @limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(3) @admin_level_required(3)

View file

@ -71,16 +71,6 @@ def leaderboard_thread():
gevent.spawn(leaderboard_thread()) gevent.spawn(leaderboard_thread())
@app.get("/@<username>/upvoters/<uid>/posts") @app.get("/@<username>/upvoters/<uid>/posts")
@auth_required @auth_required
def upvoters_posts(v, username, uid): def upvoters_posts(v, username, uid):

View file

@ -44,6 +44,8 @@
</div> </div>
{% endif %} {% endif %}
{% include 'usernote.html' %}
{% macro single_comment(c, level=1) %} {% macro single_comment(c, level=1) %}
{% set ups=c.upvotes %} {% set ups=c.upvotes %}
@ -200,7 +202,13 @@
{% if not c.author %} {% if not c.author %}
{{c.print()}} {{c.print()}}
{% endif %} {% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{c.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color:#{{c.author.namecolor}}; font-size:12px; font-weight:bold;"><img loading="lazy" src="{{c.author.profile_url}}" class="profile-pic-25 mr-2"><span {% if c.author.patron and not c.distinguish_level %}class="patron" style="background-color:#{{c.author.namecolor}};"{% elif c.distinguish_level %}class="mod"{% endif %}>{{c.author_name}}</span></a> <a href="/@{{c.author_name}}" class="user-name text-decoration-none" onclick='popclick({{c.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color:#{{c.author.namecolor}}; font-size:12px; font-weight:bold;"><img loading="lazy" src="{{c.author.profile_url}}" class="profile-pic-25 mr-2"><span {% if c.author.patron and not c.distinguish_level %}class="patron" style="background-color:#{{c.author.namecolor}};"{% elif c.distinguish_level %}class="mod"{% endif %}>{{c.author_name}}</span></a>
{% if v and v.admin_level > 2 %}
<span
class="usernote-link"
data-micromodal-trigger="modal-1"
onclick='fillnote( {{c.author.json_popover(v) | tojson}}, null, {{c.id}} )'>_U_</span>
{% endif %}
{% if c.author.customtitle %}&nbsp;<bdi style="color: #{{c.author.titlecolor}}">&nbsp;{{c.author.customtitle | safe}}</bdi>{% endif %} {% if c.author.customtitle %}&nbsp;<bdi style="color: #{{c.author.titlecolor}}">&nbsp;{{c.author.customtitle | safe}}</bdi>{% endif %}
{% endif %} {% endif %}

View file

@ -5,6 +5,7 @@
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval' ajax.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io {% if PUSHER_ID != 'blahblahblah' %}{{PUSHER_ID}}.pushnotifications.pusher.com{% endif %}; object-src 'none';"> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval' ajax.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io {% if PUSHER_ID != 'blahblahblah' %}{{PUSHER_ID}}.pushnotifications.pusher.com{% endif %}; object-src 'none';">
<script src="/assets/js/bootstrap.js?v=245"></script> <script src="/assets/js/bootstrap.js?v=245"></script>
<script src="/assets/js/micromodal.js?v=245"></script>
{% if v %} {% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style> <style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=250"> <link rel="stylesheet" href="/assets/css/main.css?v=250">
@ -327,6 +328,10 @@
<script src="/assets/js/lite-youtube.js?v=240"></script> <script src="/assets/js/lite-youtube.js?v=240"></script>
<script>
MicroModal.init();
</script>
</body> </body>
</html> </html>

View file

@ -15,7 +15,7 @@
<pre></pre> <pre></pre>
<h1 class="h5">401 Not Authorized</h1> <h1 class="h5">401 Not Authorized</h1>
<p class="text-muted mb-5">What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.</p> <p class="text-muted mb-5">You need an account for this. Please make one!</p>
<div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div> <div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div>
<div><a href="/login" class="text-muted text-small">Or sign in</a></div> <div><a href="/login" class="text-muted text-small">Or sign in</a></div>
</div> </div>

View file

@ -13,7 +13,7 @@
<img alt=":#marseyconfused" loading="lazy" src="/e/marseyconfused.webp"> <img alt=":#marseyconfused" loading="lazy" src="/e/marseyconfused.webp">
<pre></pre> <pre></pre>
<h1 class="h5">404 Page Not Found</h1> <h1 class="h5">404 Page Not Found</h1>
<p class="text-muted mb-5">Someone typed something wrong and it was probably you, please do better.</p> <p class="text-muted mb-5">That page doesn't exist. If you got here from a link on the website, please report this issue. Thanks!</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div> <div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div> </div>
</div> </div>

View file

@ -13,7 +13,7 @@
<img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp"> <img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp">
<pre></pre> <pre></pre>
<h1 class="h5">405 Method Not Allowed</h1> <h1 class="h5">405 Method Not Allowed</h1>
<p class="text-muted mb-5">idk how anyone gets this error but if you see this, remember to follow @carpathianflorist<BR>the original error text here talked about internet gremlins and wtf</p> <p class="text-muted mb-5">Something went wrong and it's probably my fault. If you can do it reliably, or it's causing problems for you, please report it!</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div> <div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
{% extends "default.html" %} {% extends "default.html" %}
{% block title %} {% block title %}
<title>Max file size is 8 MB (16 MB for paypigs)</title> <title>Max file size is 8 MB</title>
{% endblock %} {% endblock %}
{% block pagetype %}error-413{% endblock %} {% block pagetype %}error-413{% endblock %}
@ -12,7 +12,7 @@
<div class="text-center px-3 my-8"> <div class="text-center px-3 my-8">
<img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp"> <img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp">
<pre></pre> <pre></pre>
<h1 class="h5">Max file size is 8 MB (16 MB for paypigs)</h1> <h1 class="h5">Max file size is 8 MB.</h1>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div> <div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div> </div>
</div> </div>

View file

@ -13,7 +13,7 @@
<img alt=":#marseyrentfree:" loading="lazy" src="/e/marseyrentfree.webp"> <img alt=":#marseyrentfree:" loading="lazy" src="/e/marseyrentfree.webp">
<pre></pre> <pre></pre>
<h1 class="h5">429 Too Many Requests</h1> <h1 class="h5">429 Too Many Requests</h1>
<p class="text-muted mb-5">go spam somewhere else nerd</p> <p class="text-muted mb-5">Are you hammering the site? Stop that, yo.</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -13,7 +13,7 @@
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp"> <img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre> <pre></pre>
<h1 class="h5">500 Internal Server Error</h1> <h1 class="h5">500 Internal Server Error</h1>
<p class="text-muted mb-5">Hiiiii it's carp! I think this error means that there's a timeout error. And I think that means something took too long to load so it decided not to work at all. If you keep seeing this on the same page <I>but not other pages</I>, then something is probably wrong with that specific function. It may not be called a function, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3</p> <p class="text-muted mb-5">Something went wrong and it's probably my fault. If you can do it reliably, or it's causing problems for you, please report it!<3</p>
<div><a href="/" class="btn btn-primary">Go to the frontpage</a></div> <div><a href="/" class="btn btn-primary">Go to the frontpage</a></div>
</div> </div>

View file

@ -78,7 +78,7 @@
</pre> </pre>
<h2 id="The_Rules"><a href="The_Rules">The Rules</a></h2> <h2 id="The_Rules"><a href="The_Rules">The Rules</a></h2>
<p> <p>
Here's a list of subreddit rules. Each of them includes an explanation of why it's important.<br/><br/> Here's a list of community rules. Each of them includes an explanation of why it's important.<br/><br/>
Be aware that you are expected to follow all the rules, not just some of the rules. At the same time, these Be aware that you are expected to follow all the rules, not just some of the rules. At the same time, these
rules are very subjective. We often give people some flex, especially if they have a history of making good rules are very subjective. We often give people some flex, especially if they have a history of making good
@ -87,7 +87,7 @@
sheer statistical chance, you will find yourself banned in the process.<br/><br/> sheer statistical chance, you will find yourself banned in the process.<br/><br/>
Finally, you don't get a pass to break the rules if the person you're responding to broke the rules first. Report Finally, you don't get a pass to break the rules if the person you're responding to broke the rules first. Report
their comment, then either set an example by responding with something that fits the desired subreddit their comment, then either set an example by responding with something that fits the desired community
behavior, or don't respond.<br/><br/> behavior, or don't respond.<br/><br/>
Our goal is to <a href="https://slatestarcodex.com/2014/12/03/framing-for-light-instead-of-heat/"> Our goal is to <a href="https://slatestarcodex.com/2014/12/03/framing-for-light-instead-of-heat/">
@ -99,9 +99,9 @@
<p> <p>
One of the most difficult parts about communities is that it is very easy for them to turn into a pit of One of the most difficult parts about communities is that it is very easy for them to turn into a pit of
toxicity. People who see toxic behavior in a community will follow that cue with their own toxic behavior, toxicity. People who see toxic behavior in a community will follow that cue with their own toxic behavior,
and this can quickly spiral out of control. This is bad for most subreddits, but would be an absolute and this can quickly spiral out of control. This is bad for most communities, but would be an absolute
death sentence for ours - it's impossible to discuss sensitive matters in an environment full of flaming death sentence for ours - it's impossible to discuss sensitive matters in an environment full of flaming
and personal attacks. Therefore, this set of subreddit rules are intended to address this preemptively. and personal attacks. Therefore, this set of community rules are intended to address this preemptively.
</p> </p>
<h4 id="Kindness"><a href="#Kindness">Be Kind</a></h4> <h4 id="Kindness"><a href="#Kindness">Be Kind</a></h4>
@ -144,9 +144,7 @@
Remember, the goal is for people with differing opinions to discuss things; if padding a statement with words Remember, the goal is for people with differing opinions to discuss things; if padding a statement with words
helps someone not take it personally, then that's what you should do!<br/><br/> helps someone not take it personally, then that's what you should do!<br/><br/>
<a href="https://www.reddit.com/r/TheMotte/comments/b6s8up/meta_i_am_on_this_council/ejn2ba3/?context=3"> <a href="https://www.reddit.com/r/TheMotte/comments/b6s8up/meta_i_am_on_this_council/ejn2ba3/?context=3">More information here</a>.
More information here
</a>.
</p> </p>
<h4 id="Charity"><a href="#Charity">Be charitable.</a></h4> <h4 id="Charity"><a href="#Charity">Be charitable.</a></h4>
@ -164,8 +162,7 @@
<h3 id="Content"><a href="#Content">Content</a></h3> <h3 id="Content"><a href="#Content">Content</a></h3>
<p> <p>
There's a lot of common commenting practice that makes it easy for people to cause friction and inflammation There's a lot of common commenting practice that makes it easy for people to cause friction and inflammation
without producing value for the community. You can see this behavior on most high-traffic discussion forums, without producing value for the community. You can see this behavior on most high-traffic discussion forums.<br/><br/>
including most popular subreddits.<br/><br/>
This is not intended to suppress anything that people might want to post, but it is intended to force people to This is not intended to suppress anything that people might want to post, but it is intended to force people to
invest effort if they want to post things that have traditionally been pain points. invest effort if they want to post things that have traditionally been pain points.
@ -249,13 +246,11 @@
<p> <p>
In keeping with the rules above regarding "low-effort" and "weak-man" comments, and our goal to produce more In keeping with the rules above regarding "low-effort" and "weak-man" comments, and our goal to produce more
light than heat, we ask that you refrain from posting bare links to culture-war-related discussions held outside light than heat, we ask that you refrain from posting bare links to culture-war-related discussions held outside
of this sub. If you are going to link to another platform or subreddit we ask that you please put in the work of this sub. If you are going to link to another platform we ask that you please put in the work
to contextualize the post and explain why it is relevant to readers of this community. Furthermore, any links to contextualize the post and explain why it is relevant to readers of this community.<br/><br/>
to Reddit must be to the non-participation np.reddit.com domain as vote manipulation and brigading are against
the site-wide terms of service.<br/><br/>
Finally, in the interest of the health of this community, we ask that you do not post links from this subreddit Finally, in the interest of the health of this community, we ask that you do not post links to this community
to other high-participation platforms or ping Reddit users who do not already participate here. Both are invitations on other high-participation platforms. They are invitations
for users unfamiliar with our norms to come here and (often angrily) make posts that break our rules. Exceptions for users unfamiliar with our norms to come here and (often angrily) make posts that break our rules. Exceptions
may be made for communities specifically designed for compatible content, but these will be examined by the may be made for communities specifically designed for compatible content, but these will be examined by the
moderators on a case by case basis. If in doubt, please ask first by messaging the moderators. moderators on a case by case basis. If in doubt, please ask first by messaging the moderators.
@ -267,7 +262,7 @@
<h3 id="Engagement"><a href="#Engagement">Engagement</a></h3> <h3 id="Engagement"><a href="#Engagement">Engagement</a></h3>
<p> <p>
Online discussion is hard to do properly. A lot of tonal information is lost through text, and in an asynchronous Online discussion is hard to do properly. A lot of tonal information is lost through text, and in an asynchronous
forum like Reddit, simply asking someone "what do you mean?" can take hours. In addition, because Reddit is a threaded forum like this one, simply asking someone "what do you mean?" can take hours. In addition, because The Motte is a threaded
medium, responding to multiple people asking the same question requires that you either copy-paste your answer, rewrite medium, responding to multiple people asking the same question requires that you either copy-paste your answer, rewrite
your answer, make a bunch of posts that simply link to your original answer, or ignore some of the replies; all of your answer, make a bunch of posts that simply link to your original answer, or ignore some of the replies; all of
these solutions suck, for various reasons.<br/><br/> these solutions suck, for various reasons.<br/><br/>
@ -304,7 +299,7 @@
<p> <p>
In part, our temporary bans are intended for people to cool down, think about how they've been approaching In part, our temporary bans are intended for people to cool down, think about how they've been approaching
discussion, and come back when they've mentally reset. Ban evasion is treated rather strictly, and the definition discussion, and come back when they've mentally reset. Ban evasion is treated rather strictly, and the definition
of "ban evasion" is broad - in general, it includes attempts to post things in the subreddit even when the ban is of "ban evasion" is broad - in general, it includes attempts to post things to the community even when the ban is
not yet lifted. Specifically, this includes editing your comment in an attempt to continue the discussion, which not yet lifted. Specifically, this includes editing your comment in an attempt to continue the discussion, which
may be grounds for your comment to be removed and for the ban to be increased.<br/><br/> may be grounds for your comment to be removed and for the ban to be increased.<br/><br/>
@ -314,31 +309,13 @@
process and we do sometimes overturn bans. process and we do sometimes overturn bans.
</p> </p>
<h4 id="Blocking"><a href="#Blocking">Do not weaponize the block feature.</a></h4>
<p>
As a community, we strongly discourage blocking. It goes against the ethos of this sub, which is to engage with
all perspectives, even ones you find disagreeable. That said, if you really, really can't stand to see someone's
posts, block them if you absolutely have to. Quietly. Do not announce it, brag about it, or use it as a "parting shot."
If you block someone, you may not reply to them first.<br/><br/>
If you are blocking a lot of people, to the point that threads are being disrupted by multiple people unable to
reply, we will consider that an abuse of the block feature. You can't stand That One Guy? Fine. You're blocking
everyone who argues with you? That's disruptive.<br/><br/>
We cannot "prove" who is blocking, who blocked first, or whether it was justified. We will use our best judgment
and the preponderance of the evidence and it's possible we'll make mistakes, but consider this a very strong
reason not to use the feature. If you didn't block anyone and we think you did and ban you for it, we can probably
be convinced we made a mistake. If you frequently block people but try to convince us that you didn't do anything
wrong, it's going to be much harder to convince us we made a mistake.
</p>
<h4 id="Consensus"><a href="#Consensus">Don't attempt to build consensus or enforce ideological conformity.</a></h4> <h4 id="Consensus"><a href="#Consensus">Don't attempt to build consensus or enforce ideological conformity.</a></h4>
<p> <p>
"As everyone knows . . ."<br/><br/> "As everyone knows . . ."<br/><br/>
"I'm sure you all agree that . . ."<br/><br/> "I'm sure you all agree that . . ."<br/><br/>
We visit this subreddit specifically because we don't all agree, and regardless of how universal you believe We visit this site specifically because we don't all agree, and regardless of how universal you believe
knowledge is, I guarantee <a href="https://xkcd.com/1053/">someone doesn't know it yet</a>. Humans are bad at knowledge is, I guarantee <a href="https://xkcd.com/1053/">someone doesn't know it yet</a>. Humans are bad at
disagreeing with each other, and starting out from an assumption of agreement is a great way to quash disagreement. disagreeing with each other, and starting out from an assumption of agreement is a great way to quash disagreement.
It's a nice rhetorical trick in some situations, but it's against what we're trying to accomplish here. It's a nice rhetorical trick in some situations, but it's against what we're trying to accomplish here.
@ -346,7 +323,7 @@
<h4 id="Inclusion"><a href="#Inclusion">Write like everyone is reading and you want them to be included in the discussion.</a></h4> <h4 id="Inclusion"><a href="#Inclusion">Write like everyone is reading and you want them to be included in the discussion.</a></h4>
<p> <p>
If the goal of the subreddit is to promote discussion, then we ask that people keep this in mind when posting. If the goal of the community is to promote discussion, then we ask that people keep this in mind when posting.
Avoid being dismissive of your political opponents, relying too much on injokes at someone else's expense, or Avoid being dismissive of your political opponents, relying too much on injokes at someone else's expense, or
anything that discourages people from participating in the discussion. This is one of the vaguest rules and one anything that discourages people from participating in the discussion. This is one of the vaguest rules and one
of the rules least likely to be enforced, since any real violation is likely to fall under another category. of the rules least likely to be enforced, since any real violation is likely to fall under another category.
@ -399,7 +376,7 @@
follows the rules. If we were to write a rule saying "don't do this thing", they would bend the rule to be as follows the rules. If we were to write a rule saying "don't do this thing", they would bend the rule to be as
broad as possible, then complain that we're not enforcing it properly.<br/><br/> broad as possible, then complain that we're not enforcing it properly.<br/><br/>
The goal of this subreddit is not, however, slavish adherence to rules. It's discussion. And if this means The goal of this community is not, however, slavish adherence to rules. It's discussion. And if this means
we need to use our human judgement to make calls, then that's exactly what we will do.<br/><br/> we need to use our human judgement to make calls, then that's exactly what we will do.<br/><br/>
There are people who think that every rule should be absolutely objective, to the point where our job could be There are people who think that every rule should be absolutely objective, to the point where our job could be
@ -416,11 +393,11 @@
<h4 id="Sentiment"><a href="#Sentiment">Moderation is very much driven by user sentiment. Feel free to report comments or message the mods with your thoughts.</a></h4> <h4 id="Sentiment"><a href="#Sentiment">Moderation is very much driven by user sentiment. Feel free to report comments or message the mods with your thoughts.</a></h4>
<p> <p>
In the end, subreddits exist for people. They don't necessarily exist for all people, but without people, In the end, communities exist for people. They don't necessarily exist for all people, but without people,
they die.<br/><br/> they die.<br/><br/>
You are encouraged to make suggestions and ask questions. You are also encouraged to report comments that you You are encouraged to make suggestions and ask questions. You are also encouraged to report comments that you
think violate the above rules; there's a lot of comments on this subreddit and we don't necessarily see them all, think violate the above rules; there's a lot of comments on this site and we don't necessarily see them all,
so if you think a comment definitely breaks the rules, and we haven't said anything about it, we may just not so if you think a comment definitely breaks the rules, and we haven't said anything about it, we may just not
have seen it. If you're reporting for something that falls under the Wildcard, please explain why you think it have seen it. If you're reporting for something that falls under the Wildcard, please explain why you think it
should be removed. It is not against the rules to disagree with you; please don't report comments simply for making should be removed. It is not against the rules to disagree with you; please don't report comments simply for making

View file

@ -77,7 +77,6 @@
<li><a href="/rules#Disagreement">When disagreeing with someone, state your objections explicitly.</a></li> <li><a href="/rules#Disagreement">When disagreeing with someone, state your objections explicitly.</a></li>
<li><a href="/rules#Evidence">Proactively provide evidence in proportion to how partisan and inflammatory your claim might be.</a></li> <li><a href="/rules#Evidence">Proactively provide evidence in proportion to how partisan and inflammatory your claim might be.</a></li>
<li><a href="/rules#Timeouts">Accept temporary bans as a time-out, and don't attempt to rejoin the conversation until it's lifted.</a></li> <li><a href="/rules#Timeouts">Accept temporary bans as a time-out, and don't attempt to rejoin the conversation until it's lifted.</a></li>
<li><a href="/rules#Blocking">Do not weaponize the block feature.</a></li>
<li><a href="/rules#Consensus">Don't attempt to build consensus or enforce ideological conformity.</a></li> <li><a href="/rules#Consensus">Don't attempt to build consensus or enforce ideological conformity.</a></li>
<li><a href="/rules#Inclusion">Write like everyone is reading and you want them to be included in the discussion.</a></li> <li><a href="/rules#Inclusion">Write like everyone is reading and you want them to be included in the discussion.</a></li>
</ul> </ul>

View file

@ -467,7 +467,13 @@
{% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1 {% if p.author.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if p.author.verifiedcolor %}#{{p.author.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{p.author.verified}}"></i> {% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1 {% if p.author.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if p.author.verifiedcolor %}#{{p.author.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{p.author.verified}}"></i>
{% endif %} {% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold"class="user-name"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2"><span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}&nbsp;<bdi style="color: #{{p.author.titlecolor}}">&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %} <a href="/@{{p.author_name}}" class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold"class="user-name"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2"><span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}&nbsp;<bdi style="color: #{{p.author.titlecolor}}">&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %}
{% if v and v.admin_level > 2 %}
<span
class="usernote-link"
data-micromodal-trigger="modal-1"
onclick='fillnote( {{p.author.json_popover(v) | tojson}}, {{p.id}}, null )'>_U_</span>
{% endif %}
{% endif %} {% endif %}
<span data-bs-toggle="tooltip" data-bs-placement="bottom" id="timestamp" onmouseover="timestamp('timestamp','{{p.created_utc}}')">&nbsp;{{p.age_string}}</span> <span data-bs-toggle="tooltip" data-bs-placement="bottom" id="timestamp" onmouseover="timestamp('timestamp','{{p.created_utc}}')">&nbsp;{{p.age_string}}</span>
({% if p.is_image %}image post{% elif p.is_video %}video post{% elif p.domain %}<a href="/search/posts/?q=domain%3A{{p.domain}}&sort=new&t=all" {% if not v or v.newtabexternal %}target="_blank"{% endif %}>{{p.domain}}</a>{% else %}text post{% endif %}) ({% if p.is_image %}image post{% elif p.is_video %}video post{% elif p.domain %}<a href="/search/posts/?q=domain%3A{{p.domain}}&sort=new&t=all" {% if not v or v.newtabexternal %}target="_blank"{% endif %}>{{p.domain}}</a>{% else %}text post{% endif %})

View file

@ -46,6 +46,8 @@
</div> </div>
</div> </div>
{% include 'usernote.html' %}
{% for p in listing %} {% for p in listing %}
{% set ups=p.upvotes %} {% set ups=p.upvotes %}
@ -185,7 +187,13 @@
{% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1 {% if p.author.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if p.author.verifiedcolor %}#{{p.author.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{p.author.verified}}"></i> {% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1 {% if p.author.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if p.author.verifiedcolor %}#{{p.author.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{p.author.verified}}"></i>
{% endif %} {% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold;"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2"><span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}<bdi style="color: #{{p.author.titlecolor}}">&nbsp;&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %} <a href="/@{{p.author_name}}" class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold;"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2"><span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}<bdi style="color: #{{p.author.titlecolor}}">&nbsp;&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %}
{% if v and v.admin_level > 2 %}
<span
class="usernote-link"
data-micromodal-trigger="modal-1"
onclick='fillnote( {{p.author.json_popover(v) | tojson}}, {{p.id}}, null )'>_U_</span>
{% endif %}
{% endif %} {% endif %}
<span data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('timestamp-{{p.id}}','{{p.created_utc}}')" id="timestamp-{{p.id}}">&nbsp;{{p.age_string}}</span> <span data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('timestamp-{{p.id}}','{{p.created_utc}}')" id="timestamp-{{p.id}}">&nbsp;{{p.age_string}}</span>
&nbsp; &nbsp;

View file

@ -0,0 +1,41 @@
<div class="modal micromodal-slide" id="modal-1" aria-hidden="true">
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
<header class="modal__header">
<h2 class="modal__title" id="modal-1-title">
Notes - <span class="notes_target"></span>
</h2>
<button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
</header>
<main class="modal__content" id="modal-1-content">
<div class="content__wrapper">
<div class="table_headers">
<strong>Author</strong>
<strong>Note</strong>
<strong>Tag</strong>
<strong>Actions</strong>
</div>
<div class="table_content">
</div>
</div>
<form>
<textarea></textarea>
<select id="usernote_tag">
<option class="quality" value="0"> Quality </option>
<option class="good" value="1"> Good </option>
<option class="comment" value="2"> Comment </option>
<option class="warning" value="3"> Warning </option>
<option class="tempban" value="4"> Tempban </option>
<option class="permban" value="5"> Permban </option>
<option class="spam" value="6"> Spam </option>
<option class="bot" value="7"> Bot </option>
</select>
</form>
</main>
<footer class="modal__footer">
<button class="modal__btn modal__btn-primary" onclick="send_note()">Submit</button>
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
</footer>
</div>
</div>
</div>

View file

@ -1,4 +1,7 @@
This code runs https://www.themotte.org
[![Build status](https://img.shields.io/github/workflow/status/TheMotte/rDrama/run_tests.py/frost)](https://github.com/TheMotte/rDrama/actions?query=workflow%3Arun_tests.py+branch%3Afrost)
This code runs https://www.themotte.org .
# Installation (Windows/Linux/MacOS) # Installation (Windows/Linux/MacOS)

View file

@ -249,6 +249,39 @@ CREATE TABLE public.commentflags (
created_utc integer NOT NULL created_utc integer NOT NULL
); );
--
-- Name: usernotes; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.usernotes (
id integer NOT NULL,
author_id integer NOT NULL,
created_utc integer NOT NULL,
reference_user integer NOT NULL,
reference_comment integer,
reference_post integer,
note character varying(10000),
tag character varying(10)
);
--
-- Name: usernotes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.usernotes_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: usernotes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.usernotes_id_seq OWNED BY public.usernotes.id;
-- --
-- Name: comments; Type: TABLE; Schema: public; Owner: - -- Name: comments; Type: TABLE; Schema: public; Owner: -
@ -692,6 +725,12 @@ ALTER TABLE ONLY public.award_relationships ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY public.badge_defs ALTER COLUMN id SET DEFAULT nextval('public.badge_defs_id_seq'::regclass); ALTER TABLE ONLY public.badge_defs ALTER COLUMN id SET DEFAULT nextval('public.badge_defs_id_seq'::regclass);
--
-- Name: usernotes id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.usernotes ALTER COLUMN id SET DEFAULT nextval('public.usernotes_id_seq'::regclass);
-- --
-- Name: comments id; Type: DEFAULT; Schema: public; Owner: - -- Name: comments id; Type: DEFAULT; Schema: public; Owner: -
-- --