├── .gitignore ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── .sandstorm ├── .vagrant │ └── machines │ │ └── default │ │ └── virtualbox │ │ ├── action_provision │ │ ├── action_set_name │ │ ├── id │ │ ├── index_uuid │ │ ├── private_key │ │ └── synced_folders ├── Vagrantfile ├── build.sh ├── global-setup.sh ├── launcher.sh ├── sandstorm-pkgdef.capnp ├── setup.sh └── stack ├── License.md ├── client ├── index.html ├── js │ ├── boards │ │ ├── controllers │ │ │ └── boardsCtrl.ng.js │ │ └── views │ │ │ └── boards.ng.html │ ├── directives │ │ ├── add-idea.directive.ng.js │ │ ├── add-idea.ng.html │ │ └── expand.directive.ng.js │ ├── filters │ │ └── fromNow.ng.js │ ├── ideas │ │ ├── controllers │ │ │ ├── ideaDetailsCtrl.ng.js │ │ │ └── ideasCtrl.ng.js │ │ ├── directives │ │ │ ├── choose-boards.directive.ng.js │ │ │ ├── choose-boards.ng.html │ │ │ ├── idea-list-control.directive.ng.js │ │ │ ├── idea-list-control.ng.html │ │ │ ├── idea-table-item.directive.ng.js │ │ │ ├── idea-table-item.ng.html │ │ │ ├── ideas-table.directive.ng.js │ │ │ └── ideas-table.ng.html │ │ ├── filters │ │ │ └── displayName.ng.js │ │ └── views │ │ │ ├── idea-details.ng.html │ │ │ └── ideas.ng.html │ ├── lib │ │ └── app.ng.js │ └── routes.ng.js └── styles │ ├── components │ ├── _boards.scss │ ├── _choose-boards.scss │ ├── _header.scss │ └── _ideas.scss │ ├── fonts │ └── fonts.scss │ ├── main.scss │ └── ui │ ├── _forms.scss │ ├── _inline-table-form.scss │ ├── _paginator.scss │ ├── _pills.scss │ └── _tables.scss ├── meta ├── changelog.md ├── description.md └── icons │ ├── ideaotter_128.svg │ ├── ideaotter_150.svg │ ├── ideaotter_24.svg │ └── ideaotter_300.svg ├── model ├── boards.js └── ideas.js ├── public ├── favicon.ico ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── header-banner.gif ├── header-banner.svg ├── idea_bulb.png ├── ideaotter.png ├── ideaotterbw.gif └── otters-404.gif ├── readme.md └── server ├── boards.js ├── ideas.js ├── methods.js └── startup └── loadIdeas.js /.gitignore: -------------------------------------------------------------------------------- 1 | settings.json 2 | packages/* 3 | pgp-* 4 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | kmx3bl1d35mzwbpdrn1 8 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | angular 9 | momentjs:moment 10 | angularui:angular-ui-router 11 | urigo:angular-utils-pagination 12 | tmeasday:publish-counts 13 | fourseven:scss 14 | u2622:persistent-session 15 | aldeed:simple-schema 16 | aldeed:collection2 17 | kenton:accounts-sandstorm 18 | jacksingleton:accounts-sandstorm-dev 19 | rzymek:randomcolor 20 | autopublish 21 | insecure 22 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.3 2 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | aldeed:collection2@2.5.0 3 | aldeed:simple-schema@1.3.3 4 | amplify@1.0.0 5 | angular@1.0.1 6 | angular:angular@1.4.6 7 | angularui:angular-ui-router@0.2.15 8 | autopublish@1.0.3 9 | autoupdate@1.2.1 10 | base64@1.0.3 11 | binary-heap@1.0.3 12 | blaze@2.1.2 13 | blaze-tools@1.0.3 14 | boilerplate-generator@1.0.3 15 | callback-hook@1.0.3 16 | check@1.0.5 17 | dburles:mongo-collection-instances@0.3.4 18 | ddp@1.1.0 19 | deps@1.0.7 20 | ejson@1.0.6 21 | fastclick@1.0.3 22 | fourseven:scss@3.2.0 23 | geojson-utils@1.0.3 24 | html-tools@1.0.4 25 | htmljs@1.0.4 26 | http@1.1.0 27 | id-map@1.0.3 28 | insecure@1.0.3 29 | jacksingleton:accounts-sandstorm-dev@0.1.2 30 | jquery@1.11.3_2 31 | json@1.0.3 32 | kenton:accounts-sandstorm@0.1.4 33 | lai:collection-extensions@0.1.4 34 | launch-screen@1.0.2 35 | livedata@1.0.13 36 | localstorage@1.0.3 37 | logging@1.0.7 38 | meteor@1.1.6 39 | meteor-platform@1.2.2 40 | minifiers@1.1.5 41 | minimongo@1.0.8 42 | mobile-status-bar@1.0.3 43 | momentjs:moment@2.10.6 44 | mongo@1.1.0 45 | observe-sequence@1.0.6 46 | ordered-dict@1.0.3 47 | random@1.0.3 48 | reactive-dict@1.1.0 49 | reactive-var@1.0.5 50 | reload@1.1.3 51 | retry@1.0.3 52 | routepolicy@1.0.5 53 | rzymek:randomcolor@0.1.1_1 54 | service-configuration@1.0.4 55 | session@1.1.0 56 | spacebars@1.0.6 57 | spacebars-compiler@1.0.6 58 | templating@1.1.1 59 | tmeasday:publish-counts@0.7.2 60 | tracker@1.0.7 61 | u2622:persistent-session@0.4.1 62 | ui@1.0.6 63 | underscore@1.0.3 64 | urigo:angular@0.9.3 65 | urigo:angular-utils-pagination@1.0.5 66 | url@1.0.4 67 | webapp@1.2.0 68 | webapp-hashing@1.0.3 69 | -------------------------------------------------------------------------------- /.sandstorm/.vagrant/machines/default/virtualbox/action_provision: -------------------------------------------------------------------------------- 1 | 1.5:906bd61b-db08-4434-9ec6-598cfa886604 -------------------------------------------------------------------------------- /.sandstorm/.vagrant/machines/default/virtualbox/action_set_name: -------------------------------------------------------------------------------- 1 | 1452289699 -------------------------------------------------------------------------------- /.sandstorm/.vagrant/machines/default/virtualbox/id: -------------------------------------------------------------------------------- 1 | 906bd61b-db08-4434-9ec6-598cfa886604 -------------------------------------------------------------------------------- /.sandstorm/.vagrant/machines/default/virtualbox/index_uuid: -------------------------------------------------------------------------------- 1 | 1e832b69d08e42b6b2bc316d9444083f -------------------------------------------------------------------------------- /.sandstorm/.vagrant/machines/default/virtualbox/private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA3WKRzhj66qCE1kIytEjvYudbZlbFav7y8hGFP7TgZbsFSPxQ 3 | WWurcMnvzfARyD/xh2LKEua28yjUQJfCl6Lt/0ngDXH+KH1gXnnAQoa7OzUnw1Ma 4 | WiuS+CTj1nG0REKoUcioppqG/3k6d5X3FJ4nbrmz54DkbyjnkPrMq2jnkZLmVMpg 5 | +9plIqld+u6EFmDLdWE+YduFYWzZTvqX1nFG+L9wXMisLi4xa6mS5ZR24E1Jl3lG 6 | MiVRCx4vmhcMwR3iPuw3YuRmyiWd1G4eccNuLtLVnpprRgPjZyfHTZx+UNVsU+2O 7 | 6ZdTe+vlmJvH3l0bp9fPOtWy1/3LKudobBlLwQIDAQABAoIBAAVELgHAGF/yd5Z9 8 | NqWRQpiQtlzl3YZhEDhv8EKGs/vXihWlYuftpsNlGl5EwreHxeAZmoL+mAWKyBnN 9 | QmTHDHBROpI3Tvt5mAxHSyqJ9/Q9nHeKB4tzk+hxQDY+uDITf9uKMXDdJi73nEtx 10 | 9tG3vWpl4oC0UTrPt0p8nNfLBA8zaxCcbV5cKjyvs2P0w+64BCWLR1FjIIlaOY+s 11 | K53J4A4yE5XxuyKTCbaZJ/7Vtk1Z37bJF3vdxsRPBK9yJwp1vi1qe4N2NFLTu9ah 12 | uvuwBTCMEP7O70xTrj2uCrZ4zYsSd2WDavXJV7YyxIquXOlnkmiK/cWMyLs+4+jN 13 | tkxVG40CgYEA9LmrBzhK8G/bUcytqTD8kJQezBmPrVF43khr8dgMFk+rYOEWSlWI 14 | UnID9rrcsKZOYNktXwv/jaROdQkupFcwgSKjct7VCA4IcdTwXxtWZ+Y7uEH+/q+F 15 | gorIF/y0vrSFIYWWb6b+u0sSkZWgMRZ+72pdWmpgRwruZ99FsTLcz1sCgYEA55Wf 16 | b6NzxVlSYvBSR4Wqsn7USR8BzH0wwGuWz7WSC1+n4iiOPlPGgEl5LSnx4aONbkzc 17 | XJCC7ererDQoNGxtB0NB4esQjgWBZMD/svQQmH/i477QnCpIwNz78jp9AsrIalZV 18 | uw9QQuDNDiZNmHYDUILfZHgGCr/SRvAQ3iIuOBMCgYBmb3EPTJjRm4CJ9MqcD00o 19 | l1dsxyZEvd2B7X6BdJVaoW5y1sSlyLePCvptxDmsF3CnSUCckEEf4K6WfolvcK90 20 | 3jLcWjmBbZ5LR3510NOSGeTVc4pwwLd5tO2cXMKrMJSpO2mEwrnNW9ch6SdsZKYt 21 | QLBisDQsiH7SK7rYORoEEQKBgQCactNOZTiE2bl0esqlIKRjPurdiWxtsOCetCJ0 22 | jKfGJhmVX27eaojsbhP+5waGccmVUnV01nw7qHSxbGeT7uwX9+csgNUdotZVF9Jw 23 | yw12Z3QuZ8hBHoI+/iKZviA5iHx5TvMGOoRQ1hRr52fA0pQLUf0iyFlfOedHoDvf 24 | qfQhOQKBgQCLxwA0MH2bWuMzQ2voJQ6Z5pmzzPA7pMTvy1jSoHPS0zZm68iyDxvx 25 | WoXrFOlMlc2RwQpGoddgUiDdupAWCJaH5B/v/ty9kYVApDkiXBmq/39Ry60PLHaX 26 | zxxdb6xqYOBHyhSgGe1tcJgSYRqbq8WvTku3qsoJ4DSeHj5LGu9Vqg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /.sandstorm/.vagrant/machines/default/virtualbox/synced_folders: -------------------------------------------------------------------------------- 1 | {"virtualbox":{"/opt/app":{"guestpath":"/opt/app","hostpath":"/Users/simon/src/ideaotter","disabled":false},"/host-dot-sandstorm":{"guestpath":"/host-dot-sandstorm","hostpath":"/Users/simon/.sandstorm","disabled":false},"/vagrant":{"guestpath":"/vagrant","hostpath":"/Users/simon/src/ideaotter","disabled":false}}} -------------------------------------------------------------------------------- /.sandstorm/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Guess at a reasonable name for the VM based on the folder vagrant-spk is 5 | # run from. The timestamp is there to avoid conflicts if you have multiple 6 | # folders with the same name. 7 | VM_NAME = File.basename(File.dirname(File.dirname(__FILE__))) + "_sandstorm_#{Time.now.utc.to_i}" 8 | 9 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 10 | VAGRANTFILE_API_VERSION = "2" 11 | 12 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 13 | # We base ourselves off Debian Jessie 14 | config.vm.box = "debian/jessie64" 15 | 16 | if Vagrant.has_plugin?("vagrant-vbguest") then 17 | # vagrant-vbguest is a Vagrant plugin that upgrades 18 | # the version of VirtualBox Guest Additions within each 19 | # guest. If you have the vagrant-vbguest plugin, then it 20 | # needs to know how to compile kernel modules, etc., and so 21 | # we give it this hint about operating system type. 22 | config.vm.guest = "debian" 23 | end 24 | 25 | # We forward port 6080, the Sandstorm web port, so that developers can 26 | # visit their sandstorm app from their browser as local.sandstorm.io:6080 27 | # (aka 127.0.0.1:6080). 28 | config.vm.network :forwarded_port, guest: 6080, host: 6080 29 | 30 | # Use a shell script to "provision" the box. This installs Sandstorm using 31 | # the bundled installer. 32 | config.vm.provision "shell", inline: "sudo bash /opt/app/.sandstorm/global-setup.sh", keep_color: true 33 | # Then, do stack-specific and app-specific setup. 34 | config.vm.provision "shell", inline: "sudo bash /opt/app/.sandstorm/setup.sh", keep_color: true 35 | 36 | # Shared folders are configured per-provider since vboxsf can't handle >4096 open files, 37 | # NFS requires privilege escalation every time you bring a VM up, 38 | # and 9p is only available on libvirt. 39 | 40 | # Calculate the number of CPUs and the amount of RAM the system has, 41 | # in a platform-dependent way; further logic below. 42 | cpus = nil 43 | total_kB_ram = nil 44 | 45 | host = RbConfig::CONFIG['host_os'] 46 | if host =~ /darwin/ 47 | cpus = `sysctl -n hw.ncpu`.to_i 48 | total_kB_ram = `sysctl -n hw.memsize`.to_i / 1024 49 | elsif host =~ /linux/ 50 | cpus = `nproc`.to_i 51 | total_kB_ram = `grep MemTotal /proc/meminfo | awk '{print $2}'`.to_i 52 | elsif host =~ /mingw/ 53 | # powershell may not be available on Windows XP and Vista, so wrap this in a rescue block 54 | begin 55 | cpus = `powershell -Command "(Get-WmiObject Win32_Processor -Property NumberOfLogicalProcessors | Select-Object -Property NumberOfLogicalProcessors | Measure-Object NumberOfLogicalProcessors -Sum).Sum"`.to_i 56 | total_kB_ram = `powershell -Command "Get-CimInstance -class cim_physicalmemory | % $_.Capacity}"`.to_i / 1024 57 | rescue 58 | end 59 | end 60 | # Use the same number of CPUs within Vagrant as the system, with 1 61 | # as a default. 62 | # 63 | # Use at least 512MB of RAM, and if the system has more than 2GB of 64 | # RAM, use 1/4 of the system RAM. This seems a reasonable compromise 65 | # between having the Vagrant guest operating system not run out of 66 | # RAM entirely (which it basically would if we went much lower than 67 | # 512MB) and also allowing it to use up a healthily large amount of 68 | # RAM so it can run faster on systems that can afford it. 69 | if cpus.nil? or cpus.zero? 70 | cpus = 1 71 | end 72 | if total_kB_ram.nil? or total_kB_ram < 2048000 73 | assign_ram_mb = 512 74 | else 75 | assign_ram_mb = (total_kB_ram / 1024 / 4) 76 | end 77 | # Actually apply these CPU/memory values to the providers. 78 | config.vm.provider :virtualbox do |vb, override| 79 | vb.cpus = cpus 80 | vb.memory = assign_ram_mb 81 | vb.name = VM_NAME 82 | 83 | override.vm.synced_folder "..", "/opt/app" 84 | override.vm.synced_folder ENV["HOME"] + "/.sandstorm", "/host-dot-sandstorm" 85 | override.vm.synced_folder "..", "/vagrant" 86 | end 87 | config.vm.provider :libvirt do |libvirt, override| 88 | libvirt.cpus = cpus 89 | libvirt.memory = assign_ram_mb 90 | libvirt.default_prefix = VM_NAME 91 | 92 | override.vm.synced_folder "..", "/opt/app", type: "9p", accessmode: "passthrough" 93 | override.vm.synced_folder ENV["HOME"] + "/.sandstorm", "/host-dot-sandstorm", type: "9p", accessmode: "passthrough" 94 | override.vm.synced_folder "..", "/vagrant", type: "9p", accessmode: "passthrough" 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /.sandstorm/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Make meteor bundle 5 | 6 | METEOR_WAREHOUSE_DIR="${METEOR_WAREHOUSE_DIR:-$HOME/.meteor}" 7 | METEOR_DEV_BUNDLE=$(dirname $(readlink -f "$METEOR_WAREHOUSE_DIR/meteor"))/dev_bundle 8 | 9 | cd /opt/app 10 | meteor build --directory /home/vagrant/ 11 | (cd /home/vagrant/bundle/programs/server && "$METEOR_DEV_BUNDLE/bin/npm" install) 12 | 13 | # Copy our launcher script into the bundle so the grain can start up. 14 | mkdir -p /home/vagrant/bundle/opt/app/.sandstorm/ 15 | cp /opt/app/.sandstorm/launcher.sh /home/vagrant/bundle/opt/app/.sandstorm/ 16 | -------------------------------------------------------------------------------- /.sandstorm/global-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | CURL_OPTS="--silent --show-error" 5 | echo localhost > /etc/hostname 6 | hostname localhost 7 | curl $CURL_OPTS https://install.sandstorm.io/ > /host-dot-sandstorm/caches/install.sh 8 | SANDSTORM_CURRENT_VERSION=$(curl $CURL_OPTS -f "https://install.sandstorm.io/dev?from=0&type=install") 9 | SANDSTORM_PACKAGE="sandstorm-$SANDSTORM_CURRENT_VERSION.tar.xz" 10 | if [[ ! -f /host-dot-sandstorm/caches/$SANDSTORM_PACKAGE ]] ; then 11 | echo -n "Downloading Sandstorm version ${SANDSTORM_CURRENT_VERSION}..." 12 | curl $CURL_OPTS --output "/host-dot-sandstorm/caches/$SANDSTORM_PACKAGE.partial" "https://dl.sandstorm.io/$SANDSTORM_PACKAGE" 13 | mv "/host-dot-sandstorm/caches/$SANDSTORM_PACKAGE.partial" "/host-dot-sandstorm/caches/$SANDSTORM_PACKAGE" 14 | echo "...done." 15 | fi 16 | if [ ! -e /opt/sandstorm/latest/sandstorm ] ; then 17 | echo -n "Installing Sandstorm version ${SANDSTORM_CURRENT_VERSION}..." 18 | bash /host-dot-sandstorm/caches/install.sh -d -e "/host-dot-sandstorm/caches/$SANDSTORM_PACKAGE" >/dev/null 19 | echo "...done." 20 | fi 21 | modprobe ip_tables 22 | # Make the vagrant user part of the sandstorm group so that commands like 23 | # `spk dev` work. 24 | usermod -a -G 'sandstorm' 'vagrant' 25 | # Bind to all addresses, so the vagrant port-forward works. 26 | sudo sed --in-place='' \ 27 | --expression='s/^BIND_IP=.*/BIND_IP=0.0.0.0/' \ 28 | /opt/sandstorm/sandstorm.conf 29 | sudo service sandstorm restart 30 | # Enable apt-cacher-ng proxy to make things faster if one appears to be running on the gateway IP 31 | GATEWAY_IP=$(ip route | grep ^default | cut -d ' ' -f 3) 32 | if nc -z "$GATEWAY_IP" 3142 ; then 33 | echo "Acquire::http::Proxy \"http://$GATEWAY_IP:3142\";" > /etc/apt/apt.conf.d/80httpproxy 34 | fi 35 | -------------------------------------------------------------------------------- /.sandstorm/launcher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | exec node /start.js -p 8000 5 | -------------------------------------------------------------------------------- /.sandstorm/sandstorm-pkgdef.capnp: -------------------------------------------------------------------------------- 1 | @0xe98f49f11539d137; 2 | 3 | using Spk = import "/sandstorm/package.capnp"; 4 | # This imports: 5 | # $SANDSTORM_HOME/latest/usr/include/sandstorm/package.capnp 6 | # Check out that file to see the full, documented package definition format. 7 | 8 | const pkgdef :Spk.PackageDefinition = ( 9 | # The package definition. Note that the spk tool looks specifically for the 10 | # "pkgdef" constant. 11 | 12 | id = "du1nkmp0edheyjq07rm8s1tzr4qs4ut7k24vc2ps0vzm30zp77y0", 13 | # Your app ID is actually its public key. The private key was placed in 14 | # your keyring. All updates must be signed with the same key. 15 | 16 | manifest = ( 17 | # This manifest is included in your app package to tell Sandstorm 18 | # about your app. 19 | 20 | appTitle = (defaultText = "Idea Otter"), 21 | 22 | appVersion = 13, # Increment this for every release. 23 | 24 | appMarketingVersion = (defaultText = "1.5.0"), 25 | # Human-readable representation of appVersion. Should match the way you 26 | # identify versions of your app in documentation and marketing. 27 | 28 | actions = [ 29 | # Define your "new document" handlers here. 30 | ( title = (defaultText = "New Instance"), 31 | command = .myCommand 32 | # The command to run when starting for the first time. (".myCommand" 33 | # is just a constant defined at the bottom of the file.) 34 | ) 35 | ], 36 | 37 | continueCommand = .myCommand, 38 | # This is the command called to start your app back up after it has been 39 | # shut down for inactivity. Here we're using the same command as for 40 | # starting a new instance, but you could use different commands for each 41 | # case. 42 | 43 | metadata = ( 44 | # Data which is not needed specifically to execute the app, but is useful 45 | # for purposes like marketing and display. These fields are documented at 46 | # https://docs.sandstorm.io/en/latest/developing/publishing-apps/#add-required-metadata 47 | # and (in deeper detail) in the sandstorm source code, in the Metadata section of 48 | # https://github.com/sandstorm-io/sandstorm/blob/master/src/sandstorm/package.capnp 49 | icons = ( 50 | # Various icons to represent the app in various contexts. 51 | appGrid = (svg = embed "../meta/icons/ideaotter_128.svg"), 52 | grain = (svg = embed "../meta/icons/ideaotter_24.svg"), 53 | market = (svg = embed "../meta/icons/ideaotter_150.svg"), 54 | marketBig = (svg = embed "../meta/icons/ideaotter_300.svg"), 55 | ), 56 | 57 | website = "http://ideaotter.com", 58 | # This should be the app's main website url. 59 | 60 | codeUrl = "https://github.com/simonv3/IdeaOtter", 61 | # URL of the app's source code repository, e.g. a GitHub URL. 62 | # Required if you specify a license requiring redistributing code, but optional otherwise. 63 | 64 | license = (openSource = mit), 65 | # The license this package is distributed under. See 66 | # https://docs.sandstorm.io/en/latest/developing/publishing-apps/#license 67 | 68 | categories = [productivity, office], 69 | # A list of categories/genres to which this app belongs, sorted with best fit first. 70 | # See the list of categories at 71 | # https://docs.sandstorm.io/en/latest/developing/publishing-apps/#categories 72 | 73 | author = ( 74 | # Fields relating to the author of this app. 75 | 76 | contactEmail = "svansintjan@gmail.com", 77 | # Email address to contact for any issues with this app. This includes end-user support 78 | # requests as well as app store administrator requests, so it is very important that this be a 79 | # valid address with someone paying attention to it. 80 | 81 | pgpSignature = embed "../pgp-signature", 82 | # PGP signature attesting responsibility for the app ID. This is a binary-format detached 83 | # signature of the following ASCII message (not including the quotes, no newlines, and 84 | # replacing with the standard base-32 text format of the app's ID): 85 | # 86 | # "I am the author of the Sandstorm.io app with the following ID: " 87 | # 88 | # You can create a signature file using `gpg` like so: 89 | # 90 | # echo -n "I am the author of the Sandstorm.io app with the following ID: " | gpg --sign > pgp-signature 91 | # 92 | # Further details including how to set up GPG and how to use keybase.io can be found 93 | # at https://docs.sandstorm.io/en/latest/developing/publishing-apps/#verify-your-identity 94 | 95 | # upstreamAuthor = "Example App Team", 96 | # Name of the original primary author of this app, if it is different from the person who 97 | # produced the Sandstorm package. Setting this implies that the author connected to the PGP 98 | # signature only "packaged" the app for Sandstorm, rather than developing the app. 99 | # Remove this line if you consider yourself as the author of the app. 100 | ), 101 | 102 | pgpKeyring = embed "../pgp-keyring", 103 | # A keyring in GPG keyring format containing all public keys needed to verify PGP signatures in 104 | # this manifest (as of this writing, there is only one: `author.pgpSignature`). 105 | # 106 | # To generate a keyring containing just your public key, do: 107 | # 108 | # gpg --export > keyring 109 | # 110 | # Where `` is a PGP key ID or email address associated with the key. 111 | 112 | description = (defaultText = embed "../meta/description.md"), 113 | 114 | shortDescription = (defaultText = "Ideation Tracking"), 115 | 116 | screenshots = [ 117 | # Screenshots to use for marketing purposes. Examples below. 118 | # Sizes are given in device-independent pixels, so if you took these 119 | # screenshots on a Retina-style high DPI screen, divide each dimension by two. 120 | 121 | #(width = 746, height = 795, jpeg = embed "path/to/screenshot-1.jpeg"), 122 | #(width = 640, height = 480, png = embed "path/to/screenshot-2.png"), 123 | ], 124 | changeLog = (defaultText = embed "../meta/changelog.md"), 125 | # Documents the history of changes in Github-flavored markdown format (with the same restrictions 126 | # as govern `description`). We recommend formatting this with an H1 heading for each version 127 | # followed by a bullet list of changes. 128 | ), 129 | ), 130 | 131 | sourceMap = ( 132 | # The following directories will be copied into your package. 133 | searchPath = [ 134 | ( sourcePath = "/home/vagrant/bundle" ), 135 | ( sourcePath = "/opt/meteor-spk/meteor-spk.deps" ) 136 | ] 137 | ), 138 | 139 | alwaysInclude = [ "." ], 140 | # This says that we always want to include all files from the source map. 141 | # (An alternative is to automatically detect dependencies by watching what 142 | # the app opens while running in dev mode. To see what that looks like, 143 | # run `spk init` without the -A option.) 144 | 145 | #bridgeConfig = ( 146 | # # Used for integrating permissions and roles into the Sandstorm shell 147 | # # and for sandstorm-http-bridge to pass to your app. 148 | # # Uncomment this block and adjust the permissions and roles to make 149 | # # sense for your app. 150 | # # For more information, see high-level documentation at 151 | # # https://docs.sandstorm.io/en/latest/developing/auth/ 152 | # # and advanced details in the "BridgeConfig" section of 153 | # # https://github.com/sandstorm-io/sandstorm/blob/master/src/sandstorm/package.capnp 154 | # viewInfo = ( 155 | # # For details on the viewInfo field, consult "ViewInfo" in 156 | # # https://github.com/sandstorm-io/sandstorm/blob/master/src/sandstorm/grain.capnp 157 | # 158 | # permissions = [ 159 | # # Permissions which a user may or may not possess. A user's current 160 | # # permissions are passed to the app as a comma-separated list of `name` 161 | # # fields in the X-Sandstorm-Permissions header with each request. 162 | # # 163 | # # IMPORTANT: only ever append to this list! Reordering or removing fields 164 | # # will change behavior and permissions for existing grains! To deprecate a 165 | # # permission, or for more information, see "PermissionDef" in 166 | # # https://github.com/sandstorm-io/sandstorm/blob/master/src/sandstorm/grain.capnp 167 | # ( 168 | # name = "editor", 169 | # # Name of the permission, used as an identifier for the permission in cases where string 170 | # # names are preferred. Used in sandstorm-http-bridge's X-Sandstorm-Permissions HTTP header. 171 | # 172 | # title = (defaultText = "editor"), 173 | # # Display name of the permission, e.g. to display in a checklist of permissions 174 | # # that may be assigned when sharing. 175 | # 176 | # description = (defaultText = "grants ability to modify data"), 177 | # # Prose describing what this role means, suitable for a tool tip or similar help text. 178 | # ), 179 | # ], 180 | # roles = [ 181 | # # Roles are logical collections of permissions. For instance, your app may have 182 | # # a "viewer" role and an "editor" role 183 | # ( 184 | # title = (defaultText = "editor"), 185 | # # Name of the role. Shown in the Sandstorm UI to indicate which users have which roles. 186 | # 187 | # permissions = [true], 188 | # # An array indicating which permissions this role carries. 189 | # # It should be the same length as the permissions array in 190 | # # viewInfo, and the order of the lists must match. 191 | # 192 | # verbPhrase = (defaultText = "can make changes to the document"), 193 | # # Brief explanatory text to show in the sharing UI indicating 194 | # # what a user assigned this role will be able to do with the grain. 195 | # 196 | # description = (defaultText = "editors may view all site data and change settings."), 197 | # # Prose describing what this role means, suitable for a tool tip or similar help text. 198 | # ), 199 | # ( 200 | # title = (defaultText = "viewer"), 201 | # permissions = [false], 202 | # verbPhrase = (defaultText = "can view the document"), 203 | # description = (defaultText = "viewers may view what other users have written."), 204 | # ), 205 | # ], 206 | # ), 207 | # #apiPath = "/api", 208 | # # Apps can export an API to the world. The API is to be used primarily by Javascript 209 | # # code and native apps, so it can't serve out regular HTML to browsers. If a request 210 | # # comes in to your app's API, sandstorm-http-bridge will prefix the request's path with 211 | # # this string, if specified. 212 | #), 213 | ); 214 | 215 | const myCommand :Spk.Manifest.Command = ( 216 | # Here we define the command used to start up your server. 217 | argv = ["/sandstorm-http-bridge", "8000", "--", "/opt/app/.sandstorm/launcher.sh"], 218 | environ = [ 219 | # Note that this defines the *entire* environment seen by your app. 220 | (key = "PATH", value = "/usr/local/bin:/usr/bin:/bin"), 221 | (key = "SANDSTORM", value = "1"), 222 | # Export SANDSTORM=1 into the environment, so that apps running within Sandstorm 223 | # can detect if $SANDSTORM="1" at runtime, switching UI and/or backend to use 224 | # the app's Sandstorm-specific integration code. 225 | ] 226 | ); 227 | -------------------------------------------------------------------------------- /.sandstorm/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | CURL_OPTS="--silent --show-error" 5 | 6 | cd /opt/ 7 | 8 | PACKAGE=meteor-spk-0.1.8 9 | PACKAGE_FILENAME="$PACKAGE.tar.xz" 10 | CACHE_TARGET="/host-dot-sandstorm/caches/${PACKAGE_FILENAME}" 11 | 12 | # Fetch meteor-spk tarball if not cached 13 | if [ ! -f "$CACHE_TARGET" ] ; then 14 | echo -n "Downloading ${PACKAGE}..." 15 | curl $CURL_OPTS https://dl.sandstorm.io/${PACKAGE_FILENAME} > "$CACHE_TARGET.partial" 16 | mv "${CACHE_TARGET}.partial" "${CACHE_TARGET}" 17 | echo "...done." 18 | fi 19 | 20 | # Extract to /opt 21 | tar xf "$CACHE_TARGET" 22 | 23 | # Create symlink so we can rely on the path /opt/meteor-spk 24 | if [ ! -e meteor-spk ] ; then ln -s "${PACKAGE}" meteor-spk ; fi 25 | 26 | # Add bash, and its dependencies, so they get mapped into the image. 27 | # Bash runs the launcher script. 28 | cp -a /bin/bash /opt/meteor-spk/meteor-spk.deps/bin/ 29 | cp -a /lib/x86_64-linux-gnu/libncurses.so.* /opt/meteor-spk/meteor-spk.deps/lib/x86_64-linux-gnu/ 30 | cp -a /lib/x86_64-linux-gnu/libtinfo.so.* /opt/meteor-spk/meteor-spk.deps/lib/x86_64-linux-gnu/ 31 | 32 | # Unfortunately, Meteor does not explicitly make it easy to cache packages, but 33 | # we know experimentally that the package is mostly directly extractable to a 34 | # user's $HOME/.meteor directory. 35 | METEOR_RELEASE=1.1.0.2 36 | METEOR_PLATFORM=os.linux.x86_64 37 | METEOR_TARBALL_FILENAME="meteor-bootstrap-${METEOR_PLATFORM}.tar.gz" 38 | METEOR_TARBALL_URL="https://d3sqy0vbqsdhku.cloudfront.net/packages-bootstrap/${METEOR_RELEASE}/${METEOR_TARBALL_FILENAME}" 39 | METEOR_CACHE_TARGET="/host-dot-sandstorm/caches/${METEOR_TARBALL_FILENAME}" 40 | 41 | # Fetch meteor tarball if not cached 42 | if [ ! -f "$METEOR_CACHE_TARGET" ] ; then 43 | echo -n "Downloading Meteor version ${METEOR_RELEASE}..." 44 | curl $CURL_OPTS "$METEOR_TARBALL_URL" > "${METEOR_CACHE_TARGET}.partial" 45 | mv "${METEOR_CACHE_TARGET}"{.partial,} 46 | echo "...done." 47 | fi 48 | 49 | # Extract as unprivileged user, which is the usual meteor setup 50 | cd /home/vagrant/ 51 | su -c "tar xf '${METEOR_CACHE_TARGET}'" vagrant 52 | # Link into global PATH 53 | if [ ! -e /usr/bin/meteor ] ; then ln -s /home/vagrant/.meteor/meteor /usr/bin/meteor ; fi 54 | -------------------------------------------------------------------------------- /.sandstorm/stack: -------------------------------------------------------------------------------- 1 | meteor 2 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Simon Vansintjan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Idea Otter 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | light bulb 12 | Ideas 13 | Boards 14 |
15 |
16 |
17 |
18 |
19 | The code for Idea Otter is on GitHub. 20 |
21 | 22 | -------------------------------------------------------------------------------- /client/js/boards/controllers/boardsCtrl.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter').controller('BoardsCtrl', 2 | function($scope, $rootScope, $q, $meteor){ 3 | 4 | $q.all([ 5 | $scope.$meteorSubscribe('boards') 6 | ]).then(function(data) { 7 | $scope.boards = $meteor.collection(Boards, false); 8 | }); 9 | 10 | $scope.remove = function(board){ 11 | $scope.boards.remove(board); 12 | }; 13 | 14 | $scope.edit = function(board) { 15 | $scope.boards.forEach(function(board) { 16 | board.editing = false; 17 | }); 18 | $scope.boards.save(); 19 | board.editing = true; 20 | }; 21 | 22 | $scope.doneEditing = function(board) { 23 | board.editing = false; 24 | $scope.boards.save(); 25 | }; 26 | 27 | $scope.add = function(board){ 28 | board.owner = $rootScope.currentUser._id; 29 | board.color = randomColor({seed: board.name}); 30 | $scope.boards.save(board); 31 | $scope.newBoard = {}; 32 | }; 33 | 34 | $scope.getUserById = function(userId){ 35 | return Meteor.users.findOne(userId); 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /client/js/boards/views/boards.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 18 | 27 | 30 | 33 | 37 | 40 | 41 |
10 | 13 | 17 | 19 | 20 | {{board.name}} 21 | 22 | 26 | 28 | {{board.date | fromNow}} 29 | 31 | Created by {{ creator(board) | displayName }} 32 | 34 | edit 35 | save changes 36 | 38 | × 39 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /client/js/directives/add-idea.directive.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter') 2 | .directive('addIdea', function ($state, $rootScope) { 3 | return { 4 | restrict: 'A', 5 | scope: { 6 | ideas: '=addIdea', 7 | board: '=postIdeasTo', 8 | }, 9 | link: function ($scope, $element, $attrs) { 10 | 11 | $scope.add = function(idea) { 12 | 13 | idea.date_added = moment().toDate(); 14 | idea.date_last_updated = moment().toDate(); 15 | 16 | if ($scope.board !== undefined && $scope.board !== null) { 17 | idea.board = $scope.board; 18 | } 19 | if ($rootScope.currentUser) { 20 | idea.owner = $rootScope.currentUser._id; 21 | } else { 22 | idea.owner = Meteor.call('createTemporaryUser'); 23 | idea.is_public = true; 24 | } 25 | // Ideas.insert(idea, function(error, ideaId) { 26 | // if (error) console.log('error', error); 27 | // 28 | // $scope.ideas.sort(function(idea1, idea2) { 29 | // return moment(idea1.date_added).isBefore(idea2.date_added); 30 | // }); 31 | // $scope.newIdea={}; 32 | // }) 33 | $scope.ideas.save(idea).then(function(success) { 34 | $scope.ideas.sort(function(idea1, idea2) { 35 | return moment(idea1.date_added).isBefore(idea2.date_added); 36 | }); 37 | $scope.newIdea={}; 38 | }, function(error) { 39 | console.log('error', error); 40 | }); 41 | }; 42 | }, 43 | templateUrl: 'client/js/directives/add-idea.ng.html', 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /client/js/directives/add-idea.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /client/js/directives/expand.directive.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter') 2 | .directive('expand', function ($state, $rootScope) { 3 | return { 4 | restrict: 'A', 5 | link: function ($scope, $element, $attrs) { 6 | 7 | $element.on('keyup', function(ev) { 8 | 9 | var isShift = false; 10 | isShift = !!ev.shiftKey; 11 | 12 | if ($element[0].value.length > 40 || 13 | ev.keyCode === 13 && isShift) { 14 | $element.addClass('grow'); 15 | } 16 | 17 | if (ev.keyCode === 13 && !isShift) { 18 | $scope.add($scope.newIdea); 19 | } 20 | }); 21 | }, 22 | templateUrl: 'client/js/directives/add-idea.ng.html', 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /client/js/filters/fromNow.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter').filter('fromNow', function() { 2 | return function(date) { 3 | return moment(date).fromNow(); 4 | }; 5 | }); 6 | -------------------------------------------------------------------------------- /client/js/ideas/controllers/ideaDetailsCtrl.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter').controller('IdeaDetailsCtrl', ['$scope', '$stateParams', '$meteor', 2 | function($scope, $stateParams, $meteor){ 3 | console.log('idea details'); 4 | $scope.idea = $meteor.object(Ideas, $stateParams.ideaId, false); 5 | 6 | var subscriptionHandle; 7 | 8 | $meteor.subscribe('ideas').then(function(handle) { 9 | subscriptionHandle = handle; 10 | }); 11 | 12 | $scope.users = $meteor.collection(Meteor.users, false).subscribe('users'); 13 | 14 | $scope.save = function() { 15 | $scope.idea.save().then(function(numberOfDocs){ 16 | console.log('save success doc affected ', numberOfDocs); 17 | }, function(error){ 18 | console.log('save error', error); 19 | }); 20 | }; 21 | 22 | $scope.reset = function() { 23 | $scope.idea.reset(); 24 | }; 25 | 26 | // Catch the controller closing event. 27 | $scope.$on('$destroy', function() { 28 | subscriptionHandle.stop(); 29 | }); 30 | }]); 31 | -------------------------------------------------------------------------------- /client/js/ideas/controllers/ideasCtrl.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter').controller('IdeasCtrl', 2 | function($scope, $q, $rootScope, $meteor){ 3 | 4 | $scope.hideArchived = true; 5 | 6 | $scope.page = 1; 7 | $scope.perPage = 20; 8 | $scope.sort = { date_added: -1 }; 9 | 10 | $meteor.autorun($scope, function() { 11 | var publisherArguments = { 12 | limit: parseInt($scope.getReactively('perPage')), 13 | skip: parseInt(($scope.getReactively('page') - 1) * $scope.getReactively('perPage')), 14 | sort: $scope.getReactively('sort'), 15 | }; 16 | 17 | $scope.$meteorSubscribe( 18 | 'ideas', 19 | publisherArguments, 20 | $scope.getReactively('search'), 21 | $scope.getReactively('boardFilter') 22 | ).then(function(){ 23 | var filter = { 24 | 'idea' : { '$regex' : '.*' + ($scope.search !== undefined ? $scope.search : '') + '.*', '$options' : 'i' }, 25 | }; 26 | console.log($scope.boardFilter); 27 | if ($scope.boardFilter !== null && 28 | $scope.boardFilter !== undefined && 29 | $scope.boardFilter !== '') 30 | filter.board = $scope.boardFilter; 31 | 32 | $scope.ideas = $scope.$meteorCollection(function() { 33 | return Ideas.find(filter, publisherArguments) 34 | }, false); 35 | 36 | $scope.ideasCount = $meteor.object(Counts ,'numberOfIdeas', false); 37 | }); 38 | }); 39 | 40 | $scope.getUserById = function(userId){ 41 | return Meteor.users.findOne(userId); 42 | }; 43 | 44 | // $scope.$watch('orderProperty', function(){ 45 | // if ($scope.orderProperty) 46 | // $scope.sort = {name: parseInt($scope.orderProperty)}; 47 | // }); 48 | 49 | $scope.pageChanged = function(newPage) { 50 | console.log('new page', newPage); 51 | $scope.page = newPage; 52 | }; 53 | 54 | // TODO: this needs to moved somewhere else - reusability 55 | $rootScope.creator = function(idea){ 56 | if (!idea) 57 | return; 58 | 59 | var owner = $scope.getUserById(idea.owner); 60 | 61 | if (!owner) 62 | return 'nobody'; 63 | if (!owner._id) { 64 | if (owner === Session.get('tempUser')) 65 | return 'me'; 66 | return owner; 67 | } 68 | if ($rootScope.currentUser) { 69 | if ($rootScope.currentUser._id) { 70 | if (owner._id === $rootScope.currentUser._id) 71 | return 'me'; 72 | } 73 | } 74 | 75 | return owner; 76 | }; 77 | }); 78 | -------------------------------------------------------------------------------- /client/js/ideas/directives/choose-boards.directive.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter') 2 | .directive('chooseBoards', function ($state, $q, $meteor) { 3 | return { 4 | restrict: 'A', 5 | scope: { 6 | selectedBoard: '=chooseBoards', 7 | }, 8 | link: function ($scope, $element, $attrs, ideasTableCtrl) { 9 | 10 | $q.all([ 11 | $scope.$meteorSubscribe('boards') 12 | ]).then(function(data) { 13 | $scope.boards = $meteor.collection(Boards); 14 | $scope.boards.forEach(function(board, idx) { 15 | if (board.selected) 16 | $scope.selectedBoard = board._id; 17 | }); 18 | }); 19 | 20 | $scope.selectBoard = function(board) { 21 | $scope.boards.forEach(function(b) { 22 | b.selected = false; 23 | }); 24 | 25 | if (board !== null) { 26 | board.selected = !board.selected; 27 | $scope.selectedBoard = board._id; 28 | } else { 29 | $scope.selectedBoard = undefined; 30 | } 31 | }; 32 | }, 33 | templateUrl: 'client/js/ideas/directives/choose-boards.ng.html', 34 | }; 35 | }); 36 | -------------------------------------------------------------------------------- /client/js/ideas/directives/choose-boards.ng.html: -------------------------------------------------------------------------------- 1 |
2 | Post to board: 3 |
    4 |
  • None
  • 5 |
  • 6 | 7 | {{ board.name }}{{randomColor({seed: $index})}} 8 |
  • 9 |
  • 10 | Manage boards » 11 |
  • 12 |
13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /client/js/ideas/directives/idea-list-control.directive.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter') 2 | .directive('ideaListControl', function ($state, $rootScope, $q, $meteor) { 3 | return { 4 | restrict: 'A', 5 | scope: { 6 | search: '=', 7 | order: '=', 8 | count: '=', 9 | boardFilter: '=' 10 | }, 11 | link: function ($scope) { 12 | 13 | $q.all([ 14 | $scope.$meteorSubscribe('boards') 15 | ]).then(function(data) { 16 | $scope.boards = $meteor.collection(Boards, false); 17 | }); 18 | 19 | }, 20 | templateUrl: 'client/js/ideas/directives/idea-list-control.ng.html', 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /client/js/ideas/directives/idea-list-control.ng.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Filter: 4 | 5 | 9 |
10 | 17 |
18 | -------------------------------------------------------------------------------- /client/js/ideas/directives/idea-table-item.directive.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter') 2 | .directive('ideaTableItem', function ($state, $rootScope, $q, $meteor) { 3 | return { 4 | restrict: 'A', 5 | require: '^ideasTable', 6 | scope: { 7 | idea: '=ideaTableItem', 8 | ideas: '=' 9 | }, 10 | link: function ($scope, $element, $attrs, ideasTableCtrl) { 11 | $scope.creator = $rootScope.creator; 12 | $scope.remove = ideasTableCtrl.remove; 13 | 14 | $q.all([ 15 | $scope.$meteorSubscribe('boards') 16 | ]).then(function(data) { 17 | $scope.boards = $meteor.collection(Boards, false); 18 | }); 19 | 20 | $scope.edit = function(editingIdea) { 21 | $scope.ideas.forEach(function(idea) { 22 | idea.editing = false; 23 | }); 24 | $scope.ideas.save(); 25 | editingIdea.editing = true; 26 | }; 27 | 28 | $scope.doneEditing = function(editingIdea) { 29 | editingIdea.editing = false; 30 | $scope.ideas.save(); 31 | }; 32 | 33 | $scope.getBoardById = function(boardId){ 34 | return Boards.findOne(boardId); 35 | }; 36 | 37 | $scope.hasBoard = function(idea) { 38 | if (!idea) 39 | return false; 40 | if (!idea.board) 41 | return false; 42 | return true; 43 | }; 44 | 45 | $scope.getBoard = function(idea) { 46 | if (!idea) 47 | return null; 48 | 49 | var board = $scope.getBoardById(idea.board); 50 | 51 | if (!board) 52 | return null; 53 | 54 | return board; 55 | }; 56 | 57 | }, 58 | templateUrl: 'client/js/ideas/directives/idea-table-item.ng.html', 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /client/js/ideas/directives/idea-table-item.ng.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | {{idea.idea}} 13 | 17 | 18 | 19 | {{ idea.date_added | fromNow }} 20 | 21 | 22 | Posted by {{ creator(idea) | displayName }} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /client/js/ideas/directives/ideas-table.directive.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter') 2 | .directive('ideasTable', function ($state, $rootScope) { 3 | return { 4 | restrict: 'A', 5 | scope: true, 6 | controller: function ($scope, $element, $attrs) { 7 | 8 | this.remove = function(idea){ 9 | idea.archived = true; 10 | $scope.ideas.save(); 11 | }; 12 | }, 13 | templateUrl: 'client/js/ideas/directives/ideas-table.ng.html', 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /client/js/ideas/directives/ideas-table.ng.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 |
15 | 16 | 18 | 19 | 20 |
21 |

22 | You haven't added ideas yet, all we found was some otters! 23 |

24 |

25 | To start adding ideas, simply click on the input field above, start typing, and click "Add Idea". You can also just press Enter. 26 |

27 | missing ideas otter image 28 |
29 | -------------------------------------------------------------------------------- /client/js/ideas/filters/displayName.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter').filter('displayName', function () { 2 | return function (user) { 3 | if (!user) 4 | return; 5 | if (user.profile && user.profile.name) 6 | return user.profile.name; 7 | else if (user.emails) 8 | return user.emails[0].address; 9 | else 10 | return user; 11 | }; 12 | }); 13 | 14 | -------------------------------------------------------------------------------- /client/js/ideas/views/idea-details.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 7 | 8 | 9 | 10 | Cancel 11 |
12 | -------------------------------------------------------------------------------- /client/js/ideas/views/ideas.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
5 | 6 |
7 | 15 | 16 |
21 |
22 | 23 |
26 |
27 | -------------------------------------------------------------------------------- /client/js/lib/app.ng.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('ideaotter', 3 | ['angular-meteor', 4 | 'ui.router', 5 | 'angularUtils.directives.dirPagination' 6 | ]); 7 | 8 | Meteor.subscribe("userData"); 9 | -------------------------------------------------------------------------------- /client/js/routes.ng.js: -------------------------------------------------------------------------------- 1 | angular.module('ideaotter').run( function($rootScope, $state) { 2 | 3 | $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) { 4 | // We can catch the error thrown when the $requireUser promise is rejected 5 | // and redirect the user back to the main page 6 | if (error === "AUTH_REQUIRED") { 7 | $state.go('ideas'); 8 | } 9 | }); 10 | }); 11 | 12 | angular.module('ideaotter').config(function($urlRouterProvider, $stateProvider, $locationProvider){ 13 | 14 | $locationProvider.html5Mode(true); 15 | 16 | $stateProvider 17 | .state('boards', { 18 | url: '/boards', 19 | templateUrl: 'client/js/boards/views/boards.ng.html', 20 | controller: 'BoardsCtrl', 21 | resolve: { 22 | 'currentUser': [function() { 23 | return Meteor.user() 24 | }] 25 | } 26 | }) 27 | .state('ideas', { 28 | url: '/', 29 | templateUrl: 'client/js/ideas/views/ideas.ng.html', 30 | controller: 'IdeasCtrl', 31 | resolve: { 32 | 'currentUser': [function() { 33 | return Meteor.user(); 34 | }] 35 | } 36 | }) 37 | .state('ideaDetails', { 38 | url: '/ideas/:ideaId', 39 | templateUrl: 'client/js/ideas/views/idea-details.ng.html', 40 | controller: 'IdeaDetailsCtrl', 41 | resolve: { 42 | "currentUser": [function(){ 43 | return Meteor.user(); 44 | }] 45 | } 46 | }); 47 | $urlRouterProvider.otherwise('/'); 48 | }); 49 | -------------------------------------------------------------------------------- /client/styles/components/_boards.scss: -------------------------------------------------------------------------------- 1 | .boards { 2 | margin-top: 1rem; 3 | 4 | input[type=color] { 5 | border: 2px $light-grey; 6 | background-color: transparent; 7 | height: 2rem; 8 | width: 2rem; 9 | border-radius: 100%; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/styles/components/_choose-boards.scss: -------------------------------------------------------------------------------- 1 | .choose-boards { 2 | margin-bottom: .75rem; 3 | position: relative; 4 | 5 | ul { display: inline-block; } 6 | li { 7 | cursor: pointer; 8 | display: inline-block; 9 | margin-left: .5rem; 10 | margin-top: .5rem; 11 | padding: .25rem .75rem; 12 | } 13 | 14 | li:not(.manage) { 15 | border-radius: 1rem; 16 | border: 1px solid $choose-board-tab-color; 17 | 18 | &.selected { 19 | background-color: darken($choose-board-tab-background, 10%); 20 | position: relative; 21 | } 22 | } 23 | 24 | .manage { 25 | margin-left: 1rem; 26 | text-decoration: none; 27 | } 28 | } 29 | 30 | .indicator { 31 | width: .75rem; 32 | height: .75rem; 33 | border-radius: 100%; 34 | display: inline-block; 35 | } 36 | -------------------------------------------------------------------------------- /client/styles/components/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | background: $light-grey; 3 | padding: .75rem; 4 | margin-bottom: .75rem; 5 | border-bottom: solid 1px darken($light-grey, 10%); 6 | 7 | * { 8 | vertical-align: middle; 9 | } 10 | 11 | img { 12 | margin-right: .5rem; 13 | } 14 | 15 | a { 16 | padding: .75rem; 17 | padding-left: 0; 18 | color: $header-color; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/styles/components/_ideas.scss: -------------------------------------------------------------------------------- 1 | .idea-list-control { 2 | div { 3 | display: inline-block; 4 | margin-left: 1rem; 5 | } 6 | 7 | div:first-child { 8 | margin-left: 0; 9 | } 10 | } 11 | 12 | .archived-control { 13 | float: right; 14 | a { 15 | color: $light-grey; 16 | } 17 | } 18 | 19 | .idea-list-wrapper { 20 | margin-top: 1.25rem; 21 | padding-top: 1.25rem; 22 | border-top: 1px solid $light-grey; 23 | } 24 | 25 | table.ideas { 26 | .board { 27 | margin-right: .25rem; 28 | } 29 | 30 | td.idea { 31 | * { 32 | vertical-align: middle; 33 | } 34 | 35 | textarea { 36 | width: 100%; 37 | } 38 | } 39 | 40 | .board { 41 | width: 2rem; 42 | } 43 | 44 | .time-ago, 45 | .author { 46 | width: 8rem; 47 | } 48 | 49 | .edit, 50 | .remove { 51 | width: 2.5rem; 52 | padding: 0; 53 | 54 | a { 55 | width: 100%; 56 | height: 100%; 57 | display: block; 58 | text-align: center; 59 | } 60 | } 61 | 62 | .editing { 63 | select { 64 | width: 5rem; 65 | } 66 | 67 | .board { 68 | width: 6rem; 69 | } 70 | } 71 | 72 | .archived * { 73 | opacity: .5; 74 | } 75 | } 76 | 77 | 78 | .empty-board { 79 | text-align: center; 80 | 81 | p { 82 | max-width: 640px; 83 | margin: 1rem auto; 84 | } 85 | 86 | img { 87 | margin-bottom: 2rem; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /client/styles/fonts/fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('fonts/icomoon.eot?cac5nn'); 4 | src:url('fonts/icomoon.eot?cac5nn#iefix') format('embedded-opentype'), 5 | url('fonts/icomoon.ttf?cac5nn') format('truetype'), 6 | url('fonts/icomoon.woff?cac5nn') format('woff'), 7 | url('fonts/icomoon.svg?cac5nn#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | font-family: 'icomoon'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .icon-trash:before { 27 | content: "\e64f"; 28 | } 29 | .icon-check:before { 30 | content: "\e67d"; 31 | } 32 | .icon-pencil:before { 33 | content: "\e6f3"; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /client/styles/main.scss: -------------------------------------------------------------------------------- 1 | 2 | $black: #3B3B3B; 3 | $grey: #aaaaaa; 4 | $pink: pink; 5 | $light-grey: #e7e7e7; 6 | $white: #ffffff; 7 | 8 | $choose-board-tab-color: darken($light-grey, 10%); 9 | $choose-board-tab-background: $white; 10 | 11 | $body-color: $black; 12 | $link-color: #FF4848; 13 | $header-color: $link-color; 14 | 15 | @import "fonts/fonts", 16 | "ui/forms", 17 | "ui/inline-table-form", 18 | "ui/pills", 19 | "ui/tables", 20 | "ui/paginator", 21 | "components/header", 22 | "components/ideas", 23 | "components/choose-boards", 24 | "components/boards"; 25 | 26 | *, 27 | *:after, 28 | *:before { 29 | margin: 0; 30 | padding: 0; 31 | box-sizing: border-box; 32 | } 33 | 34 | body { 35 | color: $body-color; 36 | font-family: Georgia, serif; 37 | } 38 | 39 | a { 40 | color: $link-color; 41 | cursor: pointer; 42 | } 43 | 44 | .wrapper { 45 | max-width: 960px; 46 | margin: 0 auto; 47 | } 48 | 49 | .footer { 50 | margin-top: 2rem; 51 | margin-bottom: 2rem; 52 | } 53 | -------------------------------------------------------------------------------- /client/styles/ui/_forms.scss: -------------------------------------------------------------------------------- 1 | :-moz-ui-invalid:-moz-focusring:not(output) { 2 | box-shadow: none; // 0px 0px 2px 2px rgba(255,0,0,0.4); 3 | } 4 | :-moz-ui-invalid:not(output) { 5 | box-shadow: none; // 0px 0px 1.5px 1px red; 6 | } 7 | 8 | .horizontal-form { 9 | * { 10 | vertical-align: middle; 11 | } 12 | 13 | input, 14 | textarea { 15 | height: 1.5rem; 16 | min-width: 50%; 17 | } 18 | 19 | button { 20 | text-align: right; 21 | } 22 | } 23 | 24 | textarea { 25 | padding: .25rem; 26 | border: 1px solid $light-grey; 27 | transition: .5s height; 28 | } 29 | 30 | textarea.grow, 31 | .horizontal-form textarea.grow { 32 | height: 6rem; 33 | } 34 | 35 | button { 36 | background: none; 37 | border: 1px solid $grey; 38 | border-radius: 3px; 39 | padding: .25rem .75rem; 40 | box-shadow: 0px 2px 0px $grey; 41 | transition: box-shadow .2s, background-color .2s; 42 | cursor: pointer; 43 | 44 | &:hover { 45 | background-color: $light-grey; 46 | } 47 | 48 | &:active, 49 | &:focus { 50 | box-shadow: 0px 1px 0px $grey; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/styles/ui/_inline-table-form.scss: -------------------------------------------------------------------------------- 1 | 2 | .inline-editing { 3 | input { 4 | padding: .25rem; 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /client/styles/ui/_paginator.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | padding-left: 0; 3 | li { 4 | display: inline-block; 5 | margin: .2rem; 6 | 7 | a { 8 | padding: .5rem; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/styles/ui/_pills.scss: -------------------------------------------------------------------------------- 1 | .pill { 2 | border: 1px solid $grey; 3 | border-radius: 1rem; 4 | padding: .25rem .75rem; 5 | } 6 | -------------------------------------------------------------------------------- /client/styles/ui/_tables.scss: -------------------------------------------------------------------------------- 1 | 2 | table { 3 | margin: 1rem 0; 4 | width: 100%; 5 | border-collapse: collapse; 6 | } 7 | 8 | td { 9 | padding: .5rem; 10 | border-bottom: 1px solid $light-grey; 11 | } 12 | 13 | td.remove a { 14 | cursor: pointer; 15 | } 16 | -------------------------------------------------------------------------------- /meta/changelog.md: -------------------------------------------------------------------------------- 1 | # 1.5.0 Stop Deleting Ideas 2 | 3 | * Ideas won't be deleted, instead they'll be archived. Future releases might allow you to permanently delete them. 4 | 5 | # 1.4.0 Filter Ideas by board 6 | 7 | * Added a dropdown filter that lets you filter Ideas by board. 8 | 9 | # 1.3.1 Style changes 10 | 11 | * Minor tweaks to the visual of editing ideas, better allowance for longer ideas. 12 | 13 | # 1.3.0 Editing Ideas, style changes 14 | 15 | * It's now possible to edit ideas. 16 | * There's been some small style changes to more clearly differentiate between the create idea section of the board and the idea list. 17 | 18 | # 1.2.0 Editing Boards, Sharing With Others 19 | 20 | * It's now possible to successfully share Idea Otter grains with other users who will be able to edit and contribute to them. In the future, control of these grains will be much more fine-tuned. 21 | * Board colors and names can be edited. 22 | * Added autopublish and insecure back into the app because it's not really necessary with Sandstorm. 23 | -------------------------------------------------------------------------------- /meta/description.md: -------------------------------------------------------------------------------- 1 | A place for gathering your ideas. 2 | -------------------------------------------------------------------------------- /meta/icons/ideaotter_128.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 46 | 82 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 134 | 138 | 140 | 144 | 146 | 148 | 150 | 153 | 155 | 157 | 159 | 161 | 163 | 165 | 167 | 169 | 171 | 174 | 176 | 178 | 180 | 182 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /meta/icons/ideaotter_150.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 46 | 82 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 134 | 138 | 140 | 144 | 146 | 148 | 150 | 153 | 155 | 157 | 159 | 161 | 163 | 165 | 167 | 169 | 171 | 174 | 176 | 178 | 180 | 182 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /meta/icons/ideaotter_24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | 28 | 29 | 30 | 33 | 34 | 35 | 38 | 39 | 40 | 43 | 44 | 45 | 48 | 49 | 50 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /meta/icons/ideaotter_300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 46 | 82 | 103 | 107 | 111 | 115 | 119 | 123 | 127 | 131 | 134 | 138 | 140 | 144 | 146 | 148 | 150 | 153 | 155 | 157 | 159 | 161 | 163 | 165 | 167 | 169 | 171 | 174 | 176 | 178 | 180 | 182 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /model/boards.js: -------------------------------------------------------------------------------- 1 | Boards = new Mongo.Collection("boards"); 2 | -------------------------------------------------------------------------------- /model/ideas.js: -------------------------------------------------------------------------------- 1 | Ideas = new Mongo.Collection("ideas"); 2 | 3 | // Ideas.allow({ 4 | // insert: function (userId, idea) { 5 | // if (userId) { 6 | // return userId && idea.owner === userId; 7 | // } else { 8 | // return true; 9 | // } 10 | // }, 11 | // update: function (userId, idea, fields, modifier) { 12 | // if (userId !== idea.owner) 13 | // return false; 14 | 15 | // return true; 16 | // }, 17 | // remove: function (userId, idea) { 18 | // if (userId !== idea.owner) 19 | // return false; 20 | 21 | // return true; 22 | // } 23 | // }); 24 | 25 | // TODO: this isn't working because Angular is spitting out an $apply 26 | // is already in progress. 27 | 28 | IdeaSchema = new SimpleSchema({ 29 | idea: { 30 | type: String, 31 | label: "Idea", 32 | }, 33 | owner: { 34 | type: String, 35 | label: "Owner" 36 | }, 37 | board: { 38 | type: String, 39 | label: "Board", 40 | optional: true 41 | }, 42 | is_public: { 43 | type: Boolean, 44 | label: "Is Public", 45 | defaultValue: false 46 | }, 47 | date_added: { 48 | type: Date, 49 | label: "Date Added", 50 | defaultValue: moment(), 51 | optional: true 52 | }, 53 | // date_last_updated: { 54 | // type: Date, 55 | // label: "Date Last Updated", 56 | // autoValue: function() { 57 | // return moment(); 58 | // } 59 | // } 60 | }); 61 | 62 | // Ideas.attachSchema(IdeaSchema); 63 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/fonts/icomoon.eot -------------------------------------------------------------------------------- /public/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/fonts/icomoon.ttf -------------------------------------------------------------------------------- /public/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/fonts/icomoon.woff -------------------------------------------------------------------------------- /public/header-banner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/header-banner.gif -------------------------------------------------------------------------------- /public/header-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/idea_bulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/idea_bulb.png -------------------------------------------------------------------------------- /public/ideaotter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/ideaotter.png -------------------------------------------------------------------------------- /public/ideaotterbw.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/ideaotterbw.gif -------------------------------------------------------------------------------- /public/otters-404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonv3/IdeaOtter/6237c4d8564fd4ca5fdf0e4f39435d5a1e55a74f/public/otters-404.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Idea Otter 2 | 3 | > A place for gathering ideas. 4 | 5 | IdeaOtter is a Sandstorm app, you can install the most recent version from [their app store](https://apps.sandstorm.io/app/du1nkmp0edheyjq07rm8s1tzr4qs4ut7k24vc2ps0vzm30zp77y0). If you want instructions on how to get your own Sandstorm instance up, [check their documentation](https://docs.sandstorm.io/en/latest/administering/). 6 | 7 | # Dev 8 | 9 | IdeaOtter uses [Meteor](http://meteor.com/) as a framework. If you [set up Meteor](http://docs.meteor.com/#/full/quickstart), you can just run `meteor` in your terminal to get your own local version. 10 | 11 | Note, if you're looking to run this on a server - IdeaOtter offloads all authority and such to Sandstorm, meaning that what ever you add will be freely accessible by whomever has access to your server. 12 | -------------------------------------------------------------------------------- /server/boards.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('boards', function (options, searchString) { 2 | 3 | if (searchString === null || searchString === undefined) 4 | searchString = ''; 5 | 6 | Counts.publish(this, 'numberOfBoards', Boards.find({ 7 | 'name' : { '$regex' : '.*' + searchString || '' + '.*', '$options' : 'i' }, 8 | $and:[ 9 | {owner: this.userId}, 10 | {owner: {$exists: true}} 11 | ]}, options), { noReady: true }); 12 | 13 | return Boards.find({ 14 | 'name' : { '$regex' : '.*' + searchString || '' + '.*', '$options' : 'i' }, 15 | $and:[ 16 | {owner: this.userId}, 17 | {owner: {$exists: true}} 18 | ]}, options); 19 | }); 20 | -------------------------------------------------------------------------------- /server/ideas.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('ideas', function (options, searchString, boardFilter) { 2 | 3 | if (searchString === null || searchString === undefined) 4 | searchString = ''; 5 | 6 | if (boardFilter === null || boardFilter === undefined) 7 | boardFilter = ''; 8 | 9 | var filter = { 10 | 'idea' : 11 | { '$regex' : '.*' + (searchString !== undefined ? searchString : '') + '.*', 12 | '$options' : 'i' }, 13 | }; 14 | 15 | if (boardFilter !== '') 16 | filter.board = boardFilter; 17 | 18 | Counts.publish(this, 'numberOfIdeas', Ideas.find(filter), {noReady: true}); 19 | 20 | if (options && options.sort === undefined || options.sort === null) { 21 | options.sort = {date_added: -1}; 22 | } 23 | 24 | return Ideas.find(filter, options); 25 | }); 26 | -------------------------------------------------------------------------------- /server/methods.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('userData', function() { 2 | if (this.userId) { 3 | var user = Meteor.users.find({ 4 | _id: this.userId 5 | }, { fields: { 'services.sandstorm.permissions': 1 }}); 6 | 7 | return user; 8 | } else { 9 | this.ready(); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /server/startup/loadIdeas.js: -------------------------------------------------------------------------------- 1 | // Put startup code here 2 | --------------------------------------------------------------------------------