]> git.ipfire.org Git - ipfire.org.git/blobdiff - src/web/wiki.py
wiki: Add preview for editing pages
[ipfire.org.git] / src / web / wiki.py
index 4f92be24f46ceda84d6d72e0427f3c2e6f5baf51..b4c01e39851ad6b84ea690933bb56c5dc6c29ff1 100644 (file)
@@ -1,11 +1,64 @@
 #!/usr/bin/python3
 
+import difflib
 import tornado.web
 
 from . import auth
 from . import base
 from . import ui_modules
 
+class ActionEditHandler(auth.CacheMixin, base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self, path):
+               # Check permissions
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+
+               # Fetch the wiki page
+               page = self.backend.wiki.get_page(path)
+
+               # Empty page if it was deleted
+               if page and page.was_deleted():
+                       page = None
+
+               # Render page
+               self.render("wiki/edit.html", page=page)
+
+       @tornado.web.authenticated
+       def post(self, path):
+               # Check permissions
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+
+               content = self.get_argument("content", None)
+               changes = self.get_argument("changes")
+
+               # Create a new page in the database
+               with self.db.transaction():
+                       page = self.backend.wiki.create_page(path,
+                               self.current_user, content, changes=changes, address=self.get_remote_ip())
+
+                       # Add user as a watcher if wanted
+                       watch = self.get_argument("watch", False)
+                       if watch:
+                               page.add_watcher(self.current_user)
+
+               # Redirect back
+               if page.was_deleted():
+                       self.redirect("/")
+               else:
+                       self.redirect(page.url)
+
+       def on_finish(self):
+               """
+                       Updates the search index after the page has been edited
+               """
+               # This is being executed in the background and after
+               # the response has been set to the client
+               with self.db.transaction():
+                       self.backend.wiki.refresh()
+
+
 class ActionUploadHandler(auth.CacheMixin, base.BaseHandler):
        @tornado.web.authenticated
        def post(self):
@@ -28,7 +81,42 @@ class ActionUploadHandler(auth.CacheMixin, base.BaseHandler):
                except TypeError as e:
                        raise e
 
-               self.redirect("%s/files" % path)
+               self.redirect("%s/_files" % path)
+
+
+class ActionWatchHandler(auth.CacheMixin, base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self, path, action):
+               page = self.backend.wiki.get_page(path)
+               if not page:
+                       raise tornado.web.HTTPError(404, "Page does not exist: %s" % path)
+
+               # Check permissions
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+
+               with self.db.transaction():
+                       if action == "watch":
+                               page.add_watcher(self.current_user)
+                       elif action == "unwatch":
+                               page.remove_watcher(self.current_user)
+
+               # Redirect back to page
+               self.redirect(page.url)
+
+
+class ActionRenderHandler(auth.CacheMixin, base.BaseHandler):
+       def check_xsrf_cookie(self):
+               pass # disabled
+
+       @tornado.web.authenticated
+       def post(self, path):
+               content = self.get_argument("content")
+
+               # Render the content
+               html = self.backend.wiki.render(path, content)
+
+               self.finish(html)
 
 
 class FilesHandler(auth.CacheMixin, base.BaseHandler):
@@ -60,7 +148,14 @@ class FileHandler(base.BaseHandler):
 
                # Render detail page
                if self.action == "detail":
-                       self.render("wiki/files/detail.html", file=file)
+                       page = None
+
+                       for breadcrumb, title in self.backend.wiki.make_breadcrumbs(path):
+                               page = self.backend.wiki.get_page(breadcrumb)
+                               if page:
+                                       break
+
+                       self.render("wiki/files/detail.html", page=page, file=file)
                        return
 
                size = self.get_argument_int("s", None)
@@ -97,28 +192,38 @@ class PageHandler(auth.CacheMixin, base.BaseHandler):
                super().write_error(status_code, **kwargs)
 
        @tornado.web.removeslash
-       def get(self, page):
+       def get(self, path):
                # Check permissions
-               if not self.backend.wiki.check_acl(page, self.current_user):
-                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (page, self.current_user))
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
 
                # Check if we are asked to render a certain revision
                revision = self.get_argument("revision", None)
 
                # Fetch the wiki page
-               page = self.backend.wiki.get_page(page, revision=revision)
+               page = self.backend.wiki.get_page(path, revision=revision)
+
+               # Diff
+               if self.action == "diff":
+                       # Get both revisions
+                       a = self.get_argument("a")
+                       b = self.get_argument("b")
 
-               # Edit
-               if self.action == "edit":
-                       if not self.current_user:
-                               raise tornado.web.HTTPError(401)
+                       # Fetch both versions of the page
+                       a = self.backend.wiki.get_page(path, revision=a)
+                       b = self.backend.wiki.get_page(path, revision=b)
+                       if not a or not b:
+                               raise tornado.web.HTTPError(404)
 
-                       # Empty page if it was deleted
-                       if page and page.was_deleted():
-                               page = None
+                       # Cannot render a diff for the identical page
+                       if a == b:
+                               raise tornado.web.HTTPError(400)
 
-                       # Render page
-                       self.render("wiki/edit.html", page=page)
+                       # Make sure that b is newer than a
+                       if a > b:
+                               a, b = b, a
+
+                       self.render("wiki/diff.html", page=page, a=a, b=b)
                        return
 
                # Revisions
@@ -136,30 +241,6 @@ class PageHandler(auth.CacheMixin, base.BaseHandler):
                # Render page
                self.render("wiki/page.html", page=page, latest_revision=latest_revision)
 
-       @tornado.web.authenticated
-       def post(self, page):
-               # Check permissions
-               if not self.backend.wiki.check_acl(page, self.current_user):
-                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (page, self.current_user))
-
-               content = self.get_argument("content", None)
-               changes = self.get_argument("changes")
-
-               # Create a new page in the database
-               with self.db.transaction():
-                       page = self.backend.wiki.create_page(page,
-                               self.current_user, content, changes=changes, address=self.get_remote_ip())
-
-               # Redirect back
-               if page.was_deleted():
-                       self.redirect("/")
-               else:
-                       self.redirect(page.url)
-
-               # Update the search index
-               with self.db.transaction():
-                       self.backend.wiki.refresh()
-
 
 class SearchHandler(auth.CacheMixin, base.BaseHandler):
        @base.blacklisted
@@ -168,7 +249,7 @@ class SearchHandler(auth.CacheMixin, base.BaseHandler):
 
                pages = self.backend.wiki.search(q, account=self.current_user, limit=50)
 
-               self.render("wiki/search-results.html", q=q, pages=list(pages))
+               self.render("wiki/search-results.html", q=q, pages=pages)
 
 
 class RecentChangesHandler(auth.CacheMixin, base.BaseHandler):
@@ -178,23 +259,66 @@ class RecentChangesHandler(auth.CacheMixin, base.BaseHandler):
                self.render("wiki/recent-changes.html", recent_changes=recent_changes)
 
 
+class WatchlistHandler(auth.CacheMixin, base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self):
+               pages = self.backend.wiki.get_watchlist(self.current_user)
+
+               self.render("wiki/watchlist.html", pages=pages)
+
+
+class WikiDiffModule(ui_modules.UIModule):
+       differ = difflib.Differ()
+
+       def render(self, a, b):
+               diff = self.differ.compare(
+                       a.markdown.splitlines(),
+                       b.markdown.splitlines(),
+               )
+
+               return self.render_string("wiki/modules/diff.html", diff=diff)
+
+
 class WikiListModule(ui_modules.UIModule):
-       def render(self, pages, link_revision=False, show_breadcrumbs=True, show_changes=False):
+       def render(self, pages, link_revision=False, show_breadcrumbs=True,
+                       show_author=True, show_changes=False):
                return self.render_string("wiki/modules/list.html", link_revision=link_revision,
-                       pages=pages, show_breadcrumbs=show_breadcrumbs, show_changes=show_changes)
+                       pages=pages, show_breadcrumbs=show_breadcrumbs,
+                       show_author=show_author, show_changes=show_changes)
 
 
 class WikiNavbarModule(ui_modules.UIModule):
+       @property
+       def path(self):
+               """
+                       Returns the path of the page (without any actions)
+               """
+               path = self.request.path.split("/")
+
+               if path and path[-1].startswith("_"):
+                       path.pop()
+
+               return "/".join(path)
+
        def render(self, suffix=None):
                _ = self.locale.translate
 
-               breadcrumbs = self.backend.wiki.make_breadcrumbs(self.request.path)
+               # Make the path
+               page = self.request.path.split("/")
 
-               # Don't search for a title for the file manager
-               if self.request.path.endswith("/files"):
-                       title = _("Files")
-               else:
-                       title = self.backend.wiki.get_page_title(self.request.path)
+               # Drop the action bit
+               if page and page[-1].startswith("_"):
+                       page.pop()
+
+               page = "/".join(page)
+
+               breadcrumbs = self.backend.wiki.make_breadcrumbs(page)
+               title = self.backend.wiki.get_page_title(page)
+
+               if self.request.path.endswith("/_edit"):
+                       suffix = _("Edit")
+               elif self.request.path.endswith("/_files"):
+                       suffix = _("Files")
 
                return self.render_string("wiki/modules/navbar.html",
-                       breadcrumbs=breadcrumbs, page_title=title, suffix=suffix)
+                       breadcrumbs=breadcrumbs, page=page, page_title=title, suffix=suffix)