Browse Source

data: Added change history.

1.2 1.2.0
Gisle Aune 5 years ago
parent
commit
de3bae3353
  1. 1
      build.js
  2. 6
      marko/page/data/changes.marko
  3. 4
      marko/page/data/components/add-channel-modal/index.marko
  4. 115
      marko/page/data/components/change/component.js
  5. 15
      marko/page/data/components/change/index.marko
  6. 8
      marko/page/data/components/change/style.less
  7. 16
      marko/page/data/components/changes-page/component.js
  8. 12
      marko/page/data/components/changes-page/index.marko
  9. 36
      marko/page/data/components/changes-page/style.less
  10. 1
      marko/page/data/components/data-menu/index.marko
  11. 2
      marko/page/data/components/edit-channel-modal/index.marko
  12. 15
      routes/data/changes.js
  13. 81
      rpdata/api/Change.js
  14. 1
      server.js

1
build.js

@ -11,6 +11,7 @@ prebuild.run({
"./marko/page/logs/list.marko",
"./marko/page/data/channels.marko",
"./marko/page/data/characters.marko",
"./marko/page/data/changes.marko",
"./marko/page/story-content/view.marko",
"./marko/page/logs-content/view.marko",
]

6
marko/page/data/changes.marko

@ -0,0 +1,6 @@
<include("../layout", {title: "Data", site: "data"})>
<@body>
<background src="/assets/images/bg.png" opacity=0.25 />
<changes-page user=input.user changes=input.changes selected=input.selected />
</@body>
</include>

4
marko/page/data/components/add-channel-modal/index.marko

@ -17,8 +17,6 @@
onDesc="Log new posts in this channel."
offDesc="The logbot will not monitor this channel."
on-change("change", "logged") />
<p class="color-danger" if(state.values.logged)>The logbot is not yet available, so this does nothing right now.</p>
<button disabled=state.loading on-click("save")>Save</button>
</modal>

115
marko/page/data/components/change/component.js

@ -0,0 +1,115 @@
const moment = require("moment")
module.exports = class {
onCreate() {
this.state = {
operation: "",
links: [],
parentLink: null,
}
}
onInput(input) {
if (input.data) {
this.refresh(input.data)
}
}
refresh(data) {
this.state.links = []
this.state.operation = opText[data.op]
this.state.parentLink = null
switch (data.model) {
case "Channel": {
const key = data.keys.find(k => k.id !== "*")
this.state.operation += " channel"
this.state.links = [{
text: key.id,
href: `/data/channels/`,
}]
break
}
case "Character": {
const character = data.objects.find(o => o.type === "Character")
this.state.operation += " character"
this.state.links = [{
text: `${character.name} (${character.id})`,
href: `/data/characters/`,
}]
break
}
case "Story": {
const key = data.keys.find(k => k.model === "Story" && k.id !== "*")
const story = data.objects.find(o => o.type === "Story")
this.state.operation += " story"
this.state.links = [{
text: story.name,
href: `/story/${key.id}`,
}]
break
}
case "Chapter": {
const storyKey = data.keys.find(k => k.model === "Story")
const chapterKey = data.keys.find(k => k.model === "Chapter")
const chapter = data.objects.find(o => o.type === "Chapter")
this.state.operation += " chapter"
this.state.links = [{
text: chapter.title,
href: `/story/${storyKey.id}#${chapterKey.id}`,
}]
break
}
case "Log": {
const key = data.keys.find(k => k.id !== "*")
this.state.operation += " log"
this.state.links = [{
text: key.id,
href: `/logs/${key.id}`,
}]
break
}
case "Post": {
const logKey = data.keys.find(k => k.model === "Log")
const postKeys = data.keys.filter(k => k.model === "Post")
const postObjs = data.objects.filter(o => o.type === "Post")
this.state.operation += (postKeys.length > 1) ? " posts" : " post"
this.state.links = postObjs.map(po => ({
text: `[${moment(po.time).format("HH:mm")}] ${po.nick}`,
href: `/logs/${logKey.id}#${po.id}`,
}))
this.state.parentLink = {
prefix: "in log",
text: `${logKey.id}`,
href: `/logs/${logKey.id}`,
}
break
}
}
}
}
const opText = {
add: "added",
remove: "removed",
move: "moved",
edit: "edited",
import: "imported",
}

15
marko/page/data/components/change/index.marko

@ -0,0 +1,15 @@
import moment from "moment";
<div class="change">
<span class="change-id color-menu">[${moment(input.data.date).format("MMM D HH:mm:SS")}] * </span>
<span class="author color-text">${input.data.author} </span>
<span class="text color-text">${state.operation} </span>
<span for(link in state.links | status-var=loop) key=link.href class="link">
<a class="color-primary" href=link.href>${link.text}</a>
<span if(loop.getIndex() < state.links.length - 1) class="comma">, </span>
</span>
<if (state.parentLink != null)>
<span class="text"> ${state.parentLink.prefix} </span>
<a class="color-primary" href=state.parentLink.href>${state.parentLink.text}</a>
</if>
</div>

8
marko/page/data/components/change/style.less

@ -0,0 +1,8 @@
div.change {
text-indent: -2ch;
margin-left: 2ch;
a:hover {
text-decoration: underline;
}
}

16
marko/page/data/components/changes-page/component.js

@ -0,0 +1,16 @@
module.exports = class {
onCreate(input) {
this.state = {
characters: input.characters,
modal: null,
}
}
open(modal) {
this.state.modal = modal
}
close() {
this.state.modal = null
}
}

12
marko/page/data/components/changes-page/index.marko

@ -0,0 +1,12 @@
<data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") />
<main>
<div class="changes-page">
<h1 class="color-primary">Changes</h1>
<p>All changes to <em>listed</em> resources is stored for 90 days for transparency reasons. The current state of an object after each change exists, but is only available through the GraphQL API at this time.</p>
<for (change in input.changes)>
<change data=change />
</for>
</div>
</main>
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-added("characterAdded") on-close("close") />
<add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-close("close") />

36
marko/page/data/components/changes-page/style.less

@ -0,0 +1,36 @@
.changes-page {
width: 90%;
max-width: 95ch;
margin: auto;
> p {
text-align: center;
}
h1 {
font-weight: 200;
text-align: center;
}
.header {
text-align: center;
vertical-align: middle;
margin-bottom: 1em;
h1 {
display: block;
margin-bottom: 0;
}
a {
vertical-align: middle;
display: inline-block;
padding: 0.5em 0.5ch 0.5em 0.5ch;
opacity: 0.5;
}
a:hover {
cursor: pointer;
opacity: 1;
}
}
}

1
marko/page/data/components/data-menu/index.marko

@ -2,6 +2,7 @@
<menu-header>List</menu-header>
<menu-link key="characters" selected=input.selected.characters icon="C" href="/data/characters/">Characters</menu-link>
<menu-link key="channels" selected=input.selected.channels icon="#" href="/data/channels/">Channels</menu-link>
<menu-link key="history" selected=input.selected.changes icon="H" href="/data/changes/">History</menu-link>
<if-permitted user=input.user permission=["member", "character.add", "channel.add"]>
<menu-gap />
<menu-header>Add</menu-header>

2
marko/page/data/components/edit-channel-modal/index.marko

@ -15,7 +15,5 @@
offDesc="The logbot will not monitor this channel."
on-change("change", "logged") />
<p class="color-danger" if(state.values.logged)>The logbot is not yet available, so this does nothing right now.</p>
<button disabled=state.loading on-click("save")>Save</button>
</modal>

15
routes/data/changes.js

@ -0,0 +1,15 @@
const express = require("express")
const router = express.Router()
const {changesApi} = require("../../rpdata/api/Change")
const changesTemplate = require("../../marko/page/data/changes.marko")
router.get("/", async(req, res) => {
res.markoAsync(changesTemplate, {
changes: changesApi.list(),
selected: {changes: true},
})
})
module.exports = router

81
rpdata/api/Change.js

@ -0,0 +1,81 @@
const {query} = require("../client")
class Change {
/**
* @param {string} id
* @param {string} model
* @param {string} op
* @param {string} author
* @param {Date | string | number} date
* @param {{model:string,id:string}[]} keys '
* @param {{type:string, [x:string]:any}[]} objects
*/
constructor(id, model, op, author, date, keys, objects) {
this.id = id
this.model = model
this.op = op
this.author = author
this.date = date
this.keys = keys
this.objects = objects
}
static fromData(data) {
return new Change(data.id, data.model, data.op, data.author, data.date, data.keys, data.objects)
}
}
class ChangesAPI {
/**
* Call `channels(filter)` query
*
* @param {{limit:int, keys: {model:string,id:string}[]}} filter
* @returns {Promise<Change[]>}
*/
list(filter = null) {
return query(`
query ListChanges($filter:ChangesFilter) {
changes(filter:$filter) {
id
model
op
author
date
keys {
model
id
}
objects {
type: __typename
...on Character {
id
name
}
...on Chapter {
id
title
}
...on Story {
id
name
}
...on Post {
id
time
nick
}
}
}
}
`, {filter}).then(({changes}) => {
return changes.map(d => Change.fromData(d))
})
}
}
module.exports = { Change, changesApi: new ChangesAPI }

1
server.js

@ -73,6 +73,7 @@ app.use("/logs/:id(L[0-9]{0,7})/", require("./routes/logs-content"))
app.use("/data/", require("./routes/data"))
app.use("/data/characters/", require("./routes/data/characters"))
app.use("/data/channels/", require("./routes/data/channels"))
app.use("/data/changes/", require("./routes/data/changes"))
// Entry point
app.get("/", function(req, res) {

Loading…
Cancel
Save