diff --git a/marko/components/compact-list-item/index.marko b/marko/components/compact-list-item/index.marko
new file mode 100644
index 0000000..45892c2
--- /dev/null
+++ b/marko/components/compact-list-item/index.marko
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/marko/components/compact-list-item/style.less b/marko/components/compact-list-item/style.less
new file mode 100644
index 0000000..19a669e
--- /dev/null
+++ b/marko/components/compact-list-item/style.less
@@ -0,0 +1,5 @@
+tr.compact-list-item {
+ user-select: none;
+ outline: 0.05px dotted;
+ vertical-align: middle;
+}
\ No newline at end of file
diff --git a/marko/components/compact-list-options/index.marko b/marko/components/compact-list-options/index.marko
new file mode 100644
index 0000000..6fa49e1
--- /dev/null
+++ b/marko/components/compact-list-options/index.marko
@@ -0,0 +1,5 @@
+
+
+ |
\ No newline at end of file
diff --git a/marko/components/compact-list-options/style.less b/marko/components/compact-list-options/style.less
new file mode 100644
index 0000000..5072def
--- /dev/null
+++ b/marko/components/compact-list-options/style.less
@@ -0,0 +1,28 @@
+td.compact-list-options {
+ width: 20%;
+ padding: 0 0.5ch;
+
+
+ div.content {
+ font-size: 1em;
+ text-align: right;
+
+ a {
+ display: inline-block;
+ margin-right: 1ch;
+ opacity: 0.5;
+ }
+ a:hover {
+ opacity: 1;
+ cursor: pointer;
+ }
+ }
+}
+
+td.compact-list-options.short {
+ width: 10%;
+}
+
+td.compact-list-options.long {
+ width: 30%;
+}
\ No newline at end of file
diff --git a/marko/components/compact-list-property/index.marko b/marko/components/compact-list-property/index.marko
new file mode 100644
index 0000000..31505f5
--- /dev/null
+++ b/marko/components/compact-list-property/index.marko
@@ -0,0 +1,4 @@
+
+
+ ${input.value}
+ |
\ No newline at end of file
diff --git a/marko/components/compact-list-property/style.less b/marko/components/compact-list-property/style.less
new file mode 100644
index 0000000..3e42a79
--- /dev/null
+++ b/marko/components/compact-list-property/style.less
@@ -0,0 +1,24 @@
+td.compact-list-property {
+ width: 20%;
+ padding: 0 0.5ch;
+
+ div.label {
+ font-size: 0.5em;
+ line-height: 1em;
+ padding-top: 0.5em;
+ }
+
+ div.value {
+ font-size: 0.75em;
+ padding-bottom: 0.25em;
+ user-select: text;
+ }
+}
+
+td.compact-list-property.short {
+ width: 10%;
+}
+
+td.compact-list-property.long {
+ width: 30%;
+}
\ No newline at end of file
diff --git a/marko/components/compact-list/index.marko b/marko/components/compact-list/index.marko
new file mode 100644
index 0000000..fd42a7f
--- /dev/null
+++ b/marko/components/compact-list/index.marko
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/marko/components/compact-list/style.less b/marko/components/compact-list/style.less
new file mode 100644
index 0000000..12dcee1
--- /dev/null
+++ b/marko/components/compact-list/style.less
@@ -0,0 +1,13 @@
+table.compact-list {
+ padding-top: 0.5em;
+ padding-bottom: 3em;
+
+ width: 90%;
+ max-width: 100ch;
+ padding-right: 1em;
+
+ margin: auto;
+
+ border-collapse: separate;
+ border-spacing: 0 0.5em;
+}
\ No newline at end of file
diff --git a/marko/components/layout-navbar/index.marko b/marko/components/layout-navbar/index.marko
index a7efd3c..14f65d5 100644
--- a/marko/components/layout-navbar/index.marko
+++ b/marko/components/layout-navbar/index.marko
@@ -6,5 +6,6 @@
\ No newline at end of file
diff --git a/marko/components/menu-link/style.less b/marko/components/menu-link/style.less
index 875c6cc..72d7c2d 100644
--- a/marko/components/menu-link/style.less
+++ b/marko/components/menu-link/style.less
@@ -2,6 +2,7 @@ div.menu-link {
width: 100%;
padding-left: 0.5ch;
font-size: 1.1em;
+ padding-bottom: 0.125em;
white-space: nowrap;
@@ -10,7 +11,7 @@ div.menu-link {
div.icon {
display: inline-block;
width: 3ch;
- padding: 0.15em 0.125em;
+ padding: 0 0.125em;
text-align: center;
vertical-align: middle;
}
diff --git a/marko/global/colors.less b/marko/global/colors.less
index c6ead63..893f91c 100644
--- a/marko/global/colors.less
+++ b/marko/global/colors.less
@@ -66,6 +66,32 @@ body.theme-logs {
}
}
+body.theme-data {
+ .color-menu {
+ color: #775;
+ }
+ .menu-link.selected {
+ background-color: rgba(204, 204, 34, 0.226);
+ }
+ .menu-link:hover, .Menu > .head-menu > a:hover {
+ background-color: rgba(204, 204, 34, 0.25);
+ }
+ .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: #CC2;
+ }
+ .color-text, input, textarea, select {
+ color: #BBB;
+ }
+ .color-highlight-dark {
+ background-color: rgba(18, 8, 0, 0.50);
+ }
+ .color-highlight-primary {
+ background-color: rgba(204, 204, 34, 0.25);
+ color: #CC2;
+ }
+}
+
+
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 {
background-color: rgba(221, 170, 17, 0.125);
color: #DA1;
@@ -90,6 +116,14 @@ div.markdown-content .green, body.theme-mapp div.markdown-content blockquote, bo
color: #1F8;
}
}
+div.markdown-content .yellow, body.theme-data div.markdown-content blockquote, body.theme-data div.markdown-content table, body.theme-data div.markdown-content pre {
+ background-color: rgba(204, 204, 34, 0.25);
+ color: #CC2;
+
+ h1, h2, h3 {
+ color: #CC2;
+ }
+}
/* Tag colors */
.color-tag-event {
diff --git a/marko/page/data/characters.marko b/marko/page/data/characters.marko
new file mode 100644
index 0000000..8fab7d2
--- /dev/null
+++ b/marko/page/data/characters.marko
@@ -0,0 +1,6 @@
+
+ <@body>
+
+
+ @body>
+
diff --git a/marko/page/data/components/add-character-modal/component.js b/marko/page/data/components/add-character-modal/component.js
new file mode 100644
index 0000000..44c59ac
--- /dev/null
+++ b/marko/page/data/components/add-character-modal/component.js
@@ -0,0 +1,69 @@
+const moment = require("moment")
+
+const {charactersApi} = require("../../../../../rpdata/api/Character")
+
+module.exports = class {
+ onCreate(input) {
+ this.state = {
+ error: null,
+ loading: false,
+ values: {
+ name: "",
+ nick: "",
+ shortName: "",
+ author: "",
+ description: "",
+ }
+ }
+ }
+
+ onInput(input) {
+ this.state.values = Object.assign({}, this.state.values, {author: input.user.name})
+ }
+
+ change(key, ev) {
+ this.state.values[key] = ev.target.value
+ }
+
+ open() {
+
+ }
+
+ close() {
+ this.emit("close")
+ }
+
+ save() {
+ if (this.state.loading) {
+ return
+ }
+
+ const values = Object.assign({}, this.state.values)
+ if (values.name.length < 2) {
+ this.state.error = "Name is too short (min: 2)"
+ return
+ }
+ if (values.author == "") {
+ delete values.author
+ }
+
+ this.state.loading = true
+ charactersApi.add(this.state.values).then((res) => {
+ this.emit("added", res)
+ this.emit("close")
+ this.state.values = {
+ name: "",
+ nick: "",
+ shortName: "",
+ author: this.input.user.name,
+ description: "",
+ }
+ }).catch(errs => {
+ console.warn("Failed to edit:", errs)
+
+ this.state.error = "Failed to edit: " + errs[0].message
+ }).then(() => {
+ this.state.loading = false
+ })
+ }
+}
\ No newline at end of file
diff --git a/marko/page/data/components/add-character-modal/index.marko b/marko/page/data/components/add-character-modal/index.marko
new file mode 100644
index 0000000..3b29470
--- /dev/null
+++ b/marko/page/data/components/add-character-modal/index.marko
@@ -0,0 +1,22 @@
+
+ Add Character
+
+ ${state.error}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/marko/page/data/components/character-list/index.marko b/marko/page/data/components/character-list/index.marko
new file mode 100644
index 0000000..e2943e1
--- /dev/null
+++ b/marko/page/data/components/character-list/index.marko
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/marko/page/data/components/character/component.js b/marko/page/data/components/character/component.js
new file mode 100644
index 0000000..fee3c0b
--- /dev/null
+++ b/marko/page/data/components/character/component.js
@@ -0,0 +1,20 @@
+module.exports = class {
+ onCreate() {
+ this.state = {
+ modal: null,
+ deleted: false,
+ }
+ }
+
+ open(modal) {
+ this.state.modal = modal
+ }
+
+ close() {
+ this.state.modal = null
+ }
+
+ removed() {
+ this.state.deleted = true
+ }
+}
\ No newline at end of file
diff --git a/marko/page/data/components/character/index.marko b/marko/page/data/components/character/index.marko
new file mode 100644
index 0000000..da926c8
--- /dev/null
+++ b/marko/page/data/components/character/index.marko
@@ -0,0 +1,12 @@
+
+
+ 0) />
+
+
+
+ Edit
+ Remove
+
+
+
+
\ No newline at end of file
diff --git a/marko/page/data/components/characters-page/component.js b/marko/page/data/components/characters-page/component.js
new file mode 100644
index 0000000..5bc62d3
--- /dev/null
+++ b/marko/page/data/components/characters-page/component.js
@@ -0,0 +1,36 @@
+module.exports = class {
+ onCreate(input) {
+ this.state = {
+ characters: input.characters,
+ modal: null,
+ }
+ }
+
+ open(modal) {
+ this.state.modal = modal
+ }
+
+ close() {
+ this.state.modal = null
+ }
+
+ characterRemoved({id}) {
+ this.state.characters = this.state.characters.filter(c => c.id !== id)
+ }
+
+ characterEdited(character, patch) {
+ const characters = this.state.characters.slice()
+ const index = characters.findIndex(c => c.id === character.id)
+ if (index === -1) {
+ return
+ }
+
+ characters[index] = Object.assign(characters[index], patch)
+
+ this.state.characters = characters
+ }
+
+ characterAdded(character) {
+ this.state.characters = this.state.characters.concat(character)
+ }
+}
\ No newline at end of file
diff --git a/marko/page/data/components/characters-page/index.marko b/marko/page/data/components/characters-page/index.marko
new file mode 100644
index 0000000..5040783
--- /dev/null
+++ b/marko/page/data/components/characters-page/index.marko
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/marko/page/data/components/data-menu/index.marko b/marko/page/data/components/data-menu/index.marko
new file mode 100644
index 0000000..05c07da
--- /dev/null
+++ b/marko/page/data/components/data-menu/index.marko
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/marko/page/data/components/edit-character-modal/component.js b/marko/page/data/components/edit-character-modal/component.js
new file mode 100644
index 0000000..79276bf
--- /dev/null
+++ b/marko/page/data/components/edit-character-modal/component.js
@@ -0,0 +1,57 @@
+const moment = require("moment")
+
+const {charactersApi} = require("../../../../../rpdata/api/Character")
+
+module.exports = class {
+ onCreate(input) {
+ this.state = {
+ error: null,
+ loading: false,
+ values: {
+ name: "",
+ shortName: "",
+ description: "",
+ }
+ }
+ }
+
+ change(key, ev) {
+ this.state.values[key] = ev.target.value
+ }
+
+ onInput(input) {
+ this.state.values = {
+ name: input.character.name,
+ shortName: input.character.shortName,
+ description: input.character.description,
+ }
+ }
+
+ open() {
+
+ }
+
+ close() {
+ this.emit("close")
+ }
+
+ save() {
+ if (this.state.loading) {
+ return
+ }
+
+ const input = Object.assign({id: this.input.character.id}, this.state.values)
+
+ this.state.loading = true
+ charactersApi.edit(input).then((res) => {
+ this.emit("edited", res)
+ 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
+ })
+ }
+}
\ No newline at end of file
diff --git a/marko/page/data/components/edit-character-modal/index.marko b/marko/page/data/components/edit-character-modal/index.marko
new file mode 100644
index 0000000..ca4df44
--- /dev/null
+++ b/marko/page/data/components/edit-character-modal/index.marko
@@ -0,0 +1,16 @@
+
+ Edit Character
+
+ ${state.error}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/marko/page/data/components/remove-character-modal/component.js b/marko/page/data/components/remove-character-modal/component.js
new file mode 100644
index 0000000..d6028a0
--- /dev/null
+++ b/marko/page/data/components/remove-character-modal/component.js
@@ -0,0 +1,42 @@
+const moment = require("moment")
+
+const {charactersApi} = require("../../../../../rpdata/api/Character")
+
+module.exports = class {
+ onCreate(input) {
+ this.state = {
+ error: null,
+ loading: false,
+ }
+ }
+
+ change(key, ev) {
+ this.state.values[key] = ev.target.value
+ }
+
+ open() {
+
+ }
+
+ close() {
+ this.emit("close")
+ }
+
+ doIt() {
+ if (this.state.loading) {
+ return
+ }
+
+ this.state.loading = true
+ charactersApi.remove({id: this.input.character.id}).then(() => {
+ this.emit("removed")
+ this.emit("close")
+ }).catch(errs => {
+ console.warn("Failed to delete:", errs)
+
+ this.state.error = "Failed to delete: " + errs[0].message
+ }).then(() => {
+ this.state.loading = false
+ })
+ }
+}
\ No newline at end of file
diff --git a/marko/page/data/components/remove-character-modal/index.marko b/marko/page/data/components/remove-character-modal/index.marko
new file mode 100644
index 0000000..8c0752c
--- /dev/null
+++ b/marko/page/data/components/remove-character-modal/index.marko
@@ -0,0 +1,9 @@
+
+ Remove ${input.character.name}
+
+ ${state.error}
+
+ This is irreversible!
+
+
+
\ No newline at end of file
diff --git a/routes/data/characters.js b/routes/data/characters.js
new file mode 100644
index 0000000..b74eb30
--- /dev/null
+++ b/routes/data/characters.js
@@ -0,0 +1,15 @@
+const express = require("express")
+const router = express.Router()
+
+const {charactersApi} = require("../../rpdata/api/Character")
+
+const charactersTemplate = require("../../marko/page/data/characters.marko")
+
+router.get("/", async(req, res) => {
+ res.markoAsync(charactersTemplate, {
+ characters: charactersApi.list(),
+ selected: {characters: true},
+ })
+})
+
+module.exports = router
diff --git a/routes/data/index.js b/routes/data/index.js
new file mode 100644
index 0000000..d8ea54d
--- /dev/null
+++ b/routes/data/index.js
@@ -0,0 +1,8 @@
+const express = require("express")
+const router = express.Router()
+
+router.get("/", async(req, res) => {
+ return res.redirect("/data/characters/")
+})
+
+module.exports = router
diff --git a/rpdata/api/Character.js b/rpdata/api/Character.js
index 50b9ecf..c320edd 100644
--- a/rpdata/api/Character.js
+++ b/rpdata/api/Character.js
@@ -27,4 +27,98 @@ class Character {
}
}
-module.exports = { Character }
\ No newline at end of file
+class ChracterAPI {
+ /**
+ * Call `characters(filter)` query
+ *
+ * @param {{ids:string[], nicks:string[], names:string[], author:string, search:string, logged:boolean}} filter
+ * @returns {Promise}
+ */
+ list(filter = {}) {
+ if (filter.earliestFictionalDate != null && typeof(filter.earliestFictionalDate) !== "string") {
+ filter.earliestFictionalDate = filter.earliestFictionalDate.toISOString()
+ }
+ if (filter.latestFictionalDate != null && typeof(filter.latestFictionalDate) !== "string") {
+ filter.latestFictionalDate = filter.latestFictionalDate.toISOString()
+ }
+
+ return query(`
+ query ListCharacters($filter: CharactersFilter) {
+ characters(filter: $filter) {
+ id
+ nicks
+ author
+ name
+ shortName
+ description
+ }
+ }
+ `, {filter}).then(({characters}) => {
+ return characters.map(d => Character.fromData(d))
+ })
+ }
+
+ /**
+ * Call `addCharacter(input)` mutation, returns addable fields.
+ *
+ * @param {{id:string, name:string, shortName:string, author:string, description:string}} input
+ * @returns {Promise}
+ */
+ add(input) {
+ return query(`
+ mutation AddCharacter($input: CharacterAddInput!) {
+ addCharacter(input: $input) {
+ id
+ nicks
+ author
+ name
+ shortName
+ description
+ }
+ }
+ `, {input}, {permissions: ["member", "character.add"]}).then(({addCharacter}) => {
+ return Character.fromData(addCharacter)
+ })
+ }
+
+ /**
+ * Call `editCharacter(input)` mutation, returns editable fields.
+ *
+ * @param {{id:string, name:string, shortName:string, description:string}} input
+ * @returns {Promise<{id:string, name:string, shortName:string, description:string}>}
+ */
+ edit(input) {
+ return query(`
+ mutation EditCharacters($input: CharacterEditInput!) {
+ editCharacter(input: $input) {
+ id
+ name
+ shortName
+ description
+ }
+ }
+ `, {input}, {permissions: ["member", "character.edit"]}).then(({editCharacter}) => {
+ return editCharacter
+ })
+ }
+
+ /**
+ * Call `removeCharacter(input)` mutation
+ *
+ * @param {{id:string}} input
+ * @returns {Promise<{id:string}>}
+ */
+ remove(input) {
+ return query(`
+ mutation RemoveCharacters($input: CharacterRemoveInput!) {
+ removeCharacter(input: $input) {
+ id
+ }
+ }
+ `, {input}, {permissions: ["member", "character.remove"]}).then(({removeCharacter}) => {
+ return removeCharacter
+ })
+ }
+}
+
+module.exports = { Character, charactersApi: new ChracterAPI }
\ No newline at end of file
diff --git a/server.js b/server.js
index 1f10bc2..fbb6bce 100644
--- a/server.js
+++ b/server.js
@@ -79,7 +79,9 @@ app.use("/story/tag-list/", require("./routes/story/tag-list"))
app.use("/story/:id(S[0-9a-z]{15})/", require("./routes/story-content"))
app.use("/logs/", require("./routes/logs"))
app.use("/logs/:id([0-9]{4}-[0-1][0-9]-[0-3][0-9]_[0-9]{9}_[A-Za-z\,\']{2,})/", require("./routes/logs-content"))
-// 2018-03-10_035344747_Miner'sRespite
+app.use("/data/", require("./routes/data"))
+app.use("/data/characters/", require("./routes/data/characters"))
+
// Entry point
app.get("/", function(req, res) {
res.redirect("/story/")