Gisle Aune
6 years ago
27 changed files with 442 additions and 13 deletions
-
5marko/components/annotation/style.less
-
10marko/components/content-list-item/index.marko
-
15marko/components/content-list-item/style.less
-
2marko/components/markdown/index.marko
-
3marko/components/markdown/style.less
-
9marko/components/menu-link/style.less
-
6marko/global/colors.less
-
8marko/page/logs-content/components/logs-content-menu/index.marko
-
17marko/page/logs-content/components/page/component.js
-
7marko/page/logs-content/components/page/index.marko
-
10marko/page/logs-content/components/page/style.less
-
50marko/page/logs-content/components/post-meta/component.js
-
6marko/page/logs-content/components/post-meta/index.marko
-
20marko/page/logs-content/components/post-meta/style.less
-
50marko/page/logs-content/components/post/component.js
-
20marko/page/logs-content/components/post/index.marko
-
36marko/page/logs-content/components/post/style.less
-
6marko/page/logs-content/view.marko
-
1marko/page/logs/components/logs-list/index.marko
-
2marko/page/story-content/components/chapter/index.marko
-
4marko/page/story-content/components/chapter/style.less
-
12routes/logs-content/index.js
-
17rpdata/api/Channel.js
-
29rpdata/api/Character.js
-
81rpdata/api/Log.js
-
26rpdata/api/Post.js
-
3server.js
@ -1,3 +1,3 @@ |
|||
<div class=["markdown-content", "color-text", input.class]> |
|||
<div class=["markdown-content", input.class]> |
|||
<include(state.render) /> |
|||
</div> |
@ -0,0 +1,8 @@ |
|||
<menu user=input.user> |
|||
<menu-header>Log</menu-header> |
|||
<menu-link href=("/logs/?channels="+input.log.channel.name) icon="#">${input.log.channel.name}</menu-link> |
|||
<menu-link if(input.log.channel.eventName) href=("/logs/?channels="+input.log.channel.eventName) icon="E">${input.log.channel.eventName}</menu-link> |
|||
<for(character in input.log.characters)> |
|||
<menu-link href=("/logs/?characters="+character.id) icon="C">${character.name} <span class="weak">(${character.author})</span></menu-link> |
|||
</for> |
|||
</menu> |
@ -0,0 +1,17 @@ |
|||
const moment = require("moment") |
|||
|
|||
module.exports = class { |
|||
onCreate(input) { |
|||
this.state = { |
|||
log: input.log |
|||
} |
|||
} |
|||
|
|||
get title() { |
|||
if (this.state.log.title) { |
|||
return this.state.log.title |
|||
} |
|||
|
|||
return `${this.state.log.channel.name} – ${moment(this.state.log.date).format("MMMM D, YYYY")}` |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
<logs-content-menu user=input.user log=state.log /> |
|||
<main> |
|||
<div class="logs-content"> |
|||
<h1 class="color-primary">${component.title}</h1> |
|||
<post for(post in state.log.posts) post=post characters=state.log.characters /> |
|||
</div> |
|||
</main> |
@ -0,0 +1,10 @@ |
|||
div.logs-content { |
|||
width: 90%; |
|||
max-width: 75ch; |
|||
margin: auto; |
|||
|
|||
h1 { |
|||
font-weight: 200; |
|||
text-align: center; |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
const moment = require("moment") |
|||
|
|||
module.exports = class { |
|||
onCreate() { |
|||
this.state = {text: null, color: "color-menu", href: null, tooltip: null} |
|||
} |
|||
|
|||
onInput(input) { |
|||
if (this.state != null) { |
|||
this.updateState(input) |
|||
} |
|||
} |
|||
|
|||
updateState({kind, weak, value, updated}) { |
|||
this.state = {text: null, color: "color-menu", href: null, tooltip: null} |
|||
|
|||
if (value == null || value == "") { |
|||
return |
|||
} |
|||
|
|||
switch (kind) { |
|||
case "timestamp": { |
|||
const m = moment(value) |
|||
if (m.year() < 2) { |
|||
break |
|||
} |
|||
|
|||
this.state.tooltip = m.format("YYYY-MM-DD HH:mm:ss") |
|||
this.state.text = `[${m.format("HH:mm")}]` |
|||
|
|||
break |
|||
} |
|||
|
|||
case "author": { |
|||
this.state.text = value |
|||
this.state.tooltip = "See all stories by " + value |
|||
this.state.href = `/story/by-author/${value}/` |
|||
|
|||
break |
|||
} |
|||
|
|||
default: { |
|||
this.state.color = "color-menu" |
|||
this.state.text = value |
|||
|
|||
break |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
<if(state.text != null && state.text != "")> |
|||
<div class=["post-meta", input.kind, state.color, input.weak ? "weak" : null] title=state.tooltip> |
|||
<a if(state.href != null) href=state.href>${state.text}</a> |
|||
<span else>${state.text}</span> |
|||
</div> |
|||
</if> |
@ -0,0 +1,20 @@ |
|||
div.post-meta { |
|||
display: inline-block; |
|||
padding: 0; |
|||
padding-right: 1em; |
|||
margin: 0; |
|||
|
|||
user-select: none; |
|||
|
|||
a { |
|||
color: inherit; |
|||
border-bottom: 0.25px dotted; |
|||
} |
|||
a:hover { |
|||
border-bottom: 0.5px solid; |
|||
} |
|||
} |
|||
|
|||
div.post-meta.weak { |
|||
opacity: 0.5; |
|||
} |
@ -0,0 +1,50 @@ |
|||
module.exports = class { |
|||
onCreate(input) { |
|||
this.state = { |
|||
shortName: "", |
|||
name: "", |
|||
text: "", |
|||
} |
|||
|
|||
this.updatePost(input) |
|||
} |
|||
|
|||
onInput(input) { |
|||
if (this.state == null) { |
|||
return |
|||
} |
|||
|
|||
this.updatePost(input) |
|||
} |
|||
|
|||
updatePost(input) { |
|||
this.state.shortName = input.post.nick.split("_").shift() |
|||
this.state.name = input.post.nick |
|||
|
|||
this.state.text = input.post.text.replace(/\x02/g, "**") |
|||
|
|||
if (input.post.kind === "text" && !this.state.text.includes("\"")) { |
|||
this.state.text = '"' + this.state.text + '"' |
|||
} |
|||
|
|||
if (!input.post.nick.startsWith("=")) { |
|||
const postNick = input.post.nick.replace("'s", "").replace("s'", "s") |
|||
for (const character of input.characters) { |
|||
for (const nick of character.nicks) { |
|||
if (nick === postNick) { |
|||
this.state.shortName = character.shortName |
|||
return |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
kindClass(prefix) { |
|||
if (this.input.post == null) { |
|||
return |
|||
} |
|||
|
|||
return `${prefix}${this.input.post.kind.replace(".", "-")}` |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
<div class=["post", component.kindClass("post-type")]> |
|||
<div class="post-meta-row"> |
|||
<post-meta kind="timestamp" value=input.post.time /> |
|||
<post-meta kind="nick" value=input.post.nick /> |
|||
</div> |
|||
<div if(input.post.kind.startsWith("annotation.")) class="post-body" > |
|||
<annotation level=(input.post.kind.substring(11))> |
|||
<markdown class="post-content" source=state.text /> |
|||
</annotation> |
|||
</div> |
|||
<div if(input.post.kind === "scene") class="post-body color-text"> |
|||
<markdown class="post-content post-scene" source=state.text /> |
|||
</div> |
|||
<div if(input.post.kind === "action") class="post-body color-text"> |
|||
<markdown class="post-content post-action" source=(state.shortName + " " + state.text) /> |
|||
</div> |
|||
<div if(input.post.kind === "text") class="post-body color-text"> |
|||
<markdown class="post-content post-text" source=state.text /> |
|||
</div> |
|||
</div> |
@ -0,0 +1,36 @@ |
|||
div.post { |
|||
margin-bottom: 1ch; |
|||
|
|||
.post-meta-row { |
|||
padding: 0; |
|||
margin: 0; |
|||
} |
|||
|
|||
div.post-body { |
|||
font-size: 1.25em; |
|||
|
|||
div.nick-wrapper { |
|||
display: inline; |
|||
|
|||
.nick-decoration { |
|||
display: inline; |
|||
} |
|||
|
|||
.nick { |
|||
display: inline; |
|||
} |
|||
} |
|||
|
|||
div.post-content { |
|||
display: inline; |
|||
} |
|||
|
|||
div.annotation { |
|||
margin-top: 0.33em; |
|||
} |
|||
} |
|||
div.post-body p:first-of-type { |
|||
margin-top: 0; |
|||
padding-top: 0; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
<include("../layout", {title: "Logs", site: "logs"})> |
|||
<@body> |
|||
<background src="/assets/images/bg.png" opacity=0.25 /> |
|||
<page log=input.log user=input.user selected=input.selected /> |
|||
</@body> |
|||
</include> |
@ -0,0 +1,12 @@ |
|||
const express = require("express") |
|||
|
|||
const {logsApi} = require("../../rpdata/api/Log") |
|||
|
|||
const viewTemplate = require("../../marko/page/logs-content/view.marko") |
|||
|
|||
module.exports = async(req, res, next) => { |
|||
res.markoAsync(viewTemplate, { |
|||
log: logsApi.find(req.params.id), |
|||
selected: {index: true}, |
|||
}) |
|||
} |
@ -0,0 +1,17 @@ |
|||
const {query} = require("../client") |
|||
|
|||
class Channel { |
|||
constructor(name, logged, hub, eventName, locationName) { |
|||
this.name = name |
|||
this.logged = logged |
|||
this.hub = hub |
|||
this.eventName = eventName |
|||
this.locationName = locationName |
|||
} |
|||
|
|||
static fromData(data) { |
|||
return new Channel(data.name, data.logged, data.hub, data.eventName, data.locationName) |
|||
} |
|||
} |
|||
|
|||
module.exports = { Channel } |
@ -1,5 +1,30 @@ |
|||
const {query} = require("../client") |
|||
|
|||
class Character { |
|||
|
|||
} |
|||
/** |
|||
* @param {string} id |
|||
* @param {string[]} nicks |
|||
* @param {string} author |
|||
* @param {string} name |
|||
* @param {string} shortName |
|||
* @param {string} description |
|||
*/ |
|||
constructor(id, nicks, author, name, shortName, description) { |
|||
this.id = id |
|||
this.nicks = nicks |
|||
this.nick = this.nicks[0] || null |
|||
this.author = author |
|||
this.name = name |
|||
this.shortName = shortName |
|||
this.description = description |
|||
} |
|||
|
|||
/** |
|||
* Create a character object from data. |
|||
*/ |
|||
static fromData(data) { |
|||
return new Character(data.id, data.nicks, data.author, data.name, data.shortName, data.description) |
|||
} |
|||
} |
|||
|
|||
module.exports = { Character } |
@ -0,0 +1,81 @@ |
|||
const {query} = require("../client") |
|||
|
|||
const {Character} = require("./Character") |
|||
const {Channel} = require("./Channel") |
|||
const {Post} = require("./Post") |
|||
|
|||
class Log { |
|||
/** |
|||
* @param {string} id |
|||
* @param {string} shortId |
|||
* @param {Date | string | number} date |
|||
* @param {string} title |
|||
* @param {string} eventName |
|||
* @param {string} description |
|||
* @param {string} open |
|||
* @param {any} channel |
|||
* @param {any[]} characters |
|||
* @param {any[]} posts |
|||
*/ |
|||
constructor(id, shortId, date, title, eventName, description, open, channel, characters, posts) { |
|||
this.id = id |
|||
this.shortId = shortId |
|||
this.date = date |
|||
this.title = title |
|||
this.eventName = eventName |
|||
this.description = description |
|||
this.open = open |
|||
this.channel = Channel.fromData(channel) |
|||
this.characters = characters.map(c => Character.fromData(c)) |
|||
this.posts = posts.map(p => Post.fromData(p)) |
|||
} |
|||
|
|||
static fromData(data) { |
|||
return new Log(data.id, data.shortId, data.date, data.title, data.eventName, data.description, data.open, data.channel, data.characters, data.posts) |
|||
} |
|||
} |
|||
|
|||
const logsApi = { |
|||
find(id) { |
|||
return query(`
|
|||
query FindLog($id: String!) { |
|||
log(id: $id) { |
|||
id |
|||
shortId |
|||
date |
|||
title |
|||
eventName |
|||
description |
|||
open |
|||
channel { |
|||
name |
|||
logged |
|||
hub |
|||
eventName |
|||
locationName |
|||
} |
|||
characters { |
|||
id |
|||
nicks |
|||
author |
|||
name |
|||
shortName |
|||
description |
|||
} |
|||
posts { |
|||
id |
|||
position |
|||
time |
|||
kind |
|||
nick |
|||
text |
|||
} |
|||
} |
|||
} |
|||
`, {id}).then(({log}) => {
|
|||
return Log.fromData(log) |
|||
}) |
|||
}, |
|||
} |
|||
|
|||
module.exports = { Log, logsApi } |
@ -0,0 +1,26 @@ |
|||
const {query} = require("../client") |
|||
|
|||
class Post { |
|||
/** |
|||
* @param {string} id |
|||
* @param {number} position |
|||
* @param {Date | string | number} time |
|||
* @param {string} kind |
|||
* @param {string} nick |
|||
* @param {string} text |
|||
*/ |
|||
constructor(id, position, time, kind, nick, text) { |
|||
this.id = id |
|||
this.position = position |
|||
this.time = new Date(time) |
|||
this.kind = kind |
|||
this.nick = nick |
|||
this.text = text |
|||
} |
|||
|
|||
static fromData(data) { |
|||
return new Post(data.id, data.position, data.time, data.kind, data.nick, data.text) |
|||
} |
|||
} |
|||
|
|||
module.exports = {Post} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue