Browse Source

story-content: Added tag add/remove modal

1.0
Gisle Aune 6 years ago
parent
commit
2e8737505e
  1. 26
      marko/components/modal/style.less
  2. 12
      marko/global/colors.less
  3. 40
      marko/page/story-content/components/edit-story-tags-modal/component.js
  4. 7
      marko/page/story-content/components/edit-story-tags-modal/index.marko
  5. 7
      marko/page/story-content/components/page/component.js
  6. 4
      marko/page/story-content/components/page/index.marko
  7. 62
      marko/page/story-content/components/story-tag-options/component.js
  8. 21
      marko/page/story-content/components/story-tag-options/index.marko
  9. 46
      marko/page/story-content/components/story-tag-options/style.less
  10. 5
      marko/page/story-content/components/story-tags/component.js
  11. 1
      marko/page/story-content/components/story-tags/index.marko
  12. 14
      marko/page/story-content/components/story-tags/style.less
  13. 42
      rpdata/api/Story.js
  14. 2
      rpdata/api/Tag.js

26
marko/components/modal/style.less

@ -39,7 +39,31 @@ div.overlay {
} }
input.big { input.big {
font-size: 1.5em; font-size: 1.5em;
padding: 0 0.3333ch;
padding: 0 0.3336ch;
}
select {
display: block;
width: 100%;
padding: 0 0ch;
border: none;
font-family: inherit;
background: none;
outline: none;
opacity: 0.5;
option {
background-color: black;
}
}
select.big {
font-size: 1.5em;
margin: 0;
}
select:focus {
opacity: 1;
} }
textarea { textarea {

12
marko/global/colors.less

@ -8,10 +8,10 @@ body.theme-story {
.menu-link:hover, .menu > .head-menu > a:hover { .menu-link:hover, .menu > .head-menu > a:hover {
background-color: rgba(17, 187, 255, 0.25); background-color: rgba(17, 187, 255, 0.25);
} }
.color-primary, button, input:focus, textarea:focus, .modal h1, .modal h2, .modal label, .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content hr, .markdown-content a {
.color-primary, button, input:focus, textarea:focus, select:focus, .modal h1, .modal h2, .modal label, .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content hr, .markdown-content a {
color: #4BF; color: #4BF;
} }
.color-text, input, textarea {
.color-text, input, textarea, select {
color: #ABC; color: #ABC;
} }
.color-highlight-primary { .color-highlight-primary {
@ -33,10 +33,10 @@ body.theme-logs {
.menu-link:hover, .Menu > .head-menu > a:hover { .menu-link:hover, .Menu > .head-menu > a:hover {
background-color: rgba(255, 187, 17, 0.25); background-color: rgba(255, 187, 17, 0.25);
} }
.color-primary, button, .modal h1, .modal h2, .modal label, .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content hr, .markdown-content a {
.color-primary, button, input:focus, textarea:focus, select:focus, .modal h1, .modal h2, .modal label, .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content hr, .markdown-content a {
color: #DA1; color: #DA1;
} }
.color-text, input, textarea {
.color-text, input, textarea, select {
color: #AAA; color: #AAA;
} }
.color-highlight-dark { .color-highlight-dark {
@ -58,10 +58,10 @@ body.theme-logs {
.menu-link:hover, .menu > .head-menu > a:hover { .menu-link:hover, .menu > .head-menu > a:hover {
background-color: rgba(35, 255, 128, 0.25); background-color: rgba(35, 255, 128, 0.25);
} }
.color-primary, button, .modal h1, .modal h2, .modal label, .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content hr, .markdown-content a {
.color-primary, button, input:focus, textarea:focus, select:focus, .modal h1, .modal h2, .modal label, .markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content hr, .markdown-content a {
color: #1F8; color: #1F8;
} }
.color-text, input, textarea {
.color-text, input, textarea, select {
color: #ACB; color: #ACB;
} }
} }

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

@ -0,0 +1,40 @@
const moment = require("moment")
const {storyApi} = require("../../../../../rpdata/api/Story")
module.exports = class {
onCreate(input) {
this.state = {
error: null,
loading: false,
}
}
change(key, ev) {
this.state.values[key] = ev.target.value
}
close() {
this.emit("close")
}
addTag(tag) {
this.state.error = null
storyApi.addTag({id: this.input.story.id, tag}).then(({tags}) => {
this.emit("tags", tags)
}).catch(errs => {
this.state.error = errs.message || errs[0].message || "Add tag failed"
})
}
removeTag(tag) {
this.state.error = null
storyApi.removeTag({id: this.input.story.id, tag}).then(({tags}) => {
this.emit("tags", tags)
}).catch(errs => {
this.state.error = errs.message || errs[0].message || "Remove tag failed"
})
}
}

7
marko/page/story-content/components/edit-story-tags-modal/index.marko

@ -0,0 +1,7 @@
<modal class="modal color-text nolabel" key="modal2" enabled=(input.enabled) closable on-close("close") >
<h1>Edit Tags</h1>
<p key="error" class="color-error">${state.error}</p>
<story-tag-options tags=input.story.tags loading=state.loading on-remove("removeTag") on-add("addTag") />
</modal>

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

@ -48,8 +48,6 @@ module.exports = class {
} }
menuSelect(kind, id) { menuSelect(kind, id) {
console.log(kind, id)
switch (kind) { switch (kind) {
case "add": case "add":
this.open("chapter.add") this.open("chapter.add")
@ -57,6 +55,11 @@ module.exports = class {
} }
} }
updateStoryTags(tags) {
this.state.story.tags = tags
this.refreshStory()
}
refreshStory() { refreshStory() {
this.state.story = Object.assign({}, this.state.story) this.state.story = Object.assign({}, this.state.story)
} }

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

@ -2,10 +2,10 @@
<main> <main>
<div class="story-content"> <div class="story-content">
<h1 class="color-primary">${state.story.name}</h1> <h1 class="color-primary">${state.story.name}</h1>
<story-tags tags=state.story.tags />
<story-tags tags=state.story.tags on-select("open", "story.tags")/>
<chapter for(chapter in state.story.chapters) key=chapter.id chapter=chapter on-edit("updateChapter", chapter.id) on-remove("removeChapter", chapter.id)/> <chapter for(chapter in state.story.chapters) key=chapter.id chapter=chapter on-edit("updateChapter", chapter.id) on-remove("removeChapter", chapter.id)/>
</div> </div>
<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") />
</main> </main>

62
marko/page/story-content/components/story-tag-options/component.js

@ -0,0 +1,62 @@
const {tagApi} = require("../../../../../rpdata/api/Tag")
module.exports = class {
onCreate() {
this.state = {
values: {
name: "",
kind: "Event",
},
tags: [],
suggestions: [],
}
}
onMount() {
tagApi.list().then(list => {
this.state.tags = list
}).catch(err => {})
this.getEl("name").addEventListener("keydown", ev => {
setTimeout(() => {
const name = this.getEl("name").value
const kind = this.getEl("kind").value
const search = name.toLowerCase()
this.state.values = {name, kind}
this.state.suggestions = []
const suggestions = this.state.tags.filter(t => t.name.toLowerCase().includes(search))
if (suggestions.length > 0 && suggestions.length < 10) {
if (suggestions.length > 1 || suggestions[0].name !== name) {
this.state.suggestions = suggestions
}
}
}, 100)
})
}
add(tag) {
this.state.values = {
kind: "Event",
name: "",
}
this.emit("add", tag)
}
change(key, ev) {
const value = ev.target.value
this.state.values[key] = value
}
applySuggestion(tag) {
this.state.values = {
name: tag.name,
kind: tag.kind,
}
this.state.suggestions = []
}
}

21
marko/page/story-content/components/story-tag-options/index.marko

@ -0,0 +1,21 @@
<div class="tag-options">
<input key="name" placeholder="Name" class="big" on-change("change", "name") value=state.values.name />
<select key="kind" placeholder="Kind" class="big" on-change("change", "kind") value=state.values.kind>
<option value="Character" selected=(state.values.kind === "Character")>Character</option>
<option value="Event" selected=(state.values.kind === "Event")>Event</option>
<option value="Location" selected=(state.values.kind === "Location")>Location</option>
<option value="Organization" selected=(state.values.kind === "Organization")>Organization</option>
<option value="Series" selected=(state.values.kind === "Series")>Series</option>
</select>
<div class="suggestion-list" if(state.suggestions.length > 0)>
<div class="header color-menu">Suggestions:</div>
<a for(tag in state.suggestions) on-click("applySuggestion", tag) class=["tag", "color-tag-" + tag.kind.toLowerCase()]>${tag.name}</a>
</div>
<button disabled=(input.loading) on-click("add", state.values)>Add</button>
<div for(tag in (input.tags || [])) class="tag" key=(tag.kind+":"+tag.name)>
<div class=("content color-tag-"+tag.kind.toLowerCase())>${tag.name}</div>
<button on-click("emit", "remove", tag)>Remove</button>
</div>
</div>

46
marko/page/story-content/components/story-tag-options/style.less

@ -0,0 +1,46 @@
div.tag-options {
div.tag {
padding: 0.25em;
border-bottom: 1px solid rgba(0, 220, 255, 0.125);
div.content {
width: 80%;
display: inline-block;
}
button {
width: 20%;
display: inline-block;
margin: 0;
}
}
div.suggestion-list {
margin-top: 1em;
a.tag {
display: inline-block;
margin: 0.5em 0.5ch;
padding: 0.0625em 0.5ch;
outline: 1px solid;
opacity: 0.75;
user-select: none;
}
a.tag:hover {
opacity: 1;
cursor: pointer;
}
div.header {
display: inline-block;
margin: 0.5em 0.5ch;
padding: 0.0625em 0.5ch;
opacity: 0.5;
user-select: none;
}
}
}

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

@ -0,0 +1,5 @@
module.exports = class {
select(kind) {
this.emit("select", kind)
}
}

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

@ -1,3 +1,4 @@
<div class="story-tags"> <div class="story-tags">
<a for(tag in input.tags) href=("/story/by-tag/" + tag.kind + "/" + encodeURIComponent(tag.name)) class=["tag", "color-tag-" + tag.kind.toLowerCase()]>${tag.name}</a> <a for(tag in input.tags) href=("/story/by-tag/" + tag.kind + "/" + encodeURIComponent(tag.name)) class=["tag", "color-tag-" + tag.kind.toLowerCase()]>${tag.name}</a>
<a on-click("select", "edit") class="option color-menu">Edit</a>
</div> </div>

14
marko/page/story-content/components/story-tags/style.less

@ -17,4 +17,18 @@ div.story-tags {
opacity: 1; opacity: 1;
cursor: pointer; cursor: pointer;
} }
a.option {
display: inline-block;
margin: 0.5em 0.5ch;
padding: 0.0625em 0.5ch;
opacity: 0.5;
user-select: none;
}
a.option:hover {
opacity: 1;
cursor: pointer;
}
} }

42
rpdata/api/Story.js

@ -142,6 +142,48 @@ const storyApi = {
return data.categories return data.categories
}) })
}, },
/**
* Call `addStoryTag(input)` mutation, returns the new tag list.
*
* @param {{id:string, tag:Tag}} input
* @returns {Promise<{tags: Tag[]}>}
*/
addTag(input) {
return query(`
mutation AddStoryTag($input: StoryTagAddInput!) {
addStoryTag(input:$input) {
tags {
kind
name
}
}
}
`, {input}, {permissions: ["member", "story.edit"]}).then(({addStoryTag}) => {
return addStoryTag
})
},
/**
* Call `removeStoryTag(input)` mutation, returns the new tag list.
*
* @param {{id:string, tag:Tag}} input
* @returns {Promise<{tags: Tag[]}>}
*/
removeTag(input) {
return query(`
mutation RemoveStoryTag($input: StoryTagRemoveInput!) {
removeStoryTag(input:$input) {
tags {
kind
name
}
}
}
`, {input}, {permissions: ["member", "story.edit"]}).then(({removeStoryTag}) => {
return removeStoryTag
})
},
} }
module.exports = {storyApi, Story} module.exports = {storyApi, Story}

2
rpdata/api/Tag.js

@ -26,7 +26,7 @@ const tagApi = {
} }
} }
`, {}).then(({tags}) => { `, {}).then(({tags}) => {
return tags.map(d => new Tag(d))
return tags.map(d => new Tag(d.kind, d.name))
}) })
}, },
} }

Loading…
Cancel
Save