diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | demo/components/chat.js | 17 | ||||
-rw-r--r-- | demo/components/like-button.js | 36 | ||||
-rw-r--r-- | demo/index.html | 13 | ||||
-rw-r--r-- | graffiti.js | 4 | ||||
-rw-r--r-- | src/array.js | 94 |
6 files changed, 101 insertions, 65 deletions
@@ -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 + }, {}) + } + } } |