diff --git a/r2/r2/config/routing.py b/r2/r2/config/routing.py index 8f0f0956b..c908fb314 100644 --- a/r2/r2/config/routing.py +++ b/r2/r2/config/routing.py @@ -283,6 +283,7 @@ def make_map(): mc('/api/wiki/edit', controller='wikiapi', action='wiki_edit') mc('/api/wiki/hide', controller='wikiapi', action='wiki_revision_hide') + mc('/api/wiki/delete', controller='wikiapi', action='wiki_revision_delete') mc('/api/wiki/revert', controller='wikiapi', action='wiki_revision_revert') mc('/api/wiki/alloweditor/:act', controller='wikiapi', requirements=dict(act="del|add"), action='wiki_allow_editor') diff --git a/r2/r2/controllers/wiki.py b/r2/r2/controllers/wiki.py index 422d2a352..9a483d922 100644 --- a/r2/r2/controllers/wiki.py +++ b/r2/r2/controllers/wiki.py @@ -38,6 +38,7 @@ from r2.lib.template_helpers import join_urls from r2.lib.validator import ( nop, validate, + VAdmin, VBoolean, VExistingUname, VInt, @@ -418,6 +419,22 @@ class WikiApiController(WikiController): self.handle_error(400, 'INVALID_ACTION') return json.dumps({}) + @validate( + VModhash(), + VAdmin(), + pv=VWikiPageAndVersion(('page', 'revision')), + deleted=VBoolean('deleted'), + ) + def POST_wiki_revision_delete(self, pv, deleted): + page, revision = pv + if not revision: + self.handle_error(400, 'INVALID_REVISION') + if deleted and page.revision == str(revision._id): + self.handle_error(400, 'REVISION_IS_CURRENT') + revision.admin_deleted = deleted + revision._commit() + return json.dumps({'status': revision.admin_deleted}) + @require_oauth2_scope("modwiki") @validate(VModhash(), VWikiModerator(), diff --git a/r2/r2/lib/validator/wiki.py b/r2/r2/lib/validator/wiki.py index 6e5a038ed..e078661ba 100644 --- a/r2/r2/lib/validator/wiki.py +++ b/r2/r2/lib/validator/wiki.py @@ -264,6 +264,9 @@ class VWikiPage(VWikiPageName): return try: r = WikiRevision.get(version, pageid) + if r.admin_deleted and not c.user_is_admin: + self.set_error('INVALID_REVISION', code=404) + raise AbortWikiError if not self.allow_hidden_revision and (r.is_hidden and not c.is_wiki_mod): self.set_error('HIDDEN_REVISION', code=403) raise AbortWikiError diff --git a/r2/r2/models/builder.py b/r2/r2/models/builder.py index 52e719da6..66279dee4 100755 --- a/r2/r2/models/builder.py +++ b/r2/r2/models/builder.py @@ -639,7 +639,10 @@ class WikiRevisionBuilder(QueryBuilder): cls.add_props(user, types[cls]) return wrapped - + + def must_skip(self, item): + return item.admin_deleted and not c.user_is_admin + def keep_item(self, item): from r2.lib.validator.wiki import may_view return ((not item.is_hidden) and @@ -649,6 +652,8 @@ class WikiRecentRevisionBuilder(WikiRevisionBuilder): show_extended = False def must_skip(self, item): + if WikiRevisionBuilder.must_skip(self, item): + return True item_age = datetime.datetime.now(g.tz) - item.date return item_age.days >= wiki.WIKI_RECENT_DAYS diff --git a/r2/r2/models/wiki.py b/r2/r2/models/wiki.py index 2290b0b7d..ea3061d08 100644 --- a/r2/r2/models/wiki.py +++ b/r2/r2/models/wiki.py @@ -90,7 +90,8 @@ class WikiRevision(tdb_cassandra.UuidThing, Printable): _connection_pool = 'main' _str_props = ('pageid', 'content', 'author', 'reason') - _bool_props = ('hidden') + _bool_props = ('hidden', 'admin_deleted') + _defaults = {'admin_deleted': False} cache_ignore = set(list(_str_props)).union(Printable.cache_ignore).union(['wikipage']) diff --git a/r2/r2/public/static/css/wiki.less b/r2/r2/public/static/css/wiki.less index 5cb460d10..581ba27b5 100644 --- a/r2/r2/public/static/css/wiki.less +++ b/r2/r2/public/static/css/wiki.less @@ -44,6 +44,10 @@ .revision { .transition(opacity, 500ms); } + .revision.deleted { + opacity: .5; + text-decoration: line-through; + } .revision.hidden { opacity: .5; } diff --git a/r2/r2/public/static/js/wiki.js b/r2/r2/public/static/js/wiki.js index 56ebd8ce9..9e6e4740a 100644 --- a/r2/r2/public/static/js/wiki.js +++ b/r2/r2/public/static/js/wiki.js @@ -25,6 +25,7 @@ r.wiki = { init: function() { $('body').delegate('.wiki-page .revision_hide', 'click', this.toggleHide) + $('body').delegate('.wiki-page .revision_delete', 'click', this.toggleDelete) $('body').delegate('.wiki-page .toggle-source', 'click', this.toggleSource) }, @@ -33,6 +34,34 @@ r.wiki = { $('.wiki-page .source').toggle('slow') }, + toggleDelete: function(event) { + event.preventDefault() + var $this = $(this), + url = r.wiki.baseApiUrl() + '/delete', + $this_parent = $this.parents('.revision'), + deleted = $this_parent.hasClass('deleted') + $this_parent.toggleClass('deleted') + r.wiki.request({ + url: url, + type: 'POST', + dataType: 'json', + data: { + revision: $this.data('revision'), + deleted: !deleted + }, + error: function() { + $this_parent.toggleClass('deleted') + }, + success: function(data) { + if (!data.status) { + $this_parent.removeClass('deleted') + } else { + $this_parent.addClass('deleted') + } + } + }) + }, + toggleHide: function(event) { event.preventDefault() var $this = $(this), diff --git a/r2/r2/templates/wikirevision.html b/r2/r2/templates/wikirevision.html index e30da6dbe..7d0e61086 100644 --- a/r2/r2/templates/wikirevision.html +++ b/r2/r2/templates/wikirevision.html @@ -31,6 +31,9 @@ %if thing._hidden: hidden %endif + %if thing.admin_deleted: + deleted + %endif "> %if thing.show_extended: @@ -76,4 +79,10 @@ } %endif + + %if c.user_is_admin: + + delete + + %endif