story-content: Added edit story modal, added toggle component.
This commit is contained in:
@@ -23,7 +23,9 @@ div.overlay {
|
|||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
margin-left: 0.5ch;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = class {
|
||||||
|
set(value) {
|
||||||
|
if (value !== this.input.value) {
|
||||||
|
this.emit("change", {target: {value}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="toggle">
|
||||||
|
<div class="toggle-pill color-primary">
|
||||||
|
<a on-click("set", false) class=["toggle-option", !input.value ? "color-highlight-primary" : "color-primary"]>${input.off}</a>
|
||||||
|
<a on-click("set", true) class=["toggle-option", input.value ? "color-highlight-primary" : "color-primary"]>${input.on}</a>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-content color-text">${input.value ? input.onDesc : input.offDesc}</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
div.toggle {
|
||||||
|
div.toggle-pill {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0.5em;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
a.toggle-option {
|
||||||
|
padding: 0.125em 0.5ch;
|
||||||
|
display: inline-block;
|
||||||
|
width: 8ch;
|
||||||
|
text-align: center;
|
||||||
|
outline: 0.5px dotted;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
a.toggle-option.color-highlight-primary {
|
||||||
|
outline: 1px solid;
|
||||||
|
}
|
||||||
|
a.toggle-option:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.toggle-content {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
const moment = require("moment")
|
||||||
|
|
||||||
|
const {storyApi} = require("../../../../../rpdata/api/Story")
|
||||||
|
|
||||||
|
module.exports = class {
|
||||||
|
onCreate() {
|
||||||
|
this.state = {
|
||||||
|
error: null,
|
||||||
|
values: {
|
||||||
|
name: "",
|
||||||
|
fictionalDate: "",
|
||||||
|
category: "",
|
||||||
|
open: "",
|
||||||
|
listed: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput(input) {
|
||||||
|
if (!this.filled) {
|
||||||
|
this.state.values = {
|
||||||
|
name: input.story.name,
|
||||||
|
category: input.story.category,
|
||||||
|
open: input.story.open,
|
||||||
|
listed: input.story.listed,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.story.fictionalDate != null) {
|
||||||
|
this.state.values.fictionalDate = moment.utc(input.story.fictionalDate).format("MMM D, YYYY")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
change(key, ev) {
|
||||||
|
this.state.values[key] = ev.target.value
|
||||||
|
this.state.values = Object.assign({}, this.state.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
const values = this.state.values
|
||||||
|
|
||||||
|
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 = {id: this.input.story.id, name: values.name, category: values.category, open: values.open, listed: values.listed}
|
||||||
|
if (fictionalDate != null) {
|
||||||
|
input.fictionalDate = fictionalDate
|
||||||
|
} else {
|
||||||
|
input.clearFictionalDate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.loading = true
|
||||||
|
storyApi.edit(input).then(data => {
|
||||||
|
this.emit("edit", data)
|
||||||
|
this.emit("close")
|
||||||
|
}).catch(errs => {
|
||||||
|
console.warn("Failed to edit:", errs)
|
||||||
|
this.state.error = "Failed to edit: " + errs[0].message
|
||||||
|
}).then(() => {
|
||||||
|
this.state.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.emit("close")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<modal class="modal color-text nolabel" key="modal2" enabled=(input.enabled) closable on-close("close") >
|
||||||
|
<h1>Edit Story</h1>
|
||||||
|
|
||||||
|
<p key="error" class="color-error">${state.error}</p>
|
||||||
|
|
||||||
|
<label>Title</label>
|
||||||
|
<input key="name" placeholder="Name" class="big" on-change("change", "name") value=state.values.name />
|
||||||
|
|
||||||
|
<label>IC Date</label>
|
||||||
|
<input key="icdate" placeholder="IC Date" on-change("change", "fictionalDate") value=state.values.fictionalDate />
|
||||||
|
|
||||||
|
<label>Category</label>
|
||||||
|
<select key="category" placeholder="Kind" on-change("change", "category")>
|
||||||
|
<option for(category in input.categories) value=category.name selected=(state.values.category === category.name)>${category.name}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Options</label>
|
||||||
|
<toggle value=state.values.listed on="Listed" off="Unlisted"
|
||||||
|
onDesc="The story will be visible on the front page."
|
||||||
|
offDesc="A direct link is required to view this story."
|
||||||
|
on-change("change", "listed") />
|
||||||
|
<toggle value=state.values.open on="Open" off="Closed"
|
||||||
|
onDesc="Other authors can add chapters to this story."
|
||||||
|
offDesc="Only you can add chapters to this story."
|
||||||
|
on-change("change", "open") />
|
||||||
|
|
||||||
|
<button disabled=state.loading on-click("save")>Save</button>
|
||||||
|
</modal>
|
||||||
@@ -15,6 +15,10 @@ module.exports = class {
|
|||||||
this.state.modal = null
|
this.state.modal = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStory(data) {
|
||||||
|
this.state.story = Object.assign(Object.assign({}, this.state.story), data)
|
||||||
|
}
|
||||||
|
|
||||||
addChapter(chapter) {
|
addChapter(chapter) {
|
||||||
this.state.story.chapters.push(chapter)
|
this.state.story.chapters.push(chapter)
|
||||||
this.refreshStory()
|
this.refreshStory()
|
||||||
|
|||||||
@@ -18,5 +18,6 @@
|
|||||||
|
|
||||||
<create-chapter-modal storyId=state.story.id enabled=(state.modal === "chapter.add") chapter=input.chapter on-close("close") on-add("addChapter") />
|
<create-chapter-modal storyId=state.story.id enabled=(state.modal === "chapter.add") chapter=input.chapter on-close("close") on-add("addChapter") />
|
||||||
<edit-story-tags-modal enabled=(state.modal === "story.tags") story=state.story on-tags("updateStoryTags") on-close("close") />
|
<edit-story-tags-modal enabled=(state.modal === "story.tags") story=state.story on-tags("updateStoryTags") on-close("close") />
|
||||||
|
<edit-story-modal enabled=(state.modal === "story.edit") story=state.story categories=input.categories on-edit("updateStory") on-close("close") />
|
||||||
<remove-story-modal enabled=(state.modal === "story.remove") story=state.story on-remove("timeToDie") on-close("close") />
|
<remove-story-modal enabled=(state.modal === "story.remove") story=state.story on-remove("timeToDie") on-close("close") />
|
||||||
</main>
|
</main>
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
vertical-align: middle;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<include("../layout", {title: "Stories", site: "story"})>
|
<include("../layout", {title: "Stories", site: "story"})>
|
||||||
<@body>
|
<@body>
|
||||||
<background src="/assets/images/bg.png" opacity=0.25 />
|
<background src="/assets/images/bg.png" opacity=0.25 />
|
||||||
<page story=input.story user=input.user selected=input.selected />
|
<page story=input.story categories=input.categories user=input.user selected=input.selected />
|
||||||
</@body>
|
</@body>
|
||||||
</include>
|
</include>
|
||||||
@@ -11,6 +11,10 @@ module.exports = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
if (JSON.stringify(err) === "{}") {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(404).json(err)
|
return res.status(404).json(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const viewTemplate = require("../../marko/page/story-content/view.marko")
|
|||||||
module.exports = async(req, res, next) => {
|
module.exports = async(req, res, next) => {
|
||||||
res.markoAsync(viewTemplate, {
|
res.markoAsync(viewTemplate, {
|
||||||
story: storyApi.find(req.params.id),
|
story: storyApi.find(req.params.id),
|
||||||
|
categories: storyApi.categories(),
|
||||||
selected: {index: true},
|
selected: {index: true},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
+35
-2
@@ -10,17 +10,21 @@ class Story {
|
|||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} author
|
* @param {string} author
|
||||||
* @param {"Info"|"News"|"Document"|"Background"|"Story"} category Story's category
|
* @param {"Info"|"News"|"Document"|"Background"|"Story"} category Story's category
|
||||||
|
* @param {boolean} listed
|
||||||
|
* @param {boolean} open
|
||||||
* @param {string} createdDate
|
* @param {string} createdDate
|
||||||
* @param {string} updatedDate
|
* @param {string} updatedDate
|
||||||
* @param {string} fictionalDate
|
* @param {string} fictionalDate
|
||||||
* @param {{kind:string, name:string}[]} tags
|
* @param {{kind:string, name:string}[]} tags
|
||||||
* @param {Chapter[]} chapters
|
* @param {Chapter[]} chapters
|
||||||
*/
|
*/
|
||||||
constructor(id, name, author, category, createdDate, updatedDate, fictionalDate, tags, chapters) {
|
constructor(id, name, author, category, listed, open, createdDate, updatedDate, fictionalDate, tags, chapters) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.name = name
|
this.name = name
|
||||||
this.author = author
|
this.author = author
|
||||||
this.category = category
|
this.category = category
|
||||||
|
this.listed = listed
|
||||||
|
this.open = open
|
||||||
this.createdDate = new Date(createdDate)
|
this.createdDate = new Date(createdDate)
|
||||||
this.updatedDate = new Date(updatedDate)
|
this.updatedDate = new Date(updatedDate)
|
||||||
this.fictionalDate = fictionalDate != null ? new Date(fictionalDate) : null
|
this.fictionalDate = fictionalDate != null ? new Date(fictionalDate) : null
|
||||||
@@ -69,13 +73,15 @@ const storyApi = {
|
|||||||
kind
|
kind
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
listed
|
||||||
|
open
|
||||||
createdDate
|
createdDate
|
||||||
fictionalDate
|
fictionalDate
|
||||||
updatedDate
|
updatedDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, {filter}).then(({stories}) => {
|
`, {filter}).then(({stories}) => {
|
||||||
return stories.map(d => new Story(d.id, d.name, d.author, d.category, d.createdDate, d.updatedDate, d.fictionalDate, d.tags))
|
return stories.map(d => new Story(d.id, d.name, d.author, d.category, d.listed, d.open, d.createdDate, d.updatedDate, d.fictionalDate, d.tags))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,6 +101,8 @@ const storyApi = {
|
|||||||
kind
|
kind
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
listed
|
||||||
|
open
|
||||||
createdDate
|
createdDate
|
||||||
fictionalDate
|
fictionalDate
|
||||||
updatedDate
|
updatedDate
|
||||||
@@ -112,6 +120,7 @@ const storyApi = {
|
|||||||
`, {id}).then(({story}) => {
|
`, {id}).then(({story}) => {
|
||||||
return new Story(
|
return new Story(
|
||||||
story.id, story.name, story.author, story.category,
|
story.id, story.name, story.author, story.category,
|
||||||
|
story.listed, story.open,
|
||||||
story.createdDate, story.updatedDate, story.fictionalDate,
|
story.createdDate, story.updatedDate, story.fictionalDate,
|
||||||
story.tags, story.chapters
|
story.tags, story.chapters
|
||||||
)
|
)
|
||||||
@@ -185,6 +194,30 @@ const storyApi = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call `editStory(input)` mutation, returns the changable fields.
|
||||||
|
*
|
||||||
|
* @param {{id:string, name:string, category:string, author:string, open:boolean, listed:boolean, fictionalDate:Date, clearFictionalDate:boolean}} input
|
||||||
|
* @returns {Promise<{id:string, name:string, category:string, author:string, open:boolean, listed:boolean, fictionalDate:Date}>}
|
||||||
|
*/
|
||||||
|
edit(input) {
|
||||||
|
return query(`
|
||||||
|
mutation EditStory($input: StoryEditInput!) {
|
||||||
|
editStory(input:$input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
category
|
||||||
|
author
|
||||||
|
open
|
||||||
|
listed
|
||||||
|
fictionalDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {input}, {permissions: ["member", "story.edit"]}).then(({editStory}) => {
|
||||||
|
return editStory
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call `removeStory(input)` mutation, returns the id.
|
* Call `removeStory(input)` mutation, returns the id.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user