From 8f973ff9c9d9e420e084741bcbbd32072b51ecaf Mon Sep 17 00:00:00 2001 From: Ben Rog-Wilhelm Date: Sun, 15 May 2022 14:20:53 -0500 Subject: [PATCH 1/5] Add open-in-tab support for userpages. Originally written by @faul-sname --- files/templates/comments.html | 2 +- files/templates/submission.html | 2 +- files/templates/submission_listing.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/templates/comments.html b/files/templates/comments.html index c73ecfc1e..23d71d028 100644 --- a/files/templates/comments.html +++ b/files/templates/comments.html @@ -200,7 +200,7 @@ {% if not c.author %} {{c.print()}} {% endif %} - {{c.author_name}} + {{c.author_name}} {% if c.author.customtitle %}  {{c.author.customtitle | safe}}{% endif %} {% endif %} diff --git a/files/templates/submission.html b/files/templates/submission.html index dda7fc20e..e413063d3 100644 --- a/files/templates/submission.html +++ b/files/templates/submission.html @@ -467,7 +467,7 @@ {% if p.author.verified %} {% endif %} - {{p.author_name}}{% if p.author.customtitle %}  {{p.author.customtitle | safe}}{% endif %} + {{p.author_name}}{% if p.author.customtitle %}  {{p.author.customtitle | safe}}{% endif %} {% endif %}  {{p.age_string}} ({% if p.is_image %}image post{% elif p.is_video %}video post{% elif p.domain %}{{p.domain}}{% else %}text post{% endif %}) diff --git a/files/templates/submission_listing.html b/files/templates/submission_listing.html index 59771b0b2..c319ec1fc 100644 --- a/files/templates/submission_listing.html +++ b/files/templates/submission_listing.html @@ -181,7 +181,7 @@ {% if p.author.verified %} {% endif %} - {{p.author_name}}{% if p.author.customtitle %}  {{p.author.customtitle | safe}}{% endif %} + {{p.author_name}}{% if p.author.customtitle %}  {{p.author.customtitle | safe}}{% endif %} {% endif %}  {{p.age_string}}   From 222c2d9df0a3aa06fee010b51c4f02dc338bd526 Mon Sep 17 00:00:00 2001 From: Ben Rog-Wilhelm Date: Sun, 15 May 2022 07:40:20 -0500 Subject: [PATCH 2/5] Edit a bunch of error messages. --- files/templates/errors/401.html | 2 +- files/templates/errors/404.html | 2 +- files/templates/errors/405.html | 2 +- files/templates/errors/413.html | 4 ++-- files/templates/errors/429.html | 2 +- files/templates/errors/500.html | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/templates/errors/401.html b/files/templates/errors/401.html index b235847ab..9bcc0c333 100644 --- a/files/templates/errors/401.html +++ b/files/templates/errors/401.html @@ -15,7 +15,7 @@

 
 			

401 Not Authorized

-

What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.

+

You need an account for this. Please make one!

Create an account
Or sign in
diff --git a/files/templates/errors/404.html b/files/templates/errors/404.html index a5b7ce074..1ed61e422 100644 --- a/files/templates/errors/404.html +++ b/files/templates/errors/404.html @@ -13,7 +13,7 @@ :#marseyconfused

 		

404 Page Not Found

-

Someone typed something wrong and it was probably you, please do better.

+

That page doesn't exist. If you got here from a link on the website, please report this issue. Thanks!

Go to frontpage
diff --git a/files/templates/errors/405.html b/files/templates/errors/405.html index 33215384a..69a2a4161 100644 --- a/files/templates/errors/405.html +++ b/files/templates/errors/405.html @@ -13,7 +13,7 @@ :#marseyretard:

 		

405 Method Not Allowed

-

idk how anyone gets this error but if you see this, remember to follow @carpathianflorist
the original error text here talked about internet gremlins and wtf

+

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!

Go to frontpage
diff --git a/files/templates/errors/413.html b/files/templates/errors/413.html index 59b043714..73e3e15b4 100644 --- a/files/templates/errors/413.html +++ b/files/templates/errors/413.html @@ -1,7 +1,7 @@ {% extends "default.html" %} {% block title %} -Max file size is 8 MB (16 MB for paypigs) +Max file size is 8 MB {% endblock %} {% block pagetype %}error-413{% endblock %} @@ -12,7 +12,7 @@
:#marseyretard:

-		

Max file size is 8 MB (16 MB for paypigs)

+

Max file size is 8 MB.

Go to frontpage
diff --git a/files/templates/errors/429.html b/files/templates/errors/429.html index 0860ae1f2..48307eee2 100644 --- a/files/templates/errors/429.html +++ b/files/templates/errors/429.html @@ -13,7 +13,7 @@ :#marseyrentfree:

 		

429 Too Many Requests

-

go spam somewhere else nerd

+

Are you hammering the site? Stop that, yo.

diff --git a/files/templates/errors/500.html b/files/templates/errors/500.html index de4ffcc26..85ff7fdd0 100644 --- a/files/templates/errors/500.html +++ b/files/templates/errors/500.html @@ -13,7 +13,7 @@ :#marseydead:

 			

500 Internal Server Error

-

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 but not other pages, 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

+

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

Go to the frontpage
From e590068786630ded3c9014b7d557d3f490bf82bc Mon Sep 17 00:00:00 2001 From: Ben Rog-Wilhelm Date: Sun, 15 May 2022 07:47:08 -0500 Subject: [PATCH 3/5] Add workflow badge --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 349cf8bff..af19ea879 100644 --- a/readme.md +++ b/readme.md @@ -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) From 957edab4dca7873ae9ef80bafcf27de1f5c5694f Mon Sep 17 00:00:00 2001 From: Ben Rog-Wilhelm Date: Sun, 15 May 2022 11:55:42 -0500 Subject: [PATCH 4/5] Update rule text for new site. --- files/templates/rules.html | 57 ++++++++------------------- files/templates/sidebar_TheMotte.html | 1 - 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/files/templates/rules.html b/files/templates/rules.html index 1031f08ce..b72d45dc6 100644 --- a/files/templates/rules.html +++ b/files/templates/rules.html @@ -78,7 +78,7 @@

The Rules

- Here's a list of subreddit rules. Each of them includes an explanation of why it's important.

+ Here's a list of community rules. Each of them includes an explanation of why it's important.

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 @@ -87,7 +87,7 @@ sheer statistical chance, you will find yourself banned in the process.

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.

Our goal is to @@ -99,9 +99,9 @@

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, - 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 - 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.

Be Kind

@@ -144,9 +144,7 @@ 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!

- - More information here - . + More information here.

Be charitable.

@@ -164,8 +162,7 @@

Content

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, - including most popular subreddits.

+ without producing value for the community. You can see this behavior on most high-traffic discussion forums.

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. @@ -249,13 +246,11 @@

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 - of this sub. If you are going to link to another platform or subreddit 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 Reddit must be to the non-participation np.reddit.com domain as vote manipulation and brigading are against - the site-wide terms of service.

+ 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.

- Finally, in the interest of the health of this community, we ask that you do not post links from this subreddit - to other high-participation platforms or ping Reddit users who do not already participate here. Both are invitations + Finally, in the interest of the health of this community, we ask that you do not post links to this community + 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 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. @@ -267,7 +262,7 @@

Engagement

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 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.

@@ -304,7 +299,7 @@

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 - 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 may be grounds for your comment to be removed and for the ban to be increased.

@@ -314,31 +309,13 @@ process and we do sometimes overturn bans.

-

Do not weaponize the block feature.

-

- 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.

- - 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.

- - 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. -

-

Don't attempt to build consensus or enforce ideological conformity.

"As everyone knows . . ."

"I'm sure you all agree that . . ."

- 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 someone doesn't know it yet. Humans are bad at 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. @@ -346,7 +323,7 @@

Write like everyone is reading and you want them to be included in the discussion.

- 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 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. @@ -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 broad as possible, then complain that we're not enforcing it properly.

- 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.

There are people who think that every rule should be absolutely objective, to the point where our job could be @@ -416,11 +393,11 @@

Moderation is very much driven by user sentiment. Feel free to report comments or message the mods with your thoughts.

- 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.

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 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 diff --git a/files/templates/sidebar_TheMotte.html b/files/templates/sidebar_TheMotte.html index eb1d39456..3f051dc98 100644 --- a/files/templates/sidebar_TheMotte.html +++ b/files/templates/sidebar_TheMotte.html @@ -77,7 +77,6 @@

  • When disagreeing with someone, state your objections explicitly.
  • Proactively provide evidence in proportion to how partisan and inflammatory your claim might be.
  • Accept temporary bans as a time-out, and don't attempt to rejoin the conversation until it's lifted.
  • -
  • Do not weaponize the block feature.
  • Don't attempt to build consensus or enforce ideological conformity.
  • Write like everyone is reading and you want them to be included in the discussion.
  • From 19903cccb541c0e18184989a62cab8e60df98cf6 Mon Sep 17 00:00:00 2001 From: Michael House Date: Mon, 16 May 2022 11:53:24 -0500 Subject: [PATCH 5/5] Adding usernotes. --- files/assets/css/TheMotte.css | 154 ++++++++++++++++++ .../assets/js/comments+submission_listing.js | 100 ++++++++++++ files/assets/js/micromodal.js | 1 + files/classes/__init__.py | 1 + files/classes/comment.py | 1 + files/classes/submission.py | 1 + files/classes/user.py | 2 + files/classes/usernotes.py | 66 ++++++++ files/routes/admin.py | 50 ++++++ files/routes/users.py | 10 -- files/templates/comments.html | 8 + files/templates/default.html | 5 + files/templates/submission.html | 6 + files/templates/submission_listing.html | 9 + files/templates/usernote.html | 41 +++++ schema.sql | 39 +++++ 16 files changed, 484 insertions(+), 10 deletions(-) create mode 100644 files/assets/js/micromodal.js create mode 100644 files/classes/usernotes.py create mode 100644 files/templates/usernote.html diff --git a/files/assets/css/TheMotte.css b/files/assets/css/TheMotte.css index 8e2000a55..a24bcf8c2 100644 --- a/files/assets/css/TheMotte.css +++ b/files/assets/css/TheMotte.css @@ -83,3 +83,157 @@ blockquote { #frontpage .post-title a:visited, .visited { 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; +} diff --git a/files/assets/js/comments+submission_listing.js b/files/assets/js/comments+submission_listing.js index 2a2a6e5c1..19da955a7 100644 --- a/files/assets/js/comments+submission_listing.js +++ b/files/assets/js/comments+submission_listing.js @@ -31,6 +31,106 @@ function popclick(author) { ; }, 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 += "" + + "
    " + + "
    " + + note.author_name + "
    " + + "" + date_str + ", " + time_str + "" + + "
    " + + "
    " + note.note + "
    " + + "
    " + tag + "
    " + + "
    Delete
    " + + "
    \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(){ active = document.activeElement.getAttributeNode("class"); if (active && active.nodeValue == "user-name text-decoration-none"){ diff --git a/files/assets/js/micromodal.js b/files/assets/js/micromodal.js new file mode 100644 index 000000000..09167d26a --- /dev/null +++ b/files/assets/js/micromodal.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).MicroModal=t()}(this,(function(){"use strict";function e(e,t){for(var o=0;oe.length)&&(t=e.length);for(var o=0,n=new Array(t);o0&&this.registerTriggers.apply(this,t(a)),this.onClick=this.onClick.bind(this),this.onKeydown=this.onKeydown.bind(this)}var i,a,r;return i=o,(a=[{key:"registerTriggers",value:function(){for(var e=this,t=arguments.length,o=new Array(t),n=0;n0&&void 0!==arguments[0]?arguments[0]:null;if(this.activeElement=document.activeElement,this.modal.setAttribute("aria-hidden","false"),this.modal.classList.add(this.config.openClass),this.scrollBehaviour("disable"),this.addEventListeners(),this.config.awaitOpenAnimation){var o=function t(){e.modal.removeEventListener("animationend",t,!1),e.setFocusToFirstNode()};this.modal.addEventListener("animationend",o,!1)}else this.setFocusToFirstNode();this.config.onShow(this.modal,this.activeElement,t)}},{key:"closeModal",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=this.modal;if(this.modal.setAttribute("aria-hidden","true"),this.removeEventListeners(),this.scrollBehaviour("enable"),this.activeElement&&this.activeElement.focus&&this.activeElement.focus(),this.config.onClose(this.modal,this.activeElement,e),this.config.awaitCloseAnimation){var o=this.config.openClass;this.modal.addEventListener("animationend",(function e(){t.classList.remove(o),t.removeEventListener("animationend",e,!1)}),!1)}else t.classList.remove(this.config.openClass)}},{key:"closeModalById",value:function(e){this.modal=document.getElementById(e),this.modal&&this.closeModal()}},{key:"scrollBehaviour",value:function(e){if(this.config.disableScroll){var t=document.querySelector("body");switch(e){case"enable":Object.assign(t.style,{overflow:""});break;case"disable":Object.assign(t.style,{overflow:"hidden"})}}}},{key:"addEventListeners",value:function(){this.modal.addEventListener("touchstart",this.onClick),this.modal.addEventListener("click",this.onClick),document.addEventListener("keydown",this.onKeydown)}},{key:"removeEventListeners",value:function(){this.modal.removeEventListener("touchstart",this.onClick),this.modal.removeEventListener("click",this.onClick),document.removeEventListener("keydown",this.onKeydown)}},{key:"onClick",value:function(e){(e.target.hasAttribute(this.config.closeTrigger)||e.target.parentNode.hasAttribute(this.config.closeTrigger))&&(e.preventDefault(),e.stopPropagation(),this.closeModal(e))}},{key:"onKeydown",value:function(e){27===e.keyCode&&this.closeModal(e),9===e.keyCode&&this.retainFocus(e)}},{key:"getFocusableNodes",value:function(){var e=this.modal.querySelectorAll(n);return Array.apply(void 0,t(e))}},{key:"setFocusToFirstNode",value:function(){var e=this;if(!this.config.disableFocus){var t=this.getFocusableNodes();if(0!==t.length){var o=t.filter((function(t){return!t.hasAttribute(e.config.closeTrigger)}));o.length>0&&o[0].focus(),0===o.length&&t[0].focus()}}}},{key:"retainFocus",value:function(e){var t=this.getFocusableNodes();if(0!==t.length)if(t=t.filter((function(e){return null!==e.offsetParent})),this.modal.contains(document.activeElement)){var o=t.indexOf(document.activeElement);e.shiftKey&&0===o&&(t[t.length-1].focus(),e.preventDefault()),!e.shiftKey&&t.length>0&&o===t.length-1&&(t[0].focus(),e.preventDefault())}else t[0].focus()}}])&&e(i.prototype,a),r&&e(i,r),o}(),a=null,r=function(e){if(!document.getElementById(e))return console.warn("MicroModal: ❗Seems like you have missed %c'".concat(e,"'"),"background-color: #f8f9fa;color: #50596c;font-weight: bold;","ID somewhere in your code. Refer example below to resolve it."),console.warn("%cExample:","background-color: #f8f9fa;color: #50596c;font-weight: bold;",'')),!1},s=function(e,t){if(function(e){e.length<=0&&(console.warn("MicroModal: ❗Please specify at least one %c'micromodal-trigger'","background-color: #f8f9fa;color: #50596c;font-weight: bold;","data attribute."),console.warn("%cExample:","background-color: #f8f9fa;color: #50596c;font-weight: bold;",''))}(e),!t)return!0;for(var o in t)r(o);return!0},{init:function(e){var o=Object.assign({},{openTrigger:"data-micromodal-trigger"},e),n=t(document.querySelectorAll("[".concat(o.openTrigger,"]"))),r=function(e,t){var o=[];return e.forEach((function(e){var n=e.attributes[t].value;void 0===o[n]&&(o[n]=[]),o[n].push(e)})),o}(n,o.openTrigger);if(!0!==o.debugMode||!1!==s(n,r))for(var l in r){var c=r[l];o.targetModal=l,o.triggers=t(c),a=new i(o)}},show:function(e,t){var o=t||{};o.targetModal=e,!0===o.debugMode&&!1===r(e)||(a&&a.removeEventListeners(),(a=new i(o)).showModal())},close:function(e){e?a.closeModalById(e):a.closeModal()}});return"undefined"!=typeof window&&(window.MicroModal=l),l})); diff --git a/files/classes/__init__.py b/files/classes/__init__.py index 8ce4237ba..37ac578fd 100644 --- a/files/classes/__init__.py +++ b/files/classes/__init__.py @@ -6,6 +6,7 @@ from .domains import * from .flags import * from .user import * from .userblock import * +from .usernotes import * from .submission import * from .votes import * from .domains import * diff --git a/files/classes/comment.py b/files/classes/comment.py index 900c02032..b1f7f93b0 100644 --- a/files/classes/comment.py +++ b/files/classes/comment.py @@ -57,6 +57,7 @@ class Comment(Base): child_comments = relationship("Comment", lazy="dynamic", remote_side=[parent_comment_id], viewonly=True) awards = relationship("AwardRelationship", viewonly=True) reports = relationship("CommentFlag", viewonly=True) + notes = relationship("UserNote", back_populates="comment") def __init__(self, *args, **kwargs): if "created_utc" not in kwargs: diff --git a/files/classes/submission.py b/files/classes/submission.py index adcae93e6..c46aa6683 100644 --- a/files/classes/submission.py +++ b/files/classes/submission.py @@ -59,6 +59,7 @@ class Submission(Base): reports = relationship("Flag", viewonly=True) comments = relationship("Comment", primaryjoin="Comment.parent_submission==Submission.id") 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())) diff --git a/files/classes/user.py b/files/classes/user.py index d177cbbd8..607f37693 100644 --- a/files/classes/user.py +++ b/files/classes/user.py @@ -130,6 +130,7 @@ class User(Base): authorizations = relationship("ClientAuth", viewonly=True) awards = relationship("AwardRelationship", primaryjoin="User.id==AwardRelationship.user_id", viewonly=True) referrals = relationship("User", viewonly=True) + notes = relationship("UserNote", foreign_keys='UserNote.reference_user', back_populates="user") 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, '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], + 'notes': [x.json() for x in self.notes] } return data diff --git a/files/classes/usernotes.py b/files/classes/usernotes.py new file mode 100644 index 000000000..f66bfe65a --- /dev/null +++ b/files/classes/usernotes.py @@ -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"" + + 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 \ No newline at end of file diff --git a/files/routes/admin.py b/files/routes/admin.py index e8adb197a..f510b1ec2 100644 --- a/files/routes/admin.py +++ b/files/routes/admin.py @@ -217,6 +217,56 @@ def distribute(v, comment): g.db.commit() return {"message": f"Each winner has received {coinsperperson} coins!"} +@app.post("/@/delete_note/") +@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("/@/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("/@/revert_actions") @limiter.limit("1/second;30/minute;200/hour;1000/day") @admin_level_required(3) diff --git a/files/routes/users.py b/files/routes/users.py index 1c65e728e..2bd42a3f1 100644 --- a/files/routes/users.py +++ b/files/routes/users.py @@ -71,16 +71,6 @@ def leaderboard_thread(): gevent.spawn(leaderboard_thread()) - - - - - - - - - - @app.get("/@/upvoters//posts") @auth_required def upvoters_posts(v, username, uid): diff --git a/files/templates/comments.html b/files/templates/comments.html index 23d71d028..6c1cc36a1 100644 --- a/files/templates/comments.html +++ b/files/templates/comments.html @@ -44,6 +44,8 @@ {% endif %} +{% include 'usernote.html' %} + {% macro single_comment(c, level=1) %} {% set ups=c.upvotes %} @@ -201,6 +203,12 @@ {{c.print()}} {% endif %} {{c.author_name}} + {% if v and v.admin_level > 2 %} + _U_ + {% endif %} {% if c.author.customtitle %}  {{c.author.customtitle | safe}}{% endif %} {% endif %} diff --git a/files/templates/default.html b/files/templates/default.html index 2306f63d0..19f5476cf 100644 --- a/files/templates/default.html +++ b/files/templates/default.html @@ -5,6 +5,7 @@ + {% if v %} @@ -327,6 +328,10 @@ + + diff --git a/files/templates/submission.html b/files/templates/submission.html index e413063d3..3fd0c75c2 100644 --- a/files/templates/submission.html +++ b/files/templates/submission.html @@ -468,6 +468,12 @@ {% if p.author.verified %} {% endif %} {{p.author_name}}{% if p.author.customtitle %}  {{p.author.customtitle | safe}}{% endif %} + {% if v and v.admin_level > 2 %} + _U_ + {% endif %} {% endif %}  {{p.age_string}} ({% if p.is_image %}image post{% elif p.is_video %}video post{% elif p.domain %}{{p.domain}}{% else %}text post{% endif %}) diff --git a/files/templates/submission_listing.html b/files/templates/submission_listing.html index c319ec1fc..13c4af51c 100644 --- a/files/templates/submission_listing.html +++ b/files/templates/submission_listing.html @@ -4,6 +4,7 @@ {% endif %} + +{% include 'usernote.html' %} + {% for p in listing %} {% set ups=p.upvotes %} @@ -182,6 +185,12 @@ {% if p.author.verified %} {% endif %} {{p.author_name}}{% if p.author.customtitle %}  {{p.author.customtitle | safe}}{% endif %} + {% if v and v.admin_level > 2 %} + _U_ + {% endif %} {% endif %}  {{p.age_string}}   diff --git a/files/templates/usernote.html b/files/templates/usernote.html new file mode 100644 index 000000000..7d24cf433 --- /dev/null +++ b/files/templates/usernote.html @@ -0,0 +1,41 @@ + \ No newline at end of file diff --git a/schema.sql b/schema.sql index 7de470f52..d0b800b2b 100644 --- a/schema.sql +++ b/schema.sql @@ -248,6 +248,39 @@ CREATE TABLE public.commentflags ( 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: - @@ -691,6 +724,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); +-- +-- 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: - --