diff --git a/marko/components/modal/style.less b/marko/components/modal/style.less index e833695..c5ebfaa 100644 --- a/marko/components/modal/style.less +++ b/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 { diff --git a/marko/components/toggle/component.js b/marko/components/toggle/component.js new file mode 100644 index 0000000..a3adbca --- /dev/null +++ b/marko/components/toggle/component.js @@ -0,0 +1,7 @@ +module.exports = class { + set(value) { + if (value !== this.input.value) { + this.emit("change", {target: {value}}) + } + } +} \ No newline at end of file diff --git a/marko/components/toggle/index.marko b/marko/components/toggle/index.marko new file mode 100644 index 0000000..5973501 --- /dev/null +++ b/marko/components/toggle/index.marko @@ -0,0 +1,7 @@ +
+
+ ${input.off} + ${input.on} +
+
${input.value ? input.onDesc : input.offDesc}
+
\ No newline at end of file diff --git a/marko/components/toggle/style.less b/marko/components/toggle/style.less new file mode 100644 index 0000000..5cb5f7b --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/marko/page/story-content/components/edit-story-modal/component.js b/marko/page/story-content/components/edit-story-modal/component.js new file mode 100644 index 0000000..84136d1 --- /dev/null +++ b/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") + } +} \ No newline at end of file diff --git a/marko/page/story-content/components/edit-story-modal/index.marko b/marko/page/story-content/components/edit-story-modal/index.marko new file mode 100644 index 0000000..d2def41 --- /dev/null +++ b/marko/page/story-content/components/edit-story-modal/index.marko @@ -0,0 +1,28 @@ + +

Edit Story

+ +

${state.error}

+ + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/marko/page/story-content/components/page/component.js b/marko/page/story-content/components/page/component.js index ae1b90b..483bc62 100644 --- a/marko/page/story-content/components/page/component.js +++ b/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() diff --git a/marko/page/story-content/components/page/index.marko b/marko/page/story-content/components/page/index.marko index 6f493b5..f3c5b09 100644 --- a/marko/page/story-content/components/page/index.marko +++ b/marko/page/story-content/components/page/index.marko @@ -18,5 +18,6 @@ + \ No newline at end of file diff --git a/marko/page/story-content/components/page/style.less b/marko/page/story-content/components/page/style.less index 8afacbf..7f70b4b 100644 --- a/marko/page/story-content/components/page/style.less +++ b/marko/page/story-content/components/page/style.less @@ -14,7 +14,6 @@ margin-bottom: 1em; h1 { - vertical-align: middle; display: block; margin-bottom: 0; } diff --git a/marko/page/story-content/view.marko b/marko/page/story-content/view.marko index f6a78c5..eebd5ab 100644 --- a/marko/page/story-content/view.marko +++ b/marko/page/story-content/view.marko @@ -1,6 +1,6 @@ <@body> - + \ No newline at end of file diff --git a/middleware/locals.js b/middleware/locals.js index d4d5720..188c936 100644 --- a/middleware/locals.js +++ b/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) } diff --git a/routes/story-content/index.js b/routes/story-content/index.js index 59d662d..9ede535 100644 --- a/routes/story-content/index.js +++ b/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}, }) } \ No newline at end of file diff --git a/rpdata/api/Story.js b/rpdata/api/Story.js index ac1f20b..65c6a35 100644 --- a/rpdata/api/Story.js +++ b/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. *