summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--demo/components/chat.js17
-rw-r--r--demo/components/like-button.js36
-rw-r--r--demo/index.html13
-rw-r--r--graffiti.js4
-rw-r--r--src/array.js94
6 files changed, 101 insertions, 65 deletions
diff --git a/README.md b/README.md
index 199b871..05412b9 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,4 @@
This library enables any webpage to interface with the [Graffiti server](https://github.com/graffiti-garden/graffiti-server). It also includes a plugin that extends it to operate with the [Vue.js framework](https://vuejs.org/).
-Check out the live [demo](https://graffiti.garden/graffiti-js/demo) of the library and plugin in action. The demo's source code is in the `/demo` folder.
+Check out the live [demo](https://graffiti.garden/graffiti-js/demo) of the library and plugin in action. The demo's source code is in the [`/demo`](https://github.com/graffiti-garden/graffiti-js/tree/main/demo) folder.
diff --git a/demo/components/chat.js b/demo/components/chat.js
index 86d8fe7..08b478b 100644
--- a/demo/components/chat.js
+++ b/demo/components/chat.js
@@ -1,13 +1,13 @@
import { Name } from './name.js'
+import LikeButton from './like-button.js'
export default {
- components: { Name },
-
- props: ['tags'],
+ components: { Name, LikeButton },
data: ()=> ({
- message: ''
+ message: '',
+ channel: 'demo'
}),
methods: {
@@ -25,20 +25,23 @@ export default {
this.$graffitiUpdate({
message: this.message,
timestamp: Date.now(),
- _tags: this.tags
+ _tags: [this.channel]
})
this.message = ''
}
},
template: `
- <graffiti-objects :tags="tags" v-slot="{objects}">
+ Chat Channel: <input v-model="channel"/>
+
+ <graffiti-objects :tags="[channel]" v-slot="{objects}">
<ul v-for="object in messageObjects(objects)">
<li>
<em><Name :of="object._by"/></em>:
-
{{ object.message }}
+ <LikeButton :messageID="object._id" />
+
<template v-if="object._by==$graffitiMyID">
<button @click="object.message+='!!';object._update()">
‼️
diff --git a/demo/components/like-button.js b/demo/components/like-button.js
new file mode 100644
index 0000000..1c9649a
--- /dev/null
+++ b/demo/components/like-button.js
@@ -0,0 +1,36 @@
+export default {
+
+ props: ['messageID'],
+
+ methods: {
+ likeObjects(objects) {
+ return objects.filter(o=>
+ 'like' in o &&
+ 'timestamp' in o &&
+ o.like == this.messageID &&
+ typeof o.timestamp == 'number')
+
+ },
+
+ toggleLike(objects) {
+ const myLikes = this.likeObjects(objects).mine
+ if (myLikes.length) {
+ myLikes.removeMine()
+ } else {
+ this.$graffitiUpdate({
+ like: this.messageID,
+ timestamp: Date.now(),
+ _tags: [this.messageID]
+ })
+ }
+ }
+ },
+
+ template: `
+ <graffiti-objects :tags="[messageID]" v-slot="{objects}">
+ <button @click="toggleLike(objects)" :class="likeObjects(objects).mine.length?'button-primary':''">
+ 👍 {{ likeObjects(objects).length }}
+ </button>
+ </graffiti-objects>`
+}
+
diff --git a/demo/index.html b/demo/index.html
index 06e83d3..b409ee4 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -9,8 +9,9 @@
<script async src="https://ga.jspm.io/npm:es-module-shims@1.6.2/dist/es-module-shims.js"></script>
<script type="importmap">{ "imports": {
"vue": "https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.45/vue.esm-browser.prod.min.js",
- "graffiti-vue": "https://graffiti.garden/graffiti-js/plugins/vue/plugin.js"
+ "graffiti-vue": "../plugins/vue/plugin.js"
}}</script>
+ <!--"graffiti-vue": "https://graffiti.garden/graffiti-js/plugins/vue/plugin.js"-->
<script type="module">
import { createApp } from 'vue'
import { Name, SetMyName } from './components/name.js'
@@ -28,10 +29,6 @@
<h1>Graffiti Demo</h1>
- <p>
- This demonstrates use of the <a href="">graffiti-js</a> library Vue interface.
- </p>
-
<h2>Connection Status</h2>
<p>
@@ -60,13 +57,13 @@
<h2>Chatting</h2>
- <chat :tags="['demo']"></chat>
+ <chat></chat>
<h2>Moderation</h2>
- <h2>Context</h2>
-
<h2>Private Messages</h2>
+
+ <h2>Tagging</h2>
</body>
</html>
diff --git a/graffiti.js b/graffiti.js
index 3cdc7fc..4100e08 100644
--- a/graffiti.js
+++ b/graffiti.js
@@ -13,6 +13,7 @@ export default class {
this.open = false
this.eventTarget = new EventTarget()
this.tagMap = {}
+ this.GraffitiArray = GraffitiArray(this)
this.#initialize()
}
@@ -144,6 +145,7 @@ export default class {
// Add properties to the object
// so it can be updated and removed
// without the collection
+ Object.defineProperty(object, '_id', { value: this.#objectUUID(object) })
Object.defineProperty(object, '_update', { value: ()=>this.update(object) })
Object.defineProperty(object, '_remove', { value: ()=>this.remove(object) })
objectMap[uuid] = object
@@ -246,7 +248,7 @@ export default class {
...tags.map(tag=> this.tagMap[tag].objectMap))
// Return an array wrapped with graffiti functions
- return new GraffitiArray(this, ...Object.values(combinedMaps))
+ return new this.GraffitiArray(...Object.values(combinedMaps))
}
async subscribe(...tags) {
diff --git a/src/array.js b/src/array.js
index f0c251f..c53113e 100644
--- a/src/array.js
+++ b/src/array.js
@@ -1,64 +1,62 @@
// Extend the array class to expose update
// functionality, plus provide some
// useful helper methods
-export default class GraffitiArray extends Array {
+export default function(graffiti) {
- constructor(graffiti, ...elements) {
- super(...elements)
- this.graffiti = graffiti
- }
+ return class GraffitiArray extends Array {
- get mine() {
- return this.filter(o=> o._by==this.graffiti.myID)
- }
+ get mine() {
+ return this.filter(o=> o._by==graffiti.myID)
+ }
- get notMine() {
- return this.filter(o=> o._by!=this.graffiti.myID)
- }
+ get notMine() {
+ return this.filter(o=> o._by!=graffiti.myID)
+ }
- get authors() {
- return [...new Set(this.map(o=> o._by))]
- }
+ get authors() {
+ return [...new Set(this.map(o=> o._by))]
+ }
- async removeMine() {
- await Promise.all(
- this.mine.map(async o=> await o._remove()))
- }
+ async removeMine() {
+ await Promise.all(
+ this.mine.map(async o=> await o._remove()))
+ }
- #getProperty(obj, propertyPath) {
- // Split it up by periods
- propertyPath = propertyPath.match(/([^\.]+)/g)
- // Traverse down the path tree
- for (const property of propertyPath) {
- obj = obj[property]
+ #getProperty(obj, propertyPath) {
+ // Split it up by periods
+ propertyPath = propertyPath.match(/([^\.]+)/g)
+ // Traverse down the path tree
+ for (const property of propertyPath) {
+ obj = obj[property]
+ }
+ return obj
}
- return obj
- }
- sortBy(propertyPath) {
+ sortBy(propertyPath) {
- const sortOrder = propertyPath[0] == '-'? -1 : 1
- if (sortOrder < 0) propertyPath = propertyPath.substring(1)
+ const sortOrder = propertyPath[0] == '-'? -1 : 1
+ if (sortOrder < 0) propertyPath = propertyPath.substring(1)
- return this.sort((a, b)=> {
- const propertyA = this.#getProperty(a, propertyPath)
- const propertyB = this.#getProperty(b, propertyPath)
- return sortOrder * (
- propertyA < propertyB? -1 :
- propertyA > propertyB? 1 : 0 )
- })
- }
+ return this.sort((a, b)=> {
+ const propertyA = this.#getProperty(a, propertyPath)
+ const propertyB = this.#getProperty(b, propertyPath)
+ return sortOrder * (
+ propertyA < propertyB? -1 :
+ propertyA > propertyB? 1 : 0 )
+ })
+ }
- groupBy(propertyPath) {
- return this.reduce((chain, obj)=> {
- const property = this.#getProperty(obj, propertyPath)
- if (property in chain) {
- chain[property].push(obj)
- } else {
- chain[property] = new GraffitiArray(this.graffiti, obj)
- }
- return chain
- }, {})
- }
+ groupBy(propertyPath) {
+ return this.reduce((chain, obj)=> {
+ const property = this.#getProperty(obj, propertyPath)
+ if (property in chain) {
+ chain[property].push(obj)
+ } else {
+ chain[property] = new GraffitiArray(obj)
+ }
+ return chain
+ }, {})
+ }
+ }
}