Browse Source

story-content: Added edit story modal, added toggle component.

1.0
Gisle Aune 6 years ago
parent
commit
a2d949edaf
  1. 2
      marko/components/modal/style.less
  2. 7
      marko/components/toggle/component.js
  3. 7
      marko/components/toggle/index.marko
  4. 27
      marko/components/toggle/style.less
  5. 79
      marko/page/story-content/components/edit-story-modal/component.js
  6. 28
      marko/page/story-content/components/edit-story-modal/index.marko
  7. 4
      marko/page/story-content/components/page/component.js
  8. 1
      marko/page/story-content/components/page/index.marko
  9. 1
      marko/page/story-content/components/page/style.less
  10. 2
      marko/page/story-content/view.marko
  11. 4
      middleware/locals.js
  12. 1
      routes/story-content/index.js
  13. 37
      rpdata/api/Story.js

2
marko/components/modal/style.less

@ -23,7 +23,9 @@ div.overlay {
label {
display: block;
margin-top: 1em;
margin-left: 0.5ch;
font-size: 0.75em;
opacity: 0.5;
}
input {

7
marko/components/toggle/component.js

@ -0,0 +1,7 @@
module.exports = class {
set(value) {
if (value !== this.input.value) {
this.emit("change", {target: {value}})
}
}
}

7
marko/components/toggle/index.marko

@ -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>

27
marko/components/toggle/style.less

@ -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;
}
}

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

@ -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")
}
}

28
marko/page/story-content/components/edit-story-modal/index.marko

@ -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>

4
marko/page/story-content/components/page/component.js

@ -15,6 +15,10 @@ module.exports = class {
this.state.modal = null
}
updateStory(data) {
this.state.story = Object.assign(Object.assign({}, this.state.story), data)
}
addChapter(chapter) {
this.state.story.chapters.push(chapter)
this.refreshStory()

1
marko/page/story-content/components/page/index.marko

@ -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") />
<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") />
</main>

1
marko/page/story-content/components/page/style.less

@ -14,7 +14,6 @@
margin-bottom: 1em;
h1 {
vertical-align: middle;
display: block;
margin-bottom: 0;
}

2
marko/page/story-content/view.marko

@ -1,6 +1,6 @@
<include("../layout", {title: "Stories", site: "story"})>
<@body>
<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>
</include>

4
middleware/locals.js

@ -11,6 +11,10 @@ module.exports = (req, res, next) => {
}
}
} catch(err) {
if (JSON.stringify(err) === "{}") {
return next(err)
}
return res.status(404).json(err)
}

1
routes/story-content/index.js

@ -8,6 +8,7 @@ const viewTemplate = require("../../marko/page/story-content/view.marko")
module.exports = async(req, res, next) => {
res.markoAsync(viewTemplate, {
story: storyApi.find(req.params.id),
categories: storyApi.categories(),
selected: {index: true},
})
}

37
rpdata/api/Story.js

@ -10,17 +10,21 @@ class Story {
* @param {string} name
* @param {string} author
* @param {"Info"|"News"|"Document"|"Background"|"Story"} category Story's category
* @param {boolean} listed
* @param {boolean} open
* @param {string} createdDate
* @param {string} updatedDate
* @param {string} fictionalDate
* @param {{kind:string, name:string}[]} tags
* @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.name = name
this.author = author
this.category = category
this.listed = listed
this.open = open
this.createdDate = new Date(createdDate)
this.updatedDate = new Date(updatedDate)
this.fictionalDate = fictionalDate != null ? new Date(fictionalDate) : null
@ -69,13 +73,15 @@ const storyApi = {
kind
name
}
listed
open
createdDate
fictionalDate
updatedDate
}
}
`, {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
name
}
listed
open
createdDate
fictionalDate
updatedDate
@ -112,6 +120,7 @@ const storyApi = {
`, {id}).then(({story}) => {
return new Story(
story.id, story.name, story.author, story.category,
story.listed, story.open,
story.createdDate, story.updatedDate, story.fictionalDate,
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.
*

Loading…
Cancel
Save