Gisle Aune
6 years ago
14 changed files with 308 additions and 5 deletions
-
1build.js
-
6marko/page/data/changes.marko
-
4marko/page/data/components/add-channel-modal/index.marko
-
115marko/page/data/components/change/component.js
-
15marko/page/data/components/change/index.marko
-
8marko/page/data/components/change/style.less
-
16marko/page/data/components/changes-page/component.js
-
12marko/page/data/components/changes-page/index.marko
-
36marko/page/data/components/changes-page/style.less
-
1marko/page/data/components/data-menu/index.marko
-
2marko/page/data/components/edit-channel-modal/index.marko
-
15routes/data/changes.js
-
81rpdata/api/Change.js
-
1server.js
@ -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> |
@ -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", |
||||
|
} |
@ -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> |
@ -0,0 +1,8 @@ |
|||||
|
div.change { |
||||
|
text-indent: -2ch; |
||||
|
margin-left: 2ch; |
||||
|
|
||||
|
a:hover { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
} |
@ -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 |
||||
|
} |
||||
|
} |
@ -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") /> |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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 |
@ -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 } |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue