Open jace opened 3 years ago
Possible resolutions:
Proposal.is_page
. All renders for submission vs page must filter on is_page
. Pages are editable by all editors~Proposal.is_page = False
)~Should pages allow comments or not? Open question. (Decision: NO)
There are three distinct expectations here:
A post can be represented as a workflow state: Draft, Submission, Post, etc. These new states replace the existing workflow states (like Confirmed) that are better represented by labels. A submission can be converted into a post and vice versa.
A page can also be such a workflow state, or it can be an independent flag. TBD.
Updates have visibility state options of public and restricted (participants only). This flag also needs to be available on Session and Proposal.
However, on Proposal it still has the problem of deciding who gets access to set this flag: editors or proposers.
"Post" in the sense of a blog post is the same thing as an Update. We could add comments to updates instead of stretching the definition of a Proposal.
Similarly, Page should possibly be a separate data type because it's functionality diverges from Proposal. Pages have URL slugs and arguably should also allow Mustache templating so they can communicate the state of a project (number of submissions, etc).
End of comment history for the original issue ticket. We've established Page as a distinct content type.
Possible Page models with revisioning:
Page:
id
, primary_keyproject_id
, Project this page is increated_at
) and incoming foreign keys (can't ignore this!)PageRevision:
page_id
, revision_id
), compound primary key with foreign key to Pageuser_id
, User who created this revisionname
, URL stubtitle
, Page titlebody
, Contentrevises_id
, Link to previous revision that this is based off (compound fkey using page_id
, revises_id
)is_published: bool
, indicates the visible pageis_head: bool
, indicates latest revision (~there could be multiple heads~ multi-head rejected, see below)is_deleted: bool
, Indicates a deleted page or revision(project_id, name) where is_published=True
is_published
and is_deleted
cannot be True at the same time~ (rejected, see below on deleting draft head)To retrieve a page given a URL, query PageRevision.join(Page)
on (Page.project=project, PageRevision.name=url-stub)
, order by (is_published=True
before is_published=False
, then is_head=True
before is_head=False
) and get first()
If the retrieved item is None
, 404. This may be rendered as a prompt to editors to make a page.
If the retrieved item has is_deleted == True
, render 410 Gone
If the retrieved item has is_published == True
, we're all set. Render it.
If the retrieved item has is_published == False
, is_head == True
, this page was never published. Decide to render based on app logic: are drafts visible to (a) editors-only, (b) participants, (c) all?
If the retrieved item has is_published == False
, is_head == False
, we have a rename on our hands. The page_id
is relevant now. Repeat the query on PageRevision with (project=project, page_id=retrieved.page_id)
, same ordering, and re-run these steps.
This spec is missing a clean way to edit, because there are two separate concerns here:
If it's okay to have multiple revision heads, then they also need to be labeled. Options:
(pageid, user_id, where is_head=True)
. However, this complicates review and publishing, so REJECT.is_head
a string field, with a unique constraint on (pageid, is_head)
. However, this field proxies for a changelog, making the constraint untenable for UX. There is also no facility here for a merge revision that combines heads, and we don't want additional complications, so REJECT.is_published
. This is reasonable under current expectations, so ACCEPT.Editing across devices will require a three-way merge. This has to be implemented in a PageDraft model on the client side:
page_id
, revision_id
-> PageRevision.(page_id, revision_id)
, the revision being targetedreference_title
, reference_body
, a copy of the revision's title and bodyreference_timestamp
, for the revision's contentedit_title
, edit_body
, the widgets in the formWhen the client is ready to autosave, it sends the edited content along with the reference timestamp.
This draft model cannot be implemented server-side because it cannot be bound to an edit form. The user may abandon the edit or may open a second tab, causing a connection to the server that is indistinguishable from the first. It may be possible to achieve it by (a) inserting a random draft identifier into the form and (b) removing the draft if unused in a timeout period (say 1 day, to account for the client's intermittent connectivity).
Update: this scheme is has a name and a proper spec: differential synchronization. What we call "reference" here is called "shadow" there, and a proper implementation needs a shadow in both the client and the server.
Additional constraints:
is_published=True
or is_deleted=True
or is_head=False
. Only the head can be edited, and only when it's not published.Page
model when a revision is published or deleted.Revising the Page models for a single-head scenario:
Page:
id
, primary_keyproject_id
, Project this page is inpublished_revision_id
, fkey to PageRevisionhead_revision_id
, fkey to PageRevisionis_deleted
: boolid
because of PageRevision's structurePageRevision:
page_id
, page_revision_id
): fkey and compound pkeyname
, title
, body
: Page contentrevises_id
: compound fkey to selfIn this model, a URL is still accessed by looking up via PageRevision, but the access logic is simpler. However, we have previously had trouble with bi-directional fkeys in Organization <-> Team, back when there were mandatory Owners and Members teams. They make SQL backup/restore hard. If this is an okay risk, this approach may have better performance.
More considerations:
Don't bother with editable drafts. Keep the simple edit form (open, edit, save), and simply take a snapshot of the previous content for version history.
Reversing this, let there be a permanent editable draft, and take snapshots to represent published revisions. Default view will render the latest published revision, falling back to the draft.
Support for "edit requests". For eg, if we apply this revision control tech to Proposal, we have a product requirement where an editor corrects a submission and asks the proposer to accept or decline the edit. This will require per-user multi-head revisions.
Project page names should be based on the stable URL generator described in https://github.com/hasgeek/coaster/issues/301. This is required for wiki links.
Projects need a new model named Page. This will be the third content type in a project after Proposal (third party submission) and Update (editor's blog), and addresses the gaps of the existing two. Characteristics:
Nice to have (check-off when these features are implemented in later PRs):
[[Page name]]
syntax. There will be an implementation detail to convert from relative URLs to absolute URLs based on where the page content appearsProposal.description
field should be migrated into an index Page, thereby acquiring all these nice featuresOriginal text of this issue ticket:
Proposals were renamed to Submissions in December 2020, to switch to a more generic term. However, this is not generic enough for projects with editor-only content. They need:
While there has been a long-pending desire for wiki-like pages, that remains on the wishlist and is not being implemented here. Wiki-style
[[page title]]
links remain unrealized.The following comments are responding to this original text and not the newer Page spec.