Browse Source

Long time no see. A bunch of stuff I cba to summarize.

master
Gisle Aune 5 years ago
parent
commit
490725ecd2
  1. 5
      marko/components/date/index.marko
  2. 1
      marko/components/markdown/index.marko
  3. 68
      marko/global/base.less
  4. 6
      marko/global/colors.less
  5. 10
      marko/page/story-content/components/chapter-meta/index.marko
  6. 4
      marko/page/story-content/components/chapter-meta/style.less
  7. 5
      marko/page/story-content/components/chapter/component.js
  8. 14
      marko/page/story-content/components/chapter/index.marko
  9. 26
      marko/page/story-content/components/chapter/style.less
  10. 18
      marko/page/story-content/components/comment/component.js
  11. 110
      marko/page/story-content/components/comment/index.marko
  12. 113
      marko/page/story-content/components/comment/style.less
  13. 12
      marko/page/story-content/components/create-chapter-modal/component.js
  14. 8
      marko/page/story-content/components/create-chapter-modal/index.marko
  15. 96
      marko/page/story-content/components/create-comment-modal/component.js
  16. 27
      marko/page/story-content/components/create-comment-modal/index.marko
  17. 10
      marko/page/story-content/components/edit-chapter-modal/component.js
  18. 12
      marko/page/story-content/components/edit-chapter-modal/index.marko
  19. 42
      rpdata/api/Chapter.js
  20. 72
      rpdata/api/Comment.js

5
marko/components/date/index.marko

@ -0,0 +1,5 @@
import moment from "moment";
<if(input.value != null)>
<span class=["date", input.class]>${(input.utc ? moment.utc(input.value) : moment(input.value)).format(input.format || "MMM D, YYYY")}</span>
</if>

1
marko/components/markdown/index.marko

@ -1,3 +1,4 @@
<div class=["markdown-content", input.class]>
<include(input.renderBody) />
<include(state.render) />
</div>

68
marko/global/base.less

@ -51,3 +51,71 @@ body {
background-color: #555;
color: #000;
}
/*
* Tooltip magic.
*/
.tooltip {
position: relative;
border-bottom: 0.5px dotted; /* If you want dots under the hoverable text */
span {
cursor: default;
}
> .tooltip-content {
visibility: hidden;
width: 400px;
text-align: left;
padding: 0.25em 1ch;
position: absolute;
z-index: 11000;
pointer-events: none;
top: 0;
left: 105%;
border: 1px solid;
> h1 {
font-size: 1.5em;
margin: 0;
text-align: center;
border: none;
}
> h2 {
font-size: 1em;
margin: 0;
margin-bottom: 0.15em;
margin-top: -0.25em;
font-weight: 200;
opacity: 0.5;
text-align: center;
border: none;
}
p:last-of-type {
padding-bottom: 0;
}
}
}
.no-tooltip {
position: relative;
}
.tooltip:hover {
> .tooltip-content {
visibility: visible;
}
}
@media screen and (max-width: 1200px) {
.tooltip {
> .tooltip-content {
visibility: hidden !important;
}
}
}

6
marko/global/colors.less

@ -92,7 +92,7 @@ body.theme-data {
}
div.markdown-content .orange, body.theme-logs div.markdown-content blockquote, body.theme-logs div.markdown-content table, body.theme-logs div.markdown-content pre {
div.markdown-content .orange, body.theme-logs .color-blockquote, body.theme-logs div.markdown-content blockquote, body.theme-logs div.markdown-content table, body.theme-logs div.markdown-content pre {
background-color: rgba(221, 170, 17, 0.125);
color: #DA1;
@ -100,7 +100,7 @@ div.markdown-content .orange, body.theme-logs div.markdown-content blockquote, b
color: #DA1;
}
}
div.markdown-content .blue, body.theme-story div.markdown-content blockquote, body.theme-story div.markdown-content table, body.theme-story div.markdown-content pre {
div.markdown-content .blue, body.theme-story .color-blockquote, body.theme-story div.markdown-content blockquote, body.theme-story div.markdown-content table, body.theme-story div.markdown-content pre {
background-color: rgba(34, 187, 255, 0.125);
color: #4BF;
@ -108,7 +108,7 @@ div.markdown-content .blue, body.theme-story div.markdown-content blockquote, bo
color: #4BF; //
}
}
div.markdown-content .green, body.theme-mapp div.markdown-content blockquote, body.theme-mapp div.markdown-content table, body.theme-mapp div.markdown-content pre {
div.markdown-content .green, body.theme-mapp .color-blockquote, body.theme-mapp div.markdown-content blockquote, body.theme-mapp div.markdown-content table, body.theme-mapp div.markdown-content pre {
background-color: rgba(17,255,136, 0.125);
color: #1F8;

10
marko/page/story-content/components/chapter-meta/index.marko

@ -1,6 +1,14 @@
<if(state.text != null && state.text != "")>
<div class=["chapter-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>
<span else class="tooltip">
<span>${state.text}</span>
<div if(input.character != null) class="tooltip-content color-highlight-dark color-primary">
<h1>${input.character.name}</h1>
<h2>${input.character.author}</h2>
<markdown source=input.character.description />
</div>
</span>
</div>
</if>

4
marko/page/story-content/components/chapter-meta/style.less

@ -13,6 +13,10 @@ div.chapter-meta {
a:hover {
border-bottom: 0.5px solid;
}
.tooltip {
border-bottom: 0.25px dotted;
}
}
div.chapter-meta.weak {

5
marko/page/story-content/components/chapter/component.js

@ -3,6 +3,11 @@ module.exports = class {
this.state = {
modal: null,
removed: false,
commentLabels: {
Article: "Comment",
Message: "Message",
Chat: "Chat Message",
},
}
}

14
marko/page/story-content/components/chapter/index.marko

@ -16,10 +16,22 @@
<chapter-meta kind="date" utc=true value=input.chapter.fictionalDate />
<chapter-meta weak kind="date" value=input.chapter.createdDate />
<chapter-meta weak kind="author" value=input.chapter.author />
<chapter-meta weak kind="message" value=input.chapter.commentMode />
</div>
<markdown class="chapter-content color-text" source=input.chapter.source />
<div class=["comment-section", "type-" + (input.chapter.commentMode.toLowerCase() || "disabled")]>
<comment for(comment in input.chapter.comments) key=comment.id
comment=comment
locked=input.chapter.commentsLocked
mode=input.chapter.commentMode
user=input.user
/>
<if-permitted if(input.chapter.canComment) user=input.user author=input.chapter.author permission="chapter.edit">
<div class="add-comment color-menu" on-click("open", "addComment") >Add ${state.commentLabels[input.chapter.commentMode] || "Comment"}</div>
</if-permitted>
</div>
<edit-chapter-modal enabled=(state.modal === "edit") chapter=input.chapter on-close("close") on-edit("updateChapter") />
<create-comment-modal enabled=(state.modal === "addComment") chapter=input.chapter user=input.user on-close("close") on-add("addComment") />
<move-chapter-modal enabled=(state.modal === "move") chapter=input.chapter user=input.user on-close("close") on-move("moveChapter") />
<remove-chapter-modal enabled=(state.modal === "remove") chapter=input.chapter on-close("close") on-remove("removeChapter") />
</div>

26
marko/page/story-content/components/chapter/style.less

@ -49,4 +49,30 @@
div.chapter-content {
font-size: 1.25em;
}
div.comment-section {
margin: 1em 4ch 2em 4ch;
div.add-comment {
text-align: center;
margin-top: 1em;
padding: 1em 0;
font-size: 1.5em;
border: 1px solid;
opacity: 0.5;
cursor: pointer;
}
div.add-comment:hover {
opacity: 1;
}
div.add-comment:first-child {
margin-top: 0;
}
}
@media screen and (max-width: 1200px) {
div.comment-section {
margin: 1em 2ch 2em 2ch;
}
}
}

18
marko/page/story-content/components/comment/component.js

@ -0,0 +1,18 @@
module.exports = class {
onCreate(input) {
this.state = {
showCharacter: false,
paragraphs: input.comment.source.split('\n').filter(l => l.length > 1),
}
}
onInput(input) {
if (this.state) {
this.state.paragraphs = input.comment.source.split('\n').filter(l => l.length > 1)
}
}
toggle(key) {
this.state[key] = !this.state[key]
}
}

110
marko/page/story-content/components/comment/index.marko

@ -0,0 +1,110 @@
import moment from "moment"
<div class=["comment", "mode-" + input.mode.toLowerCase()]>
<if (input.mode === "Message")>
<div class="metadata">
<div class="options color-menu">
<if-permitted user=input.user author=input.comment.author permission="story.edit">
<a on-click("open", "editComment", input.comment) >Edit</a>
</if-permitted>
<if-permitted user=input.user author=input.comment.author permission="story.edit">
<a on-click("open", "removeComment", input.comment) >Remove</a>
</if-permitted>
</div>
<chapter-meta weak kind="date" value=input.comment.createdDate />
<chapter-meta weak kind="author" value=input.comment.author />
</div>
<div class="content color-blockquote">
<table class="header">
<tbody>
<tr if(input.comment.subject != "")>
<th>Subject:</th>
<td>${input.comment.subject}</td>
</tr>
<tr>
<th>From:</th>
<td>
<div if(input.comment.character != null) class="tooltip" style="display: inline-block">
<span>${input.comment.characterName}</span>
<div class="tooltip-content color-highlight-dark">
<h1>${input.comment.character.name}</h1>
<h2>${input.comment.character.author}</h2>
<markdown source=input.comment.character.description />
</div>
</div>
<div else>
<span>${input.comment.characterName}</span>
</div>
</td>
</tr>
<tr if(input.comment.fictionalDate != null)>
<th>Date:</th>
<td>${moment(input.comment.fictionalDate).format("MMM D, YYYY")}</td>
</tr>
</tbody>
</table>
<markdown class="body" source=input.comment.source />
</div>
</if>
<if(input.mode === "Chat")>
<div class="content">
<div class="options color-menu">
<if-permitted user=input.user author=input.comment.author permission="story.edit">
<a on-click("open", "editComment", input.comment) >Edit</a>
</if-permitted>
<if-permitted user=input.user author=input.comment.author permission="story.edit">
<a on-click("open", "removeComment", input.comment) >Remove</a>
</if-permitted>
</div>
<for (paragraph in state.paragraphs)>
<div class="message-text color-text">
<markdown class="body" source=paragraph>
<span class="decoration color-menu">[</span>
<date class="color-menu" value=input.comment.fictionalDate></date>
<span class="decoration color-menu">]&nbsp;</span>
<span class="decoration color-menu">&lt;</span>
<span if(input.comment.character != null) class="tooltip color-primary">
<span>${input.comment.characterName}</span>
<div class="tooltip-content color-highlight-dark color-primary">
<h1>${input.comment.character.name}</h1>
<h2>${input.comment.character.author}</h2>
<markdown source=input.comment.character.description />
</div>
</span>
<span class="tooltip color-primary" else>
<span>${input.comment.characterName}</span>
<div class="tooltip-content color-highlight-dark color-primary">
<h1>(Unknown Character)</h1>
<h2>${input.comment.author}</h2>
</div>
</span>
<span class="decoration color-menu">&gt;&nbsp;</span>
</markdown>
</div>
</for>
</div>
</if>
<if(input.mode === "Article")>
<div class="metadata">
<div class="options color-menu">
<if-permitted user=input.user author=input.comment.author permission="story.edit">
<a on-click("open", "editComment", input.comment) >Edit</a>
</if-permitted>
<if-permitted user=input.user author=input.comment.author permission="story.edit">
<a on-click("open", "removeComment", input.comment) >Remove</a>
</if-permitted>
</div>
<chapter-meta kind="title" value=input.comment.subject />
<chapter-meta kind="name" value=input.comment.characterName character=input.comment.character />
<chapter-meta kind="date" value=input.comment.fictionalDate />
<chapter-meta weak kind="date" value=input.comment.createdDate />
<chapter-meta weak kind="author" value=input.comment.author />
</div>
<div class="content color-text">
<markdown class="body" source=input.comment.source />
</div>
</if>
</div>

113
marko/page/story-content/components/comment/style.less

@ -0,0 +1,113 @@
div.comment {
position: relative;
div.options {
width: 100%;
max-width: 95ch;
}
}
div.comment.mode-message {
margin-bottom: 1.5em;
font-size: 1em;
.metadata {
padding-bottom: 0.5em;
}
.content {
outline: 1px solid;
padding: 0.25em 1ch;
font-size: 1.25em;
table.header {
border-bottom: 0.5px solid;
margin: 0;
width: 100%;
tbody {
tr {
text-align: left;
th {
width: 12ch;
padding-right: 1ch;
}
td {
width: 100%;
}
}
}
}
}
div.body {
padding-top: 0.5em;
p:first-of-type {
margin-top: 0;
padding-top: 0;
}
}
}
div.comment.mode-chat {
div.options {
display: none;
bottom: -0.25em;
a {
background: black;
opacity: 0.666;
}
a:hover {
opacity: 1;
}
}
div.content {
line-height: 2em;
font-size: 1.125em;
div.tooltip-content {
text-indent: 0;
}
div.message-text {
vertical-align: top;
padding-left: 2ch;
text-indent: -2ch;
div.body {
p:first-of-type {
display: inline;
}
p:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
}
p {
line-height: 1.5em;
}
}
}
}
}
div.comment.mode-article {
margin-bottom: 1em;
.content {
font-size: 1.25em;
}
}
div.comment.mode-chat:hover {
div.options {
display: block;
}
}

12
marko/page/story-content/components/create-chapter-modal/component.js

@ -11,6 +11,7 @@ module.exports = class {
title: "",
source: "",
fictionalDate: "",
commentMode: "Disabled",
},
}
@ -19,18 +20,18 @@ module.exports = class {
onInput(input) {
if (input.chapter && !this.first) {
let {fictionalDate, title, source} = input.chapter
let {fictionalDate, title, source, commentMode, commentsLocked} = input.chapter
if (fictionalDate != null) {
fictionalDate = moment.utc(fictionalDate).format("MMM D, YYYY")
}
this.state.values = {fictionalDate, title, source}
this.state.values = {fictionalDate, title, source, commentMode, commentsLocked}
}
}
change(key, ev) {
this.state.values[key] = ev.target.value
this.state.values = Object.assign({}, this.state.values, {[key]: ev.target.value})
}
open() {
@ -60,7 +61,7 @@ module.exports = class {
fictionalDate = null
}
const input = {storyId: this.input.storyId, title: values.title, source: values.source, fictionalDate}
const input = {storyId: this.input.storyId, title: values.title, source: values.source, fictionalDate, commentMode: values.commentMode}
chapterApi.addChapter(input).then(chapter => {
this.emit("add", chapter)
@ -70,11 +71,12 @@ module.exports = class {
title: "",
source: "",
fictionalDate: "",
commentMode: "Disabled",
}
}).catch(errs => {
console.warn("Failed to edit:", errs)
this.state.error = "Failed to edit: " + errs[0].message
this.state.error = "Failed to edit: " + (errs[0]||errs||{}).message || errs
}).then(() => {
this.state.loading = false
})

8
marko/page/story-content/components/create-chapter-modal/index.marko

@ -12,5 +12,13 @@
<label>Content</label>
<textarea key="source" placeholder="" class="tall" on-change("change", "source") value=state.values.source />
<label>Comments</label>
<select key="kind" class="big" placeholder="Kind" on-change("change", "commentMode") value=state.values.commentMode>
<option value="Disabled" selected=(state.values.commentMode === "Disabled")>Disabled – Hide all comments</option>
<option value="Article" selected=(state.values.commentMode === "Article")>Article – Looks like story chapters</option>
<option value="Chat" selected=(state.values.commentMode === "Chat")>Chat – One-line chat messages</option>
<option value="Message" selected=(state.values.commentMode === "Message")>Message – Extranet Mail</option>
</select>
<button disabled=state.loading on-click("save")>Save</button>
</modal>

96
marko/page/story-content/components/create-comment-modal/component.js

@ -0,0 +1,96 @@
const moment = require("moment")
const {commentApi} = require("../../../../../rpdata/api/Comment")
const {charactersApi} = require("../../../../../rpdata/api/Character")
module.exports = class {
onCreate(input) {
this.state = {
error: null,
loading: false,
values: {
subject: "",
source: "",
characterName: "",
characterId: "",
fictionalDate: "",
},
characters: [],
opened: false,
}
this.first = false
}
onMount() {
charactersApi.list({author: (this.input.user||{}).name}).then((characters) => {
this.state.characters = characters.sort((a,b) => a.name.localeCompare(b.name))
})
}
change(key, ev) {
this.state.values = Object.assign({}, this.state.values, {[key]: ev.target.value})
}
open() {
if (!this.state.opened) {
charactersApi.list({author: (this.input.user||{}).name}).then((characters) => {
this.state.characters = characters.sort((a,b) => a.name.localeCompare(b.name))
})
}
}
close() {
this.first = false
this.state.opened = false
this.emit("close")
}
save() {
const values = this.state.values
if (values.source.length < 1) {
this.state.error = "You cannot post an empty comment."
console.warn(values)
return
}
let fictionalDate = new Date(values.fictionalDate + " UTC")
if (values.fictionalDate != "") {
if (Number.isNaN(fictionalDate)) {
this.state.error = `Could not parse ${values.fictionalDate} as date!`
return
}
} else {
fictionalDate = null
}
const input = {
chapterId: this.input.chapter.id,
subject: values.subject,
source: values.source,
characterName: values.characterName,
characterId: values.characterId,
fictionalDate: fictionalDate,
}
commentApi.addComment(input).then(chapter => {
this.emit("add", chapter)
this.emit("close")
this.state.values = {
subject: "",
source: "",
characterName: "",
characterId: "",
fictionalDate: "",
}
}).catch(errs => {
console.warn("Failed to add comemnt:", errs)
this.state.error = "Failed to add comemnt: " + (errs[0]||errs||{}).message || errs
}).then(() => {
this.state.loading = false
})
}
}

27
marko/page/story-content/components/create-comment-modal/index.marko

@ -0,0 +1,27 @@
<modal class="modal color-text nolabel" key="modal" enabled=(input.enabled) closable on-close("close") on-open("open") >
<h1>Create Chapter</h1>
<p key="error" class="color-error">${state.error}</p>
<if(input.chapter.commentMode !== "Chat")>
<label>Subject</label>
<input key="subject" autofocus placeholder="(Optional)" class="big" on-change("change", "subject") value=state.values.subject />
</if>
<label>Diaply Name</label>
<input key="characterName" autofocus placeholder="(Optional)" on-change("change", "characterName") value=state.values.characterName />
<label>Character ${state.characterId}</label>
<select on-change("change", "characterId")>
<option value="" selected=state.values.characterId=="">Anonymous</option>
<option for(character in state.characters) value=character.id selected=(state.values.characterId==character.id)>${character.name}</option>
</select>
<label>IC Date</label>
<input key="icdate" placeholder="(Optional)" on-change("change", "fictionalDate") value=state.values.fictionalDate />
<label>Content</label>
<textarea key="source" placeholder="" class="tall" on-change("change", "source") value=state.values.source />
<button disabled=state.loading on-click("save")>Save</button>
</modal>

10
marko/page/story-content/components/edit-chapter-modal/component.js

@ -11,6 +11,8 @@ module.exports = class {
title: "",
source: "",
fictionalDate: "",
commentMode: "Article",
commentsLocked: false,
},
}
@ -19,18 +21,18 @@ module.exports = class {
onInput(input) {
if (input.chapter && !this.first) {
let {fictionalDate, title, source} = input.chapter
let {fictionalDate, title, source, commentMode, commentsLocked} = input.chapter
if (fictionalDate != null) {
fictionalDate = moment.utc(fictionalDate).format("MMM D, YYYY")
}
this.state.values = {fictionalDate, title, source}
this.state.values = {fictionalDate, title, source, commentMode, commentsLocked}
}
}
change(key, ev) {
this.state.values[key] = ev.target.value
this.state.values = Object.assign({}, this.state.values, {[key]: ev.target.value})
}
open() {
@ -55,7 +57,7 @@ module.exports = class {
fictionalDate = null
}
const input = {id: this.input.chapter.id, title: values.title, source: values.source}
const input = {id: this.input.chapter.id, title: values.title, source: values.source, commentsLocked: values.commentsLocked, commentMode: values.commentMode}
if (fictionalDate != null) {
input.fictionalDate = fictionalDate

12
marko/page/story-content/components/edit-chapter-modal/index.marko

@ -12,5 +12,17 @@
<label>Content</label>
<textarea key="source" placeholder="Content" class="tall" on-change("change", "source") value=state.values.source />
<label>Comments</label>
<select key="kind" class="big" placeholder="Kind" on-change("change", "commentMode") value=state.values.commentMode>
<option value="Disabled" selected=(state.values.commentMode === "Disabled")>Disabled – Hide all comments</option>
<option value="Article" selected=(state.values.commentMode === "Article")>Article – Looks like story chapters</option>
<option value="Chat" selected=(state.values.commentMode === "Chat")>Chat – One-line chat messages</option>
<option value="Message" selected=(state.values.commentMode === "Message")>Message – Extranet Mail</option>
</select>
<toggle value=state.values.commentsLocked on="Locked" off="Unlocked"
onDesc="Logged-in users can post comments."
offDesc="Nobody can post comments to this chapter."
on-change("change", "commentsLocked") />
<button disabled=state.loading on-click("save")>Save</button>
</modal>

42
rpdata/api/Chapter.js

@ -48,12 +48,31 @@ class ChapterAPI {
createdDate
fictionalDate
editedDate
canComment
commentMode
commentsLocked
comments {
id
subject
author
characterName
character {
id
name
author
description
}
fictionalDate
createdDate
editedDate
source
}
}
}
`, {input}, {permissions: ["member", "story.add"]}).then((data) => {
const ac = data.addChapter
return new Chapter(ac.id, ac.title, ac.author, ac.source, ac.createdDate, ac.fictionalDate, ac.editedDate)
return new Chapter(ac.id, ac.title, ac.author, ac.source, ac.createdDate, ac.fictionalDate, ac.editedDate, ac.canComment, ac.commentMode, ac.commentsLocked, ac.comments)
})
}
@ -70,6 +89,25 @@ class ChapterAPI {
title
fictionalDate
source
canComment
commentMode
commentsLocked
comments {
id
subject
author
characterName
character {
id
name
author
description
}
fictionalDate
createdDate
editedDate
source
}
}
}
`, {input}, {permissions: ["member", "story.edit"]}).then(({editChapter}) => {
@ -77,7 +115,7 @@ class ChapterAPI {
editChapter.fictionalDate = new Date(editChapter.fictionalDate)
}
return editChapter
return Object.assign({comments:[]}, editChapter)
})
}

72
rpdata/api/Comment.js

@ -48,4 +48,74 @@ class CommentCharacter {
}
}
module.exports = {Comment, CommentCharacter}
class CommentAPI {
/**
* @param {string} id
* @returns {Promise<Comment>}
*/
find(id) {
return query(`
query FindComment($id: String!) {
comment(id: $id) {
id
subject
author
characterName
character {
id
name
author
description
}
fictionalDate
createdDate
editedDate
source
}
}
`, {id}).then(({comment}) => {
return new Comment(
comment.id, comment.subject, comment.author,
comment.characterName, comment.character,
comment.fictionalDate, comment.createdDate,
comment.editedDate, comment.source,
)
})
}
/**
* @param {any} input
* @returns {Promise<Comment>}
*/
addComment(input) {
return query(`
mutation AddComment($input: CommentAddInput!) {
addComment(input: $input) {
id
subject
author
characterName
character {
id
name
author
description
}
fictionalDate
createdDate
editedDate
source
}
}
`, {input}).then(({comment}) => {
return new Comment(
comment.id, comment.subject, comment.author,
comment.characterName, comment.character,
comment.fictionalDate, comment.createdDate,
comment.editedDate, comment.source,
)
})
}
}
module.exports = {Comment, CommentCharacter, commentApi: new CommentAPI}
Loading…
Cancel
Save