├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── lib ├── find.js ├── scope.js └── tag.js ├── package.json ├── spec ├── support │ └── jasmine.json └── tag.spec.js ├── tag.min.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # https://git-scm.com/docs/gitignore 2 | # https://help.github.com/articles/ignoring-files 3 | # Example .gitignore files: https://github.com/github/gitignore 4 | /node_modules/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.1' 4 | - '4.0' 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Labelmaker 2 | ========== 3 | Organize your data by tags 4 | 5 | > Labelmaker is made for [gunDB](https://github.com/amark/gun/) 6 | 7 | ## What it is 8 | If you've found yourself wanting a tagging system for gun, this is what you've been looking for. Labelmaker allows you to organize and retrieve your data by any of it's associated tags/groups. 9 | 10 | > **Note:** you cannot tag primitives. For that, use `.key`. 11 | 12 | ## How to use it 13 | **Node.js** 14 | 15 | ```bash 16 | npm install labelmaker 17 | ``` 18 | to install it, then you can require it from your app: 19 | ```javascript 20 | var labler = require('labelmaker') 21 | ``` 22 | 23 | Labelmaker works with both gun version `0.2` and `0.3`. Since it doesn't assume your version, you need to tell it explicitly by giving it the constructor you're using: 24 | 25 | ```javascript 26 | var Gun = require('gun') 27 | labler(Gun) 28 | // You now have tag support! 29 | ``` 30 | 31 | **Browser** 32 | 33 | For the browser, it's much simpler, since your version of gun is exported as a global. Just include it as a script tag, and labelmaker takes care of the rest. 34 | 35 | ```html 36 | 37 | 38 | ``` 39 | ### API 40 | Two methods are exposed for your gun instances: 41 | 42 | - `.tag` 43 | - `.tagged` 44 | 45 | #### gun.tag(name[, name...]) 46 | You can pass `.tag` multiple names to index a node under. When called, it will try to read out your current context, index it under each tag, and then place each tag under a master list of every tag ever used. 47 | 48 | ```javascript 49 | gun.put({ 50 | name: 'Bob', 51 | profession: 'developer' 52 | }).tag( 53 | 'members', 54 | 'javascript developers', 55 | 'gunDB developers', 56 | 'examples' 57 | ) 58 | ``` 59 | 60 | #### gun.tagged(name[, callback]) 61 | Iterate over the list of nodes under that tag name. 62 | ```javascript 63 | gun.tagged('members', function (member, ID) { 64 | view.show.user(member, ID) 65 | }) 66 | ``` 67 | 68 | ### Roadmap 69 | Here are some goals for labelmaker: 70 | 71 | - option to disable implicit mapping 72 | - ability to remove a node from a tag 73 | 74 | Contributions are welcome! 75 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true*/ 2 | 'use strict'; 3 | 4 | var tagger = require('./lib/tag'); 5 | var finder = require('./lib/find'); 6 | 7 | 8 | function attach(Gun) { 9 | Gun.chain.init = Gun.chain.init || Gun.chain.set; 10 | tagger(Gun); 11 | finder(Gun); 12 | 13 | return Gun; 14 | } 15 | 16 | if (typeof window !== 'undefined' && window.Gun) { 17 | attach(window.Gun); 18 | } 19 | 20 | module.exports = attach; 21 | -------------------------------------------------------------------------------- /lib/find.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true*/ 2 | 'use strict'; 3 | 4 | var scope = require('./scope'); 5 | 6 | module.exports = function find(Gun) { 7 | 8 | Gun.chain.tagged = function (name, cb) { 9 | if (arguments.length === 0) { 10 | return this.get(scope + 'all tags'); 11 | } 12 | if (!name && typeof name !== 'string') { 13 | return this; 14 | } 15 | return this.get(scope + name).map(cb, true); 16 | }; 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /lib/scope.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true*/ 2 | module.exports = 'xWVyuulvIIHyTO08nHHWrqta:'; 3 | -------------------------------------------------------------------------------- /lib/tag.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true, nomen: true*/ 2 | 'use strict'; 3 | 4 | var scope = require('./scope'); 5 | 6 | // check compatibility 7 | function invalid(value) { 8 | if (typeof value !== 'object') { 9 | console.log('Only objects can be tagged'); 10 | return true; 11 | } 12 | } 13 | 14 | /* 15 | Keep a master collection 16 | of every tag used. 17 | */ 18 | function pushTag(gun, tag) { 19 | gun.get(scope + tag).val(function (group) { 20 | gun.get(scope + 'all tags').init().path(tag).put(group); 21 | }); 22 | } 23 | 24 | /* 25 | Take a list of tags and send 26 | them through, one at a time. 27 | */ 28 | function serialize(gun, args) { 29 | // turn them into an array 30 | args = Array.prototype.slice.call(args); 31 | 32 | args.forEach(function (tag) { 33 | // run each tag 34 | gun.tag(tag); 35 | }); 36 | 37 | return gun; 38 | } 39 | 40 | module.exports = function tag(Gun) { 41 | 42 | Gun.chain.tag = function (tag) { 43 | 44 | if (arguments.length !== 1) { 45 | return serialize(this, arguments); 46 | } 47 | 48 | return this.val(function (obj) { 49 | // filter non-objects 50 | if (invalid(obj)) { 51 | return this; 52 | } 53 | 54 | // place the object under the tag 55 | this.get(scope + tag).init() 56 | .path(obj._['#']).put(obj); 57 | 58 | 59 | // place that tag under a master list of tags 60 | pushTag(this, tag); 61 | 62 | }); 63 | }; 64 | }; 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "labelmaker", 3 | "version": "0.1.0", 4 | "description": "Bring tags to gunDB", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jasmine", 8 | "build": "webpack --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/PsychoLlama/labelmaker.git" 13 | }, 14 | "keywords": [ 15 | "gun", 16 | "gunDB", 17 | "tags", 18 | "tagging", 19 | "tag", 20 | "group", 21 | "category", 22 | "label", 23 | "labels", 24 | "categories", 25 | "groups" 26 | ], 27 | "author": "Jesse Gibson (http://techllama.com)", 28 | "license": "Zlib OR MIT OR Apache-2.0", 29 | "devDependencies": { 30 | "gun": "^0.3.1", 31 | "jasmine": "^2.4.1", 32 | "webpack": "^1.12.11" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /spec/tag.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, expect, jasmine, beforeEach, afterAll*/ 2 | /*jslint node: true, nomen: true*/ 3 | 'use strict'; 4 | 5 | /* 6 | Warning: 7 | These tests are written for 8 | gun@v0.3, and may not work 9 | for older versions. 10 | */ 11 | 12 | // Squelch gun peer connection warnings 13 | console.log = (function () { 14 | var log = console.log; 15 | return function (name) { 16 | 17 | if (name && name.match(/Warning!/i)) { 18 | return; 19 | } 20 | 21 | log.apply(console, arguments); 22 | }; 23 | }()); 24 | 25 | 26 | // set the default timeout 27 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; 28 | 29 | var tagger = require('../index'); 30 | var scope = require('../lib/scope'); 31 | var Gun = require('gun'); 32 | tagger(Gun); 33 | 34 | describe('The tagger function', function () { 35 | var gun, tag; 36 | 37 | beforeEach(function () { 38 | // generate a new tag each test 39 | tag = Gun.text.random(10); 40 | 41 | // make a new gun instance 42 | gun = new Gun({ 43 | file: 'tag.spec-data.json' 44 | }); 45 | }); 46 | 47 | it('should export a "tag" method', function () { 48 | expect(Gun.chain.tag).toEqual(jasmine.any(Function)); 49 | }); 50 | 51 | it('should export a "tagged" method', function () { 52 | expect(Gun.chain.tagged).toEqual(jasmine.any(Function)); 53 | }); 54 | 55 | 56 | describe('tag method', function () { 57 | 58 | it('should index data', function (done) { 59 | gun.put({ 60 | name: 'success' 61 | }).tag(tag).get(scope + tag).map().val(done); 62 | }); 63 | 64 | it('should not pseudo-merge nodes', function (done) { 65 | gun.put({ 66 | data: true 67 | }).tag(tag).get(scope + tag).map().val(function (val) { 68 | expect(val.name).toBe(undefined); 69 | expect(val.data).toBe(true); 70 | done(); 71 | }); 72 | }); 73 | 74 | it('should be able to take multiple tags', function (done) { 75 | gun.put({ 76 | test: true 77 | }).tag(tag, 'custom'); 78 | gun.tagged('custom').val(done); 79 | }); 80 | 81 | }); 82 | 83 | describe('tagged method', function () { 84 | 85 | it('should be able to find tags', function (done) { 86 | gun.put({ 87 | data: true 88 | }).tag(tag); 89 | gun.tagged(tag).val(done); 90 | }); 91 | 92 | it('should be able to find every tag', function (done) { 93 | gun.tagged().map().val(done); 94 | }); 95 | 96 | }); 97 | 98 | }); 99 | 100 | afterAll(function () { 101 | require('fs').unlink(__dirname + '/../tag.spec-data.json'); 102 | }); 103 | -------------------------------------------------------------------------------- /tag.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function n(e){if(i[e])return i[e].exports;var o=i[e]={exports:{},id:e,loaded:!1};return t[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var i={};return n.m=t,n.c=i,n.p="",n(0)}([function(t,n,i){"use strict";function e(t){return t.chain.init=t.chain.init||t.chain.set,o(t),r(t),t}var o=i(1),r=i(3);"undefined"!=typeof window&&window.Gun&&e(window.Gun),t.exports=e},function(t,n,i){"use strict";function e(t){return"object"!=typeof t?(console.log("Only objects can be tagged"),!0):void 0}function o(t,n){t.get(u+n).val(function(i){t.get(u+"all tags").init().path(n).put(i)})}function r(t,n){return n=Array.prototype.slice.call(n),n.forEach(function(n){t.tag(n)}),t}var u=i(2);t.exports=function(t){t.chain.tag=function(t){return 1!==arguments.length?r(this,arguments):this.val(function(n){return e(n)?this:(this.get(u+t).init().path(n._["#"]).put(n),void o(this,t))})}}},function(t,n){t.exports="xWVyuulvIIHyTO08nHHWrqta:"},function(t,n,i){"use strict";var e=i(2);t.exports=function(t){t.chain.tagged=function(t,n){return 0===arguments.length?this.get(e+"all tags"):t||"string"==typeof t?this.get(e+t).map(n,!0):this}}}]); 2 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true*/ 2 | 3 | var webpack = require('webpack'); 4 | module.exports = { 5 | entry: './index.js', 6 | output: { 7 | path: './', 8 | filename: 'tag.min.js' 9 | }, 10 | plugins: [ 11 | new webpack.optimize.UglifyJsPlugin({ 12 | minimize: true 13 | }) 14 | ] 15 | }; 16 | --------------------------------------------------------------------------------