mirror of https://github.com/wekan/wekan
_,,ad8888888888bba,_ ,ad88888I888888888888888ba, ,88888888I88888888888888888888a, ,d888888888I8888888888888888888888b, d88888PP"""" ""YY88888888888888888888b, ,d88"'__,,--------,,,,.;ZZZY8888888888888, ,8IIl'" ;;l"ZZZIII8888888888, ,I88l;' ;lZZZZZ888III8888888, ,II88Zl;. ;llZZZZZ888888I888888, ,II888Zl;. .;;;;;lllZZZ888888I8888b ,II8888Z;; `;;;;;''llZZ8888888I8888, II88888Z;' .;lZZZ8888888I888b II88888Z; _,aaa, .,aaaaa,__.l;llZZZ88888888I888 II88888IZZZZZZZZZ, .ZZZZZZZZZZZZZZ;llZZ88888888I888, II88888IZZ<'(@@>Z| |ZZZ<'(@@>ZZZZ;;llZZ888888888I88I ,II88888; `""" ;| |ZZ; `""" ;;llZ8888888888I888 II888888l `;; .;llZZ8888888888I888, ,II888888Z; ;;; .;;llZZZ8888888888I888I III888888Zl; .., `;; ,;;lllZZZ88888888888I888 II88888888Z;;...;(_ _) ,;;;llZZZZ88888888888I888, II88888888Zl;;;;;' `--'Z;. .,;;;;llZZZZ88888888888I888b ]I888888888Z;;;;' ";llllll;..;;;lllZZZZ88888888888I8888, II888888888Zl.;;"Y88bd888P";;,..;lllZZZZZ88888888888I8888I II8888888888Zl;.; `"PPP";;;,..;lllZZZZZZZ88888888888I88888 II888888888888Zl;;. `;;;l;;;;lllZZZZZZZZW88888888888I88888 `II8888888888888Zl;. ,;;lllZZZZZZZZWMZ88888888888I88888 II8888888888888888ZbaalllZZZZZZZZZWWMZZZ8888888888I888888, `II88888888888888888b"WWZZZZZWWWMMZZZZZZI888888888I888888b `II88888888888888888;ZZMMMMMMZZZZZZZZllI888888888I8888888 `II8888888888888888 `;lZZZZZZZZZZZlllll888888888I8888888, II8888888888888888, `;lllZZZZllllll;;.Y88888888I8888888b, ,II8888888888888888b .;;lllllll;;;.;..88888888I88888888b, II888888888888888PZI;. .`;;;.;;;..; ...88888888I8888888888, II888888888888PZ;;';;. ;. .;. .;. .. Y8888888I88888888888b, ,II888888888PZ;;' `8888888I8888888888888b, II888888888' 888888I8888888888888888 ,II888888888 ,888888I8888888888888888 ,d88888888888 d888888I8888888888ZZZZZZ ,ad888888888888I 8888888I8888ZZZZZZZZZZZZ 888888888888888' 888888IZZZZZZZZZZZZZZZZZ 8888888888P'8P' Y888ZZZZZZZZZZZZZZZZZZZZ 888888888, " ,ZZZZZZZZZZZZZZZZZZZZZZZ 8888888888, ,ZZZZZZZZZZZZZZZZZZZZZZZZZZ 888888888888a, _ ,ZZZZZZZZZZZZZZZZZZZZ88888888 888888888888888ba,_d' ,ZZZZZZZZZZZZZZZZZ8888888888888 8888888888888888888888bbbaaa,,,______,ZZZZZZZZZZZZZZZ88888888888888888 88888888888888888888888888888888888ZZZZZZZZZZZZZZZ88888888888888888888 8888888888888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888 888888888888888888888888888888888ZZZZZZZZZZZZZZ88888888888888888888888 8888888888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888 88888888888888888888888888888ZZZZZZZZZZZZZZ888888888888888888888888888 8888888888888888888888888888ZZZZZZZZZZZZZZ88888888888888888 Normand 8 88888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888 Veilleux 8 8888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888888888pull/188/head
commit
2dbea30842
@ -0,0 +1,5 @@ |
||||
*~ |
||||
*.swp |
||||
.meteor-spk |
||||
.tx/ |
||||
*.sublime-workspace |
@ -0,0 +1,77 @@ |
||||
{ |
||||
"disallowSpacesInNamedFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInAnonymousFunctionExpression": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowSpacesInFunctionDeclaration": { |
||||
"beforeOpeningRoundBrace": true |
||||
}, |
||||
"disallowEmptyBlocks": true, |
||||
"disallowSpacesInsideArrayBrackets": true, |
||||
"disallowSpacesInsideParentheses": true, |
||||
"disallowQuotedKeysInObjects": "allButReserved", |
||||
"disallowSpaceAfterObjectKeys": true, |
||||
"disallowSpaceAfterPrefixUnaryOperators": [ |
||||
"++", |
||||
"--", |
||||
"+", |
||||
"-", |
||||
"~" |
||||
], |
||||
"disallowSpaceBeforePostfixUnaryOperators": true, |
||||
"disallowSpaceBeforeBinaryOperators": [ |
||||
"," |
||||
], |
||||
"disallowMixedSpacesAndTabs": true, |
||||
"disallowTrailingWhitespace": true, |
||||
"disallowTrailingComma": true, |
||||
"disallowYodaConditions": true, |
||||
"disallowKeywords": [ "with" ], |
||||
"disallowMultipleLineBreaks": true, |
||||
"disallowMultipleVarDecl": "exceptUndefined", |
||||
"requireSpaceBeforeBlockStatements": true, |
||||
"requireParenthesesAroundIIFE": true, |
||||
"requireSpacesInConditionalExpression": true, |
||||
"requireBlocksOnNewline": 1, |
||||
"requireCommaBeforeLineBreak": true, |
||||
"requireSpaceAfterPrefixUnaryOperators": [ |
||||
"!" |
||||
], |
||||
"requireSpaceBeforeBinaryOperators": true, |
||||
"requireSpaceAfterBinaryOperators": true, |
||||
"requireCamelCaseOrUpperCaseIdentifiers": true, |
||||
"requireLineFeedAtFileEnd": true, |
||||
"requireCapitalizedConstructors": true, |
||||
"requireDotNotation": true, |
||||
"requireSpacesInForStatement": true, |
||||
"requireSpaceBetweenArguments": true, |
||||
"requireCurlyBraces": [ |
||||
"do" |
||||
], |
||||
"requireSpaceAfterKeywords": [ |
||||
"if", |
||||
"else", |
||||
"for", |
||||
"while", |
||||
"do", |
||||
"switch", |
||||
"case", |
||||
"return", |
||||
"try", |
||||
"catch", |
||||
"typeof" |
||||
], |
||||
"safeContextKeyword": [ |
||||
"self", |
||||
"view" |
||||
], |
||||
"validateLineBreaks": "LF", |
||||
"validateQuoteMarks": "'", |
||||
"validateIndentation": 2, |
||||
"maximumLineLength": 80 |
||||
} |
@ -0,0 +1,82 @@ |
||||
{ |
||||
// JSHint options: http://jshint.com/docs/options/ |
||||
"maxerr": 50, |
||||
|
||||
// Enforcing |
||||
"camelcase": true, |
||||
"eqeqeq": true, |
||||
"undef": true, |
||||
"unused": true, |
||||
|
||||
// Environments |
||||
"browser": true, |
||||
"devel": true, |
||||
|
||||
// Authorized globals |
||||
"globals": { |
||||
// Meteor globals |
||||
"Meteor": false, |
||||
"DDP": false, |
||||
"Mongo": false, |
||||
"Session": false, |
||||
"Accounts": false, |
||||
"Template": false, |
||||
"Blaze": false, |
||||
"UI": false, |
||||
"Match": false, |
||||
"check": false, |
||||
"Tracker": false, |
||||
"Deps": false, |
||||
"ReactiveVar": false, |
||||
"EJSON": false, |
||||
"HTTP": false, |
||||
"Email": false, |
||||
"Assets": false, |
||||
"Handlebars": false, |
||||
"Package": false, |
||||
"App": false, |
||||
"Npm": false, |
||||
"Tinytest": false, |
||||
"Random": false, |
||||
"HTML": false, |
||||
|
||||
// Exported by packages we use |
||||
"_": false, |
||||
"$": false, |
||||
"Router": false, |
||||
"SimpleSchema": false, |
||||
"getSlug": false, |
||||
"Migrations": false, |
||||
"FS": false, |
||||
"BlazeComponent": false, |
||||
"TAPi18n": false, |
||||
"T9n": false, |
||||
"SubsManager": false, |
||||
"Mousetrap": false, |
||||
"Avatar": true, |
||||
|
||||
// Our collections |
||||
"Boards": true, |
||||
"Lists": true, |
||||
"Cards": true, |
||||
"CardComments": true, |
||||
"Activities": true, |
||||
"Attachments": true, |
||||
"Users": true, |
||||
"AccountsTemplates": true, |
||||
|
||||
// Our objects |
||||
"Utils": true, |
||||
"Popup": true, |
||||
"Filter": true, |
||||
"Sidebar": true, |
||||
"Mixins": true, |
||||
|
||||
// XXX Temp, we should remove these |
||||
"allowIsBoardAdmin": true, |
||||
"allowIsBoardMember": true, |
||||
"BoardSubsManager": true, |
||||
"currentlyOpenedForm": true, |
||||
"Emoji": true |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
# This file contains information which helps Meteor properly upgrade your |
||||
# app when you run 'meteor update'. You should check it into version control |
||||
# with your project. |
||||
|
||||
notices-for-0.9.0 |
||||
notices-for-0.9.1 |
||||
0.9.4-platform-file |
||||
notices-for-facebook-graph-api-2 |
@ -0,0 +1 @@ |
||||
local |
@ -0,0 +1,7 @@ |
||||
# This file contains a token that is unique to your project. |
||||
# Check it into your repository along with the rest of this directory. |
||||
# It can be used for purposes such as: |
||||
# - ensuring you don't accidentally deploy one app on top of another |
||||
# - providing package authors with aggregated statistics |
||||
|
||||
dvyihgykyzec6y1dpg |
@ -0,0 +1 @@ |
||||
|
@ -0,0 +1,53 @@ |
||||
# Meteor packages used by this project, one per line. |
||||
# |
||||
# 'meteor add' and 'meteor remove' will edit this file for you, |
||||
# but you can also edit it by hand. |
||||
|
||||
meteor-platform |
||||
|
||||
# Account system |
||||
accounts-password |
||||
kenton:accounts-sandstorm |
||||
service-configuration |
||||
useraccounts:unstyled |
||||
|
||||
# Compilers |
||||
mquandalle:jade |
||||
mquandalle:stylus |
||||
|
||||
# Collections |
||||
aldeed:collection2 |
||||
cfs:gridfs |
||||
cfs:standard-packages |
||||
dburles:collection-helpers |
||||
idmontie:migrations |
||||
matb33:collection-hooks |
||||
matteodem:easy-search |
||||
reywood:publish-composite |
||||
|
||||
# Utilities |
||||
alethes:pages |
||||
audit-argument-checks |
||||
iron:router |
||||
meteorhacks:subs-manager |
||||
mquandalle:autofocus |
||||
mquandalle:moment |
||||
ongoworks:speakingurl |
||||
raix:handlebar-helpers |
||||
random |
||||
reactive-dict |
||||
tap:i18n |
||||
tmeasday:presence |
||||
underscore |
||||
|
||||
# UI components |
||||
bengott:avatar |
||||
fortawesome:fontawesome |
||||
linto:jquery-ui |
||||
markdown |
||||
mousetrap:mousetrap |
||||
mquandalle:jquery-textcomplete |
||||
peerlibrary:blaze-components |
||||
reactive-var |
||||
seriousm:emoji-continued |
||||
useraccounts:core |
@ -0,0 +1,2 @@ |
||||
server |
||||
browser |
@ -0,0 +1 @@ |
||||
METEOR@1.1.0.2 |
@ -0,0 +1,120 @@ |
||||
accounts-base@1.2.0 |
||||
accounts-password@1.1.1 |
||||
aldeed:collection2@2.3.3 |
||||
aldeed:simple-schema@1.3.3 |
||||
alethes:pages@1.8.4 |
||||
audit-argument-checks@1.0.3 |
||||
autoupdate@1.2.1 |
||||
base64@1.0.3 |
||||
bengott:avatar@0.7.6 |
||||
binary-heap@1.0.3 |
||||
blaze@2.1.2 |
||||
blaze-tools@1.0.3 |
||||
boilerplate-generator@1.0.3 |
||||
callback-hook@1.0.3 |
||||
cfs:access-point@0.1.49 |
||||
cfs:base-package@0.0.30 |
||||
cfs:collection@0.5.5 |
||||
cfs:collection-filters@0.2.4 |
||||
cfs:data-man@0.0.6 |
||||
cfs:file@0.1.17 |
||||
cfs:gridfs@0.0.33 |
||||
cfs:http-methods@0.0.29 |
||||
cfs:http-publish@0.0.13 |
||||
cfs:power-queue@0.9.11 |
||||
cfs:reactive-list@0.0.9 |
||||
cfs:reactive-property@0.0.4 |
||||
cfs:standard-packages@0.5.9 |
||||
cfs:storage-adapter@0.2.2 |
||||
cfs:tempstore@0.1.5 |
||||
cfs:upload-http@0.0.20 |
||||
cfs:worker@0.1.4 |
||||
check@1.0.5 |
||||
coffeescript@1.0.6 |
||||
dburles:collection-helpers@1.0.3 |
||||
ddp@1.1.0 |
||||
deps@1.0.7 |
||||
ejson@1.0.6 |
||||
email@1.0.6 |
||||
fastclick@1.0.3 |
||||
fortawesome:fontawesome@4.3.0 |
||||
geojson-utils@1.0.3 |
||||
html-tools@1.0.4 |
||||
htmljs@1.0.4 |
||||
http@1.1.0 |
||||
id-map@1.0.3 |
||||
idmontie:migrations@1.0.0 |
||||
iron:controller@1.0.7 |
||||
iron:core@1.0.7 |
||||
iron:dynamic-template@1.0.7 |
||||
iron:layout@1.0.7 |
||||
iron:location@1.0.7 |
||||
iron:middleware-stack@1.0.7 |
||||
iron:router@1.0.7 |
||||
iron:url@1.0.7 |
||||
jparker:crypto-core@0.1.0 |
||||
jparker:crypto-md5@0.1.1 |
||||
jparker:gravatar@0.3.1 |
||||
jquery@1.11.3_2 |
||||
json@1.0.3 |
||||
kenton:accounts-sandstorm@0.1.3 |
||||
launch-screen@1.0.2 |
||||
less@1.0.14 |
||||
linto:jquery-ui@1.11.2 |
||||
livedata@1.0.13 |
||||
localstorage@1.0.3 |
||||
logging@1.0.7 |
||||
markdown@1.0.4 |
||||
matb33:collection-hooks@0.7.13 |
||||
matteodem:easy-search@1.5.6 |
||||
meteor@1.1.6 |
||||
meteor-platform@1.2.2 |
||||
meteorhacks:subs-manager@1.3.0 |
||||
minifiers@1.1.5 |
||||
minimongo@1.0.8 |
||||
mobile-status-bar@1.0.3 |
||||
mongo@1.1.0 |
||||
mongo-livedata@1.0.8 |
||||
mousetrap:mousetrap@1.4.6_1 |
||||
mquandalle:autofocus@1.0.0 |
||||
mquandalle:jade@0.4.3 |
||||
mquandalle:jade-compiler@0.4.3 |
||||
mquandalle:jquery-textcomplete@0.3.6_1 |
||||
mquandalle:moment@1.0.0 |
||||
mquandalle:stylus@1.1.1 |
||||
npm-bcrypt@0.7.8_2 |
||||
observe-sequence@1.0.6 |
||||
ongoworks:speakingurl@1.1.0 |
||||
ordered-dict@1.0.3 |
||||
peerlibrary:assert@0.2.5 |
||||
peerlibrary:base-component@0.8.0 |
||||
peerlibrary:blaze-components@0.10.0 |
||||
raix:eventemitter@0.1.2 |
||||
raix:handlebar-helpers@0.2.4 |
||||
random@1.0.3 |
||||
reactive-dict@1.1.0 |
||||
reactive-var@1.0.5 |
||||
reload@1.1.3 |
||||
retry@1.0.3 |
||||
reywood:publish-composite@1.3.6 |
||||
routepolicy@1.0.5 |
||||
seriousm:emoji-continued@1.4.0 |
||||
service-configuration@1.0.4 |
||||
session@1.1.0 |
||||
sha@1.0.3 |
||||
softwarerero:accounts-t9n@1.0.9 |
||||
spacebars@1.0.6 |
||||
spacebars-compiler@1.0.6 |
||||
srp@1.0.3 |
||||
stylus@1.0.7 |
||||
tap:i18n@1.4.1 |
||||
templating@1.1.1 |
||||
tmeasday:presence@1.0.6 |
||||
tracker@1.0.7 |
||||
ui@1.0.6 |
||||
underscore@1.0.3 |
||||
url@1.0.4 |
||||
useraccounts:core@1.9.1 |
||||
useraccounts:unstyled@1.9.1 |
||||
webapp@1.2.0 |
||||
webapp-hashing@1.0.3 |
@ -0,0 +1,7 @@ |
||||
language: node_js |
||||
node_js: |
||||
- "0.10" |
||||
before_install: |
||||
- "curl -L http://git.io/ejPSng | /bin/sh" |
||||
services: |
||||
- mongodb |
@ -0,0 +1,57 @@ |
||||
# Contributing |
||||
|
||||
We’re glad you’re interested in helping the LibreBoard project! We welcome bug |
||||
reports, enhancement ideas, and pull requests, in our GitHub bug tracker. Before |
||||
opening a new thread please verify that your issue hasn’t already been reported. |
||||
|
||||
<https://github.com/libreboard/libreboard> |
||||
|
||||
## Translations |
||||
|
||||
You are encouraged to translate (or improve the translation of) LibreBoard in |
||||
your locale language. For that purpose we rely on |
||||
[Transifex](https://www.transifex.com/projects/p/libreboard). So the first step |
||||
is to create a Transifex account if you don’t have one already. You can then |
||||
send a request to join one of the translation teams. If there we will create a |
||||
new one. |
||||
|
||||
Once you are in a team you can start translating the application. Please take a |
||||
look at the glossary so you can agree with other (present and future) |
||||
contributors on words to use to translate key concepts in the application like |
||||
“boards” and “cards”. |
||||
|
||||
The original application is written in English, and if you want to contribute to |
||||
the application itself, you are asked to fill the `i18n/en.i18n.json` file. When |
||||
you do that the new strings of text to translate automatically appears on |
||||
Transifex to be translated (the refresh may take a few hours). |
||||
|
||||
We pull all translations from Transifex before every new LibreBoard release |
||||
candidate, ask the translators to review the app, and pull all translations |
||||
again for the final release. |
||||
|
||||
## Installation |
||||
|
||||
LibreBoard is made with [Meteor](https://www.meteor.com). Thus the easiest way |
||||
to start hacking is by installing the framework, cloning the git repository, and |
||||
launching the application: |
||||
|
||||
```bash |
||||
$ curl https://install.meteor.com/ | sh # On Mac or Linux |
||||
$ git clone https://github.com/libreboard/libreboard.git |
||||
$ cd libreboard |
||||
$ meteor |
||||
``` |
||||
|
||||
As for any Meteor application, LibreBoard is automatically refreshed when you |
||||
change any file of the source code, just play with it to see how it behaves! |
||||
|
||||
## Style guide |
||||
|
||||
We follow the |
||||
[meteor style guide](https://github.com/meteor/meteor/wiki/Meteor-Style-Guide). |
||||
|
||||
Please read the meteor style guide before making any significant contribution. |
||||
|
||||
## Code organisation |
||||
|
||||
TODO |
@ -0,0 +1,9 @@ |
||||
FROM meteorhacks/meteord |
||||
MAINTAINER Maxime Quandalle <maxime@quandalle.com> |
||||
|
||||
# Run as you wish! |
||||
# |
||||
# sudo docker run -d \ |
||||
# -e "ROOT_URL=http://example.com" |
||||
# -e "MONGO_URL=mongodb://172.17.0.3:27017/libreboard-test" \ |
||||
# -p 8080:80 |
@ -0,0 +1,21 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2014-2015 Yasar Icli, Maxime Quandalle |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,25 @@ |
||||
# LibreBoard [![Build Status][travis-status]][travis-link] |
||||
|
||||
LibreBoard is an open-source *kanban* board that let you organize things in |
||||
cards, and cards in lists. You can use it alone, or with your team and family |
||||
thanks to our real-time synchronisation feature. Libreboard is a land of liberty |
||||
and you can implement all sort of workflows on it using tags, comments, member |
||||
assignation, and many more. |
||||
|
||||
[![Our roadmap is self-hosted on LibreBoard][thumbnail]][roadmap] |
||||
|
||||
Since it is a free software, you don’t have to trust us with your data and can |
||||
install LibreBoard on your own computer or server. In fact we encourage you to |
||||
do that by providing one-click installation for the |
||||
[Sandstorm](https://sandstorm.io) platform and verified |
||||
[Docker](https://www.docker.com) images. |
||||
|
||||
LibreBoard is released under the very permissive [MIT license](LICENSE), and |
||||
made with [Meteor](https://www.meteor.com). |
||||
|
||||
[Our roadmap is self-hosted on LibreBoard][roadmap] |
||||
|
||||
[travis-status]: https://travis-ci.org/libreboard/libreboard.svg |
||||
[travis-link]: https://travis-ci.org/libreboard/libreboard.svg |
||||
[thumbnail]: http://i.imgur.com/IIdHUmW.png |
||||
[roadmap]: http://libreboard.com/boards/MeSsFJaSqeuo9M6bs/libreboard-roadmap |
@ -0,0 +1,8 @@ |
||||
template(name="activities") |
||||
.js-sidebar-activities |
||||
//- We should use Template.dynamic here but there is a bug with |
||||
//- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30 |
||||
if $eq mode "board" |
||||
+boardActivities |
||||
else |
||||
+cardActivities |
@ -0,0 +1,77 @@ |
||||
var activitiesPerPage = 20; |
||||
|
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'activities'; |
||||
}, |
||||
|
||||
onCreated: function() { |
||||
var self = this; |
||||
// XXX Should we use ReactiveNumber?
|
||||
self.page = new ReactiveVar(1); |
||||
self.loadNextPageLocked = false; |
||||
var sidebar = self.componentParent(); // XXX for some reason not working
|
||||
sidebar.callFirstWith(null, 'resetNextPeak'); |
||||
self.autorun(function() { |
||||
var mode = self.data().mode; |
||||
var capitalizedMode = Utils.capitalize(mode); |
||||
var id = Session.get('current' + capitalizedMode); |
||||
var limit = self.page.get() * activitiesPerPage; |
||||
if (id === null) |
||||
return; |
||||
|
||||
self.subscribe('activities', mode, id, limit, function() { |
||||
self.loadNextPageLocked = false; |
||||
|
||||
// If the sibear peak hasn't increased, that mean that there are no more
|
||||
// activities, and we can stop calling new subscriptions.
|
||||
// XXX This is hacky! We need to know excatly and reactively how many
|
||||
// activities there are, we probably want to denormalize this number
|
||||
// dirrectly into card and board documents.
|
||||
var a = sidebar.callFirstWith(null, 'getNextPeak'); |
||||
sidebar.calculateNextPeak(); |
||||
var b = sidebar.callFirstWith(null, 'getNextPeak'); |
||||
if (a === b) { |
||||
sidebar.callFirstWith(null, 'resetNextPeak'); |
||||
} |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
loadNextPage: function() { |
||||
if (this.loadNextPageLocked === false) { |
||||
this.page.set(this.page.get() + 1); |
||||
this.loadNextPageLocked = true; |
||||
} |
||||
}, |
||||
|
||||
boardLabel: function() { |
||||
return TAPi18n.__('this-board'); |
||||
}, |
||||
|
||||
cardLabel: function() { |
||||
return TAPi18n.__('this-card'); |
||||
}, |
||||
|
||||
cardLink: function() { |
||||
var card = this.currentData().card(); |
||||
return Blaze.toHTML(HTML.A({ |
||||
href: card.absoluteUrl(), |
||||
'class': 'action-card' |
||||
}, card.title)); |
||||
}, |
||||
|
||||
memberLink: function() { |
||||
return Blaze.toHTMLWithData(Template.memberName, { |
||||
user: this.currentData().member() |
||||
}); |
||||
}, |
||||
|
||||
attachmentLink: function() { |
||||
var attachment = this.currentData().attachment(); |
||||
return Blaze.toHTML(HTML.A({ |
||||
href: attachment.url(), |
||||
'class': 'js-open-attachment-viewer' |
||||
}, attachment.name())); |
||||
} |
||||
}).register('activities'); |
@ -0,0 +1,30 @@ |
||||
Template.cardActivities.events({ |
||||
'click .js-edit-action': function(evt) { |
||||
var $this = $(evt.currentTarget); |
||||
var container = $this.parents('.phenom-comment'); |
||||
|
||||
// open and focus
|
||||
container.addClass('editing'); |
||||
container.find('textarea').focus(); |
||||
}, |
||||
'click .js-confirm-delete-action': function() { |
||||
CardComments.remove(this._id); |
||||
}, |
||||
'submit form': function(evt) { |
||||
var $this = $(evt.currentTarget); |
||||
var container = $this.parents('.phenom-comment'); |
||||
var text = container.find('textarea'); |
||||
|
||||
if ($.trim(text.val())) { |
||||
CardComments.update(this._id, { |
||||
$set: { |
||||
text: text.val() |
||||
} |
||||
}); |
||||
|
||||
// reset editing class
|
||||
$('.editing').removeClass('editing'); |
||||
} |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
@ -0,0 +1,154 @@ |
||||
<template name="boardActivities"> |
||||
{{# each currentBoard.activities }} |
||||
<div class="phenom phenom-action clearfix phenom-other"> |
||||
{{> userAvatar user=user size="extra-small" class="creator js-show-mem-menu" }} |
||||
<div class="phenom-desc"> |
||||
{{ > memberName user=user }} |
||||
|
||||
{{# if $eq activityType 'createBoard' }} |
||||
{{_ 'activity-created' boardLabel}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'createList' }} |
||||
{{_ 'activity-added' list.title boardLabel}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'archivedList' }} |
||||
{{_ 'activity-archived' list.title}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'createCard' }} |
||||
{{{_ 'activity-added' cardLink boardLabel}}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'archivedCard' }} |
||||
{{{_ 'activity-archived' cardLink}}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'restoredCard' }} |
||||
{{{_ 'activity-sent' cardLink boardLabel}}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'moveCard' }} |
||||
{{{_ 'activity-moved' cardLink oldList.title list.title}}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'addBoardMember' }} |
||||
{{{_ 'activity-added' memberLink boardLabel}}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'removeBoardMember' }} |
||||
{{{_ 'activity-excluded' memberLink boardLabel}}}. |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'joinMember' }} |
||||
{{# if $eq currentUser._id member._id }} |
||||
{{{_ 'activity-joined' cardLink}}}. |
||||
{{ else }} |
||||
{{{_ 'activity-added' memberLink cardLink}}}. |
||||
{{/if}} |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'unjoinMember' }} |
||||
{{# if $eq currentUser._id member._id }} |
||||
{{{_ 'activity-unjoined' cardLink}}}. |
||||
{{ else }} |
||||
{{{_ 'activity-removed' memberLink cardLink}}}. |
||||
{{/if}} |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'addComment' }} |
||||
<div class="phenom-desc"> |
||||
{{{_ 'activity-on' cardLink}}} |
||||
<div class="action-comment markeddown"> |
||||
<a href="{{ card.absoluteUrl }}" class="current-comment show tdn"> |
||||
<p>{{#viewer}}{{ comment.text }}{{/viewer}}</p> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
{{ /if }} |
||||
|
||||
{{# if $eq activityType 'addAttachment' }} |
||||
<div class="phenom-desc"> |
||||
{{{_ 'activity-attached' attachmentLink cardLink}}}. |
||||
</div> |
||||
{{ /if }} |
||||
</div> |
||||
<p class="phenom-meta quiet"> |
||||
<span class="date js-hide-on-sending"> |
||||
{{ moment createdAt }} |
||||
</span> |
||||
</p> |
||||
</div> |
||||
{{ /each }} |
||||
</template> |
||||
|
||||
<template name="cardActivities"> |
||||
{{# each currentCard.comments }} |
||||
<div class="phenom phenom-action clearfix phenom-comment"> |
||||
{{> userAvatar user=user size="small" class="creator js-show-mem-menu" }} |
||||
<form> |
||||
<div class="phenom-desc"> |
||||
{{ > memberName user=user }} |
||||
<div class="action-comment markeddown"> |
||||
<div class="current-comment"> |
||||
{{#viewer}}{{ text }}{{/viewer}} |
||||
</div> |
||||
<textarea class="js-text" tabindex="1">{{ text }}</textarea> |
||||
</div> |
||||
</div> |
||||
<div class="edit-controls clearfix"> |
||||
<input type="submit" class="primary confirm js-save-edit" value="{{_ 'save'}}" tabindex="2"> |
||||
</div> |
||||
</form> |
||||
<p class="phenom-meta quiet"> |
||||
<span class="date js-hide-on-sending">{{ moment createdAt }}</span> |
||||
{{# if currentUser }} |
||||
<span class="js-hide-on-sending"> |
||||
- <a href="#" class="js-edit-action">{{_ "edit"}}</a> |
||||
- <a href="#" class="js-confirm-delete-action">{{_ "delete"}}</a> |
||||
</span> |
||||
{{/ if }} |
||||
</p> |
||||
</div> |
||||
{{/each}} |
||||
|
||||
{{# each currentCard.activities }} |
||||
<div class="phenom phenom-action clearfix phenom-other"> |
||||
{{> userAvatar user=user size="extra-small" class="creator js-show-mem-menu" }} |
||||
{{ > memberName user=user }} |
||||
{{# if $eq activityType 'createCard' }} |
||||
{{_ 'activity-added' cardLabel list.title}}. |
||||
{{ /if }} |
||||
{{# if $eq activityType 'joinMember' }} |
||||
{{# if $eq currentUser._id member._id }} |
||||
{{_ 'activity-joined' cardLabel}}. |
||||
{{ else }} |
||||
{{{_ 'activity-added' cardLabel memberLink}}}. |
||||
{{/if}} |
||||
{{/if}} |
||||
{{# if $eq activityType 'unjoinMember' }} |
||||
{{# if $eq currentUser._id member._id }} |
||||
{{_ 'activity-unjoined' cardLabel}}. |
||||
{{ else }} |
||||
{{{_ 'activity-removed' cardLabel memberLink}}}. |
||||
{{/if}} |
||||
{{ /if }} |
||||
{{# if $eq activityType 'archivedCard' }} |
||||
{{_ 'activity-archived' cardLabel}}. |
||||
{{ /if }} |
||||
{{# if $eq activityType 'restoredCard' }} |
||||
{{_ 'activity-sent' cardLabel boardLabel}}. |
||||
{{/ if }} |
||||
{{# if $eq activityType 'moveCard' }} |
||||
{{_ 'activity-moved' cardLabel oldList.title list.title}}. |
||||
{{/ if }} |
||||
{{# if $eq activityType 'addAttachment' }} |
||||
{{{_ 'activity-attached' attachmentLink cardLabel}}}. |
||||
{{# if attachment.isImage }} |
||||
<img src="{{ attachment.url }}" class="attachment-image-preview"> |
||||
{{/if}} |
||||
{{/ if}} |
||||
</div> |
||||
{{/each}} |
||||
</template> |
@ -0,0 +1,33 @@ |
||||
//- |
||||
XXX This template can't be transformed into a component because it is |
||||
included by iron-router. That's a bug. |
||||
template(name="board") |
||||
+boardComponent |
||||
|
||||
template(name="boardComponent") |
||||
if this |
||||
.board-wrapper(class=colorClass) |
||||
.board-canvas(class=sidebarSize) |
||||
.lists.js-lists |
||||
each lists |
||||
+list(this) |
||||
if currentUser.isBoardMember |
||||
+addlistForm |
||||
+boardSidebar |
||||
if currentCard |
||||
+cardSidebar(currentCard) |
||||
else |
||||
+message(label="board-no-found") |
||||
|
||||
template(name="addlistForm") |
||||
.list.js-list.add-list.js-add-list |
||||
+inlinedForm(autoclose=false) |
||||
input.list-name-input(type="text" placeholder="{{_ 'add-list'}}" |
||||
autocomplete="off" autofocus) |
||||
div.edit-controls.clearfix |
||||
button.primary.confirm.js-save-edit(type="submit") {{_ 'save'}} |
||||
a.fa.fa-times.dark-hover.cancel.js-close-inlined-form |
||||
else |
||||
.js-open-inlined-form |
||||
i.fa.fa-plus |
||||
| {{_ 'add-list'}} |
@ -0,0 +1,70 @@ |
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'boardComponent'; |
||||
}, |
||||
|
||||
openNewListForm: function() { |
||||
this.componentChildren('addlistForm')[0].open(); |
||||
}, |
||||
|
||||
scrollLeft: function() { |
||||
// TODO
|
||||
}, |
||||
|
||||
onRendered: function() { |
||||
var self = this; |
||||
|
||||
self.scrollLeft(); |
||||
|
||||
if (Meteor.user().isBoardMember()) { |
||||
self.$('.js-lists').sortable({ |
||||
tolerance: 'pointer', |
||||
appendTo: '.js-lists', |
||||
helper: 'clone', |
||||
items: '.js-list:not(.add-list)', |
||||
placeholder: 'list placeholder', |
||||
start: function(event, ui) { |
||||
$('.list.placeholder').height(ui.item.height()); |
||||
Popup.close(); |
||||
}, |
||||
stop: function() { |
||||
self.$('.js-lists').find('.js-list:not(.add-list)').each( |
||||
function(i, list) { |
||||
var data = Blaze.getData(list); |
||||
Lists.update(data._id, { |
||||
$set: { |
||||
sort: i |
||||
} |
||||
}); |
||||
} |
||||
); |
||||
} |
||||
}); |
||||
|
||||
// If there is no data in the board (ie, no lists) we autofocus the list
|
||||
// creation form by clicking on the corresponding element.
|
||||
if (self.data().lists().count() === 0) { |
||||
this.openNewListForm(); |
||||
} |
||||
} |
||||
}, |
||||
|
||||
sidebarSize: function() { |
||||
var sidebar = this.componentChildren('boardSidebar')[0]; |
||||
if (Session.get('currentCard') !== null) |
||||
return 'next-large-sidebar'; |
||||
else if (sidebar && sidebar.isOpen()) |
||||
return 'next-small-sidebar'; |
||||
} |
||||
}).register('boardComponent'); |
||||
|
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'addlistForm'; |
||||
}, |
||||
|
||||
// Proxy
|
||||
open: function() { |
||||
this.componentChildren('inlinedForm')[0].open(); |
||||
} |
||||
}).register('addlistForm'); |
@ -0,0 +1,54 @@ |
||||
@import 'nib' |
||||
|
||||
.board-wrapper |
||||
left: 0 |
||||
top: 0 |
||||
bottom: 0 |
||||
right: 0 |
||||
position: absolute |
||||
overflow: hidden |
||||
|
||||
.board-canvas |
||||
position: absolute |
||||
left: 0 |
||||
right: 0 |
||||
top: 0 |
||||
bottom: 0 |
||||
transition: margin .1s |
||||
|
||||
&.next-small-sidebar |
||||
margin-right: 248px |
||||
|
||||
&.next-large-sidebar |
||||
opacity: 0.8 |
||||
margin-right: 496px |
||||
|
||||
.lists |
||||
align-items: flex-start |
||||
display: flex |
||||
flex-direction: row |
||||
margin-bottom: 10px |
||||
overflow-x: auto |
||||
overflow-y: hidden |
||||
padding-bottom: 10px |
||||
position: absolute |
||||
top: 0 |
||||
right: 0 |
||||
bottom: 0 |
||||
left: 0 |
||||
|
||||
&::-webkit-scrollbar |
||||
height: 13px |
||||
width: 13px |
||||
|
||||
&::-webkit-scrollbar-thumb:vertical, |
||||
&::-webkit-scrollbar-thumb:horizontal |
||||
background: rgba(255, 255, 255, .4) |
||||
|
||||
&::-webkit-scrollbar-track-piece |
||||
background: rgba(0, 0, 0, .15) |
||||
|
||||
&::-webkit-scrollbar-button |
||||
display: block |
||||
height: 5px |
||||
width: 5px |
@ -0,0 +1,34 @@ |
||||
// We define a set of six board colors that we took from the FlatUI palette. |
||||
// http://flatuicolors.com |
||||
|
||||
setBoardColor(color) |
||||
&#header, |
||||
&.sk-spinner div, |
||||
.board-backgrounds-list &.background-box, |
||||
&.pop-over .pop-over-list li a:hover, |
||||
.board-list & a |
||||
background-color: color |
||||
|
||||
& .minicard.is-selected .minicard-details |
||||
border-bottom: 2px solid color |
||||
|
||||
button[type=submit].primary, input[type=submit].primary |
||||
background-color: darken(color, 20%) |
||||
|
||||
.board-color-nephritis |
||||
setBoardColor(#27AE60) |
||||
|
||||
.board-color-pomegranate |
||||
setBoardColor(#C0392B) |
||||
|
||||
.board-color-belize |
||||
setBoardColor(#2980B9) |
||||
|
||||
.board-color-wisteria |
||||
setBoardColor(#8E44AD) |
||||
|
||||
.board-color-midnight |
||||
setBoardColor(#2C3E50) |
||||
|
||||
.board-color-pumpkin |
||||
setBoardColor(#E67E22) |
@ -0,0 +1,96 @@ |
||||
var toggleBoardStar = function(boardId) { |
||||
var queryType = Meteor.user().hasStarred(boardId) ? '$pull' : '$addToSet'; |
||||
var query = {}; |
||||
query[queryType] = { |
||||
'profile.starredBoards': boardId |
||||
}; |
||||
Meteor.users.update(Meteor.userId(), query); |
||||
}; |
||||
|
||||
Template.boards.events({ |
||||
'click .js-star-board': function(evt) { |
||||
toggleBoardStar(this._id); |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.headerBoard.events({ |
||||
'click .js-star-board': function() { |
||||
toggleBoardStar(this._id); |
||||
}, |
||||
'click .js-open-board-menu': Popup.open('boardMenu'), |
||||
'click #permission-level:not(.no-edit)': Popup.open('boardChangePermission'), |
||||
'click .js-filter-cards-indicator': function(evt) { |
||||
Session.set('currentWidget', 'filter'); |
||||
evt.preventDefault(); |
||||
}, |
||||
'click .js-filter-card-clear': function(evt) { |
||||
Filter.reset(); |
||||
evt.stopPropagation(); |
||||
} |
||||
}); |
||||
|
||||
Template.boardMenuPopup.events({ |
||||
'click .js-rename-board': Popup.open('boardChangeTitle'), |
||||
'click .js-change-board-color': Popup.open('boardChangeColor') |
||||
}); |
||||
|
||||
Template.createBoardPopup.events({ |
||||
'submit #CreateBoardForm': function(evt, t) { |
||||
var title = t.$('#boardNewTitle'); |
||||
|
||||
// trim value title
|
||||
if ($.trim(title.val())) { |
||||
// İnsert Board title
|
||||
var boardId = Boards.insert({ |
||||
title: title.val(), |
||||
permission: 'public' |
||||
}); |
||||
|
||||
// Go to Board _id
|
||||
Utils.goBoardId(boardId); |
||||
} |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.boardChangeTitlePopup.events({ |
||||
'submit #ChangeBoardTitleForm': function(evt, t) { |
||||
var title = t.$('.js-board-name').val().trim(); |
||||
if (title) { |
||||
Boards.update(this._id, { |
||||
$set: { |
||||
title: title |
||||
} |
||||
}); |
||||
Popup.close(); |
||||
} |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.boardChangePermissionPopup.events({ |
||||
'click .js-select': function(evt) { |
||||
var $this = $(evt.currentTarget); |
||||
var permission = $this.attr('name'); |
||||
|
||||
Boards.update(this._id, { |
||||
$set: { |
||||
permission: permission |
||||
} |
||||
}); |
||||
Popup.close(); |
||||
} |
||||
}); |
||||
|
||||
Template.boardChangeColorPopup.events({ |
||||
'click .js-select-background': function(evt) { |
||||
var currentBoardId = Session.get('currentBoard'); |
||||
Boards.update(currentBoardId, { |
||||
$set: { |
||||
color: this.toString() |
||||
} |
||||
}); |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
@ -0,0 +1,87 @@ |
||||
template(name="headerBoard") |
||||
h1.header-board-menu.js-open-board-menu |
||||
= title |
||||
span.fa.fa-angle-down |
||||
|
||||
.board-header-btns.left |
||||
unless isSandstorm |
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}board-header-starred{{/if}}" |
||||
title="{{# if isStarred }}{{_ 'click-to-unstar'}}{{ else }}{{_ 'click-to-star'}}{{/ if }} {{_ 'starred-boards-description'}}") |
||||
span.board-header-btn-icon.icon-sm.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") |
||||
//- XXX To implement |
||||
span.board-header-btn-text Starred |
||||
//- |
||||
XXX Normally we would disable this field for sandstorm, but we keep it |
||||
until sandstorm implements sharing capabilities |
||||
|
||||
a.board-header-btn.perms-btn.js-change-vis(class="{{#unless currentUser.isBoardAdmin}}no-edit{{/ unless}}" id="permission-level") |
||||
span.board-header-btn-icon.icon-sm.fa(class="{{#if isPublic}}fa-globe{{else}}fa-lock{{/if}}") |
||||
span.board-header-btn-text {{_ permission}} |
||||
|
||||
a.board-header-btn.js-search |
||||
span.board-header-btn-icon.icon-sm.fa.fa-tag |
||||
span.board-header-btn-text Labels |
||||
|
||||
//- XXX Clicking here should open a search field |
||||
a.board-header-btn.js-search |
||||
span.board-header-btn-icon.icon-sm.fa.fa-search |
||||
span.board-header-btn-text {{_ 'search'}} |
||||
|
||||
//- +boardMembersHeader |
||||
|
||||
template(name="boardMembersHeader") |
||||
.board-header-members |
||||
each currentBoard.members |
||||
+userAvatar(userId=userId draggable=true showBadges=true) |
||||
unless isSandstorm |
||||
if currentUser.isBoardAdmin |
||||
a.member.add-board-member.js-open-manage-board-members |
||||
i.fa.fa-plus |
||||
|
||||
template(name="boardMenuPopup") |
||||
ul.pop-over-list |
||||
li: a.js-rename-board {{_ 'rename-board'}} |
||||
li: a.js-change-board-color Change color |
||||
li: a Copy this board |
||||
li: a Rules |
||||
|
||||
template(name="boardChangeTitlePopup") |
||||
form#ChangeBoardTitleForm |
||||
label {{_ 'name'}} |
||||
input.js-board-name(type="text" value="{{ title }}" autofocus) |
||||
input.primary.wide.js-rename-board(type="submit" value="{{_ 'rename'}}") |
||||
|
||||
template(name="boardChangePermissionPopup") |
||||
ul.pop-over-list |
||||
li |
||||
a.js-select.light-hover(name="private") |
||||
span.icon-sm.fa.fa-lock.vis-icon |
||||
| {{_ 'private'}} |
||||
if check 'private' |
||||
span.icon-sm.fa.fa-check |
||||
span.sub-name {{_ 'private-desc'}} |
||||
li |
||||
a.js-select.light-hover(name="public") |
||||
span.icon-sm.fa.fa-globe.vis-icon |
||||
| {{_ 'public'}} |
||||
if check 'public' |
||||
span.icon-sm.fa.fa-check |
||||
span.sub-name {{_ 'public-desc'}} |
||||
|
||||
template(name="boardChangeColorPopup") |
||||
.board-backgrounds-list.clearfix |
||||
each backgroundColors |
||||
.board-background-select.js-select-background |
||||
span.background-box(class="board-color-{{this}}") |
||||
if isSelected |
||||
i.fa.fa-check |
||||
|
||||
template(name="createBoardPopup") |
||||
.content.clearfix |
||||
form#CreateBoardForm |
||||
label(for="boardNewTitle") {{_ 'title'}} |
||||
input#boardNewTitle.non-empty(type="text" name="name" placeholder="{{_ 'bucket-example'}}" autofocus) |
||||
p.quiet |
||||
span.icon-sm.fa.fa-globe |
||||
| {{{_ 'board-public-info'}}} |
||||
input.primary.wide(type="submit" value="{{_ 'create'}}") |
@ -0,0 +1,7 @@ |
||||
Template.headerBoard.helpers({ |
||||
isStarred: function() { |
||||
var boardId = Session.get('currentBoard'); |
||||
var user = Meteor.user(); |
||||
return boardId && user && user.hasStarred(boardId); |
||||
} |
||||
}); |
@ -0,0 +1,137 @@ |
||||
@import 'nib' |
||||
|
||||
.board-header { |
||||
height: auto; |
||||
overflow: hidden; |
||||
padding: 10px 30px 10px 8px; |
||||
position: relative; |
||||
transition: padding .15s ease-in; |
||||
} |
||||
|
||||
.board-header-btns { |
||||
position: relative; |
||||
display: block; |
||||
} |
||||
|
||||
.board-header-btn { |
||||
border-radius: 3px; |
||||
color: #f6f6f6; |
||||
cursor: default; |
||||
float: left; |
||||
font-size: 12px; |
||||
height: 30px; |
||||
line-height: 32px; |
||||
margin: 2px 4px 0 0; |
||||
overflow: hidden; |
||||
padding-left: 30px; |
||||
position: relative; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
.board-header-btn:empty { |
||||
display: none; |
||||
} |
||||
|
||||
.board-header-btn-without-icon { |
||||
padding-left: 8px; |
||||
} |
||||
|
||||
.board-header-btn-icon { |
||||
background-clip: content-box; |
||||
background-origin: content-box; |
||||
color: #f6f6f6 !important; |
||||
padding: 6px; |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
} |
||||
|
||||
.board-header-btn-text { |
||||
padding-right: 8px; |
||||
} |
||||
|
||||
.board-header-btn:not(.no-edit) .text { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.board-header-btn:not(.no-edit):hover { |
||||
background: rgba(0, 0, 0, .12); |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.board-header-btn:hover { |
||||
color: #f6f6f6; |
||||
} |
||||
|
||||
.board-header-btn.board-header-btn-enabled { |
||||
background-color: rgba(0, 0, 0, .1); |
||||
|
||||
&:hover { |
||||
background-color: rgba(0, 0, 0, .3); |
||||
} |
||||
|
||||
.board-header-btn-icon.icon-star { |
||||
color: #e6bf00 !important; |
||||
} |
||||
} |
||||
|
||||
.board-header-btn-name { |
||||
cursor: default; |
||||
font-size: 18px; |
||||
font-weight: 700; |
||||
line-height: 30px; |
||||
padding-left: 4px; |
||||
text-decoration: none; |
||||
|
||||
.board-header-btn-text { |
||||
padding-left: 6px; |
||||
} |
||||
} |
||||
|
||||
.board-header-btn-name-org-logo { |
||||
border-radius: 3px; |
||||
height: 30px; |
||||
left: 0; |
||||
position: absolute; |
||||
top: 0; |
||||
width: 30px; |
||||
|
||||
.board-header-btn-text { |
||||
padding-left: 32px; |
||||
} |
||||
} |
||||
|
||||
.board-header-btn-org-name { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
max-width: 400px; |
||||
} |
||||
|
||||
.board-header-btn-filter-indicator { |
||||
background: #3d990f; |
||||
padding-right: 30px; |
||||
color: #fff; |
||||
text-shadow: 0; |
||||
|
||||
&:hover { |
||||
background: #43a711 !important; |
||||
} |
||||
|
||||
.board-header-btn-icon-close { |
||||
background: #43a711; |
||||
border-top-left-radius: 0; |
||||
border-top-right-radius: 3px; |
||||
border-bottom-right-radius: 3px; |
||||
border-bottom-left-radius: 0; |
||||
color: #fff; |
||||
padding: 6px; |
||||
position: absolute; |
||||
right: 0; |
||||
top: 0; |
||||
|
||||
&:hover { |
||||
background: #48b512; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
Template.boards.helpers({ |
||||
boards: function() { |
||||
return Boards.find({}, { |
||||
sort: ['title'] |
||||
}); |
||||
}, |
||||
|
||||
starredBoards: function() { |
||||
var cursor = Boards.find({ |
||||
_id: { $in: Meteor.user().profile.starredBoards || [] } |
||||
}, { |
||||
sort: ['title'] |
||||
}); |
||||
return cursor.count() === 0 ? null : cursor; |
||||
}, |
||||
|
||||
isStarred: function() { |
||||
var user = Meteor.user(); |
||||
return user && user.hasStarred(this._id); |
||||
} |
||||
}); |
||||
|
||||
Template.boardChangePermissionPopup.helpers({ |
||||
check: function(perm) { |
||||
return this.permission === perm; |
||||
} |
||||
}); |
||||
|
||||
Template.boardChangeColorPopup.helpers({ |
||||
backgroundColors: function() { |
||||
return Boards.simpleSchema()._schema.color.allowedValues; |
||||
}, |
||||
|
||||
isSelected: function() { |
||||
var currentBoard = Boards.findOne(Session.get('currentBoard')); |
||||
return currentBoard.color === this.toString(); |
||||
} |
||||
}); |
||||
|
||||
Blaze.registerHelper('currentBoard', function() { |
||||
var boardId = Session.get('currentBoard'); |
||||
if (boardId) { |
||||
return Boards.findOne(boardId); |
||||
} |
||||
}); |
@ -0,0 +1,14 @@ |
||||
template(name="boards") |
||||
if boards |
||||
ul.board-list.clearfix |
||||
each boards |
||||
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) |
||||
a.js-open-board(href="{{ pathFor route='Board' boardId=_id }}") |
||||
span.details |
||||
span.board-list-item-name= title |
||||
i.fa.fa-star-o.js-star-board( |
||||
class="{{#if isStarred}}is-star-active{{/if}}" |
||||
title="{{_ 'star-board-title'}}") |
||||
else |
||||
p.quiet {{_ 'no-boards'}} |
||||
button.js-add-board {{_ 'add-board'}} |
@ -0,0 +1,85 @@ |
||||
.board-list |
||||
margin: 25px auto |
||||
width: 1200px |
||||
|
||||
li |
||||
float: left |
||||
width: 25% |
||||
box-sizing: border-box |
||||
position: relative |
||||
|
||||
&.starred .fa-star-o |
||||
opacity: 1 |
||||
|
||||
a |
||||
background-color: #999 |
||||
color: #f6f6f6 |
||||
height: 90px |
||||
font-size: 16px |
||||
line-height: 22px |
||||
border-radius: 3px |
||||
display: block |
||||
font-weight: 700 |
||||
min-height: 18px |
||||
padding: 8px 12px 8px 12px |
||||
margin: 0 16px 16px 0 |
||||
position: relative |
||||
text-decoration: none |
||||
|
||||
&.tile |
||||
background-size: auto |
||||
background-repeat: repeat |
||||
|
||||
.details |
||||
height: 84px |
||||
padding-right: 36px |
||||
bottom: 0 |
||||
left: 0 |
||||
overflow: hidden |
||||
padding: 9px 12px |
||||
position: absolute |
||||
right: 0 |
||||
top: 0 |
||||
|
||||
.board-list-item-sub-name |
||||
color: rgba(255, 255, 255, .5) |
||||
display: block |
||||
font-size: 14px |
||||
font-weight: 400 |
||||
line-height: 22px |
||||
|
||||
.fa-star-o |
||||
bottom: 0 |
||||
font-size: 14px |
||||
height: 18px |
||||
line-height: 18px |
||||
opacity: 0 |
||||
padding: 9px 9px |
||||
position: absolute |
||||
right: 0 |
||||
top: 0 |
||||
transition-duration: .15s |
||||
transition-property: color, font-size, background |
||||
|
||||
.is-star-active |
||||
color: #e6bf00 |
||||
|
||||
li:hover a |
||||
color: #f6f6f6 |
||||
|
||||
.fa-star-o |
||||
color: #fff |
||||
opacity: .75 |
||||
|
||||
&:hover |
||||
font-size: 18px |
||||
opacity: 1 |
||||
|
||||
&.is-star-active |
||||
color: #e6bf00 |
||||
opacity: 1 |
||||
|
||||
&:hover |
||||
color: #ffd91a |
||||
font-size: 16px |
||||
opacity: 1 |
@ -0,0 +1,34 @@ |
||||
Meteor.subscribe('boards'); |
||||
|
||||
BoardSubsManager = new SubsManager(); |
||||
|
||||
Router.route('/boards', { |
||||
name: 'Boards', |
||||
template: 'boards', |
||||
authenticated: true, |
||||
onBeforeAction: function() { |
||||
Session.set('currentBoard', ''); |
||||
Filter.reset(); |
||||
this.next(); |
||||
} |
||||
}); |
||||
|
||||
Router.route('/boards/:_id/:slug', { |
||||
name: 'Board', |
||||
template: 'board', |
||||
onAfterAction: function() { |
||||
Session.set('sidebarIsOpen', true); |
||||
Session.set('currentWidget', 'home'); |
||||
Session.set('menuWidgetIsOpen', false); |
||||
}, |
||||
waitOn: function() { |
||||
var params = this.params; |
||||
Session.set('currentBoard', params._id); |
||||
Session.set('currentCard', null); |
||||
|
||||
return BoardSubsManager.subscribe('board', params._id, params.slug); |
||||
}, |
||||
data: function() { |
||||
return Boards.findOne(this.params._id); |
||||
} |
||||
}); |
@ -0,0 +1,47 @@ |
||||
template(name="cardSidebar") |
||||
.card-sidebar.sidebar |
||||
.card-detail.sidebar-content.js-card-sidebar-content |
||||
if cover |
||||
.card-detail-cover(style="background-image: url({{ card.cover.url }})") |
||||
.card-detail-header(class="{{#if currentUser.isBoardMember}}editable{{/if}}") |
||||
a.js-close-card-detail |
||||
i.fa.fa-times |
||||
h2.card-detail-title.js-card-title= title |
||||
p.card-detail-list.js-move-card |
||||
| {{_ 'in-list'}} |
||||
a.card-detail-list-title( |
||||
class="{{#if currentUser.isBoardMember}}js-open-move-from-header is-editable{{/if}}") |
||||
= list.title |
||||
hr |
||||
//- if card.members |
||||
.card-detail-item.card-detail-item-members.clearfix.js-card-detail-members |
||||
h3.card-detail-item-header {{_ 'members'}} |
||||
.js-card-detail-members-list.clearfix |
||||
each members |
||||
+userAvatar(userId=this size="small" cardId=../_id) |
||||
a.card-detail-item-add-button.dark-hover.js-details-edit-members |
||||
i.fa.fa-plus |
||||
//- We should use "editable" to avoide repetiting ourselves |
||||
.clearfix |
||||
if currentUser.isBoardMember |
||||
h3 Description |
||||
+inlinedForm(classNames="js-card-description") |
||||
i.fa.fa-times.js-close-inlined-form |
||||
textarea(autofocus)= description |
||||
button(type="submit") {{_ 'edit'}} |
||||
else |
||||
.js-open-inlined-form |
||||
a {{_ 'edit'}} |
||||
+viewer |
||||
= description |
||||
else if description |
||||
h3 Description |
||||
+viewer |
||||
= description |
||||
hr |
||||
if attachments.count |
||||
+WindowAttachmentsModule(card=this) |
||||
+WindowActivityModule(card=this) |
||||
|
||||
template(name="moveCardPopup") |
||||
+boardLists |
@ -0,0 +1,103 @@ |
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'cardSidebar'; |
||||
}, |
||||
|
||||
mixins: function() { |
||||
return [Mixins.InfiniteScrolling]; |
||||
}, |
||||
|
||||
calculateNextPeak: function() { |
||||
var altitude = this.find('.js-card-sidebar-content').scrollHeight; |
||||
this.callFirstWith(this, 'setNextPeak', altitude); |
||||
}, |
||||
|
||||
reachNextPeak: function() { |
||||
var activitiesComponent = this.componentChildren('activities')[0]; |
||||
activitiesComponent.loadNextPage(); |
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
'click .js-move-card': Popup.open('moveCard'), |
||||
'submit .js-card-description': function(evt) { |
||||
evt.preventDefault(); |
||||
var cardId = Session.get('currentCard'); |
||||
var form = this.componentChildren('inlinedForm')[0]; |
||||
var newDescription = form.getValue(); |
||||
Cards.update(cardId, { |
||||
$set: { |
||||
description: newDescription |
||||
} |
||||
}); |
||||
form.close(); |
||||
}, |
||||
'click .js-close-card-detail': function() { |
||||
Utils.goBoardId(Session.get('currentBoard')); |
||||
}, |
||||
'click .editable .js-card-title': function(event, t) { |
||||
var editable = t.$('.card-detail-title'); |
||||
|
||||
// add class editing and focus
|
||||
$('.editing').removeClass('editing'); |
||||
editable.addClass('editing'); |
||||
editable.find('#title').focus(); |
||||
}, |
||||
'click .js-edit-desc': function(event, t) { |
||||
var editable = t.$('.card-detail-item'); |
||||
|
||||
// editing remove based and add current editing.
|
||||
$('.editing').removeClass('editing'); |
||||
editable.addClass('editing'); |
||||
editable.find('#desc').focus(); |
||||
|
||||
event.preventDefault(); |
||||
}, |
||||
'click .js-cancel-edit': function(event, t) { |
||||
// remove editing hide.
|
||||
$('.editing').removeClass('editing'); |
||||
}, |
||||
'submit #WindowTitleEdit': function(event, t) { |
||||
var title = t.find('#title').value; |
||||
if ($.trim(title)) { |
||||
Cards.update(this.card._id, { |
||||
$set: { |
||||
title: title |
||||
} |
||||
}, function (err, res) { |
||||
if (!err) $('.editing').removeClass('editing'); |
||||
}); |
||||
} |
||||
|
||||
event.preventDefault(); |
||||
}, |
||||
'submit #WindowDescEdit': function(event, t) { |
||||
Cards.update(this.card._id, { |
||||
$set: { |
||||
description: t.find('#desc').value |
||||
} |
||||
}, function(err) { |
||||
if (!err) $('.editing').removeClass('editing'); |
||||
}); |
||||
event.preventDefault(); |
||||
}, |
||||
'click .member': Popup.open('cardMember'), |
||||
'click .js-details-edit-members': Popup.open('cardMembers'), |
||||
'click .js-details-edit-labels': Popup.open('cardLabels') |
||||
}]; |
||||
} |
||||
}).register('cardSidebar'); |
||||
|
||||
Template.moveCardPopup.events({ |
||||
'click .js-select-list': function() { |
||||
// XXX We should *not* get the currentCard from the global state, but
|
||||
// instead from a “component” state.
|
||||
var cardId = Session.get('currentCard'); |
||||
var newListId = this._id; |
||||
Cards.update(cardId, { |
||||
$set: { |
||||
listId: newListId |
||||
} |
||||
}); |
||||
} |
||||
}); |
@ -0,0 +1,161 @@ |
||||
@import 'nib' |
||||
|
||||
.card-detail.sidebar-content |
||||
width: 496px - 2 * 20px |
||||
top: -46px !important |
||||
z-index: 20 !important |
||||
// XXX Animate apparition |
||||
|
||||
.card-detail-header |
||||
background: #F7F7F7 |
||||
border-bottom: 1px solid darken(white, 10%) |
||||
position: absolute |
||||
min-height: 38px |
||||
top: 0 |
||||
left: 0 |
||||
right: 0 |
||||
padding 7px 20px 0 |
||||
|
||||
i.fa |
||||
float: right |
||||
font-size: 1.3em |
||||
color: darken(white, 35%) |
||||
margin-top: 7px |
||||
|
||||
.card-detail-title |
||||
font-weight: bold |
||||
font-size: 1.7em |
||||
margin: 3px 0 0 |
||||
padding: 0 |
||||
|
||||
.card-detail-list |
||||
font-size: 0.85em |
||||
margin-bottom: 3px |
||||
|
||||
a.card-detail-list-title |
||||
font-weight: bold |
||||
|
||||
&.is-editable |
||||
display: inline-block |
||||
background: darken(white, 10%) |
||||
border-radius: 3px |
||||
padding: 0px 5px |
||||
|
||||
.new-comment |
||||
position: relative |
||||
margin: 0 0 20px 38px |
||||
|
||||
.member |
||||
opacity: .7 |
||||
position: absolute |
||||
top: 1px |
||||
left: -38px |
||||
|
||||
.helper |
||||
bottom: 0 |
||||
display: none |
||||
position: absolute |
||||
right: 9px |
||||
|
||||
&.focus |
||||
|
||||
.member |
||||
opacity: 1 |
||||
|
||||
.helper |
||||
display: inline-block |
||||
|
||||
.new-comment-input |
||||
min-height: 108px |
||||
color: #4d4d4d |
||||
cursor: auto |
||||
overflow: hidden |
||||
word-wrap: break-word |
||||
|
||||
.too-long |
||||
margin-top: 8px |
||||
|
||||
.new-comment-input |
||||
background-color: #fff |
||||
border: 0 |
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .23) |
||||
color: #8c8c8c |
||||
height: 36px |
||||
margin: 4px 4px 6px 0 |
||||
padding: 9px 11px |
||||
width: 100% |
||||
|
||||
&:hover, |
||||
&:focus |
||||
background-color: #fff |
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .33) |
||||
border: 0 |
||||
cursor: pointer |
||||
|
||||
&:focus |
||||
cursor: auto |
||||
|
||||
.list-voters.compact .voter |
||||
position: relative |
||||
min-height: 36px |
||||
|
||||
.member |
||||
left: 0 |
||||
position: absolute |
||||
top: 0 |
||||
|
||||
.title |
||||
display: block |
||||
line-height: 30px |
||||
left: 0 |
||||
overflow: hidden |
||||
padding-left: 38px |
||||
position: absolute |
||||
text-overflow: ellipsis |
||||
top: 0 |
||||
white-space: nowrap |
||||
width: 230px |
||||
|
||||
.list-voters .title |
||||
display: none |
||||
|
||||
.card-composer |
||||
padding-bottom: 8px |
||||
|
||||
.cc-controls |
||||
margin-top: 1px |
||||
|
||||
input[type="submit"] |
||||
float: left |
||||
margin-top: 0 |
||||
padding: 5px 18px |
||||
|
||||
.icon-lg |
||||
float: left |
||||
|
||||
.cc-opt |
||||
float: right |
||||
|
||||
.minicard-placeholder, |
||||
.minicard.placeholder |
||||
background: silver |
||||
border: none |
||||
min-height: 18px |
||||
|
||||
.hook |
||||
height: 18px |
||||
position: absolute |
||||
right: 0 |
||||
top: 0 |
||||
width: 18px |
||||
|
||||
input[type="text"].attachment-add-link-input |
||||
float: left |
||||
margin: 0 0 8px |
||||
width: 80% |
||||
|
||||
input[type="submit"].attachment-add-link-submit |
||||
float: left |
||||
margin: 0 0 8px 4px |
||||
padding: 6px 12px |
||||
width: 18% |
@ -0,0 +1,285 @@ |
||||
// Template.cards.events({
|
||||
// // 'click .js-cancel': function(event, t) {
|
||||
// // var composer = t.$('.card-composer');
|
||||
|
||||
// // // Keep the old value in memory to display it again next time
|
||||
// // var inputCacheKey = "addCard-" + this.listId;
|
||||
// // var oldValue = composer.find('.js-card-title').val();
|
||||
// // InputsCache.set(inputCacheKey, oldValue);
|
||||
|
||||
// // // add composer hide class
|
||||
// // composer.addClass('hide');
|
||||
// // composer.find('.js-card-title').val('');
|
||||
|
||||
// // // remove hide open link class
|
||||
// // $('.js-open-card-composer').removeClass('hide');
|
||||
// // },
|
||||
// 'submit': function(evt, tpl) {
|
||||
// evt.preventDefault();
|
||||
// var textarea = $(evt.currentTarget).find('textarea');
|
||||
// var title = textarea.val();
|
||||
// var lastCard = tpl.find('.js-minicard:last-child');
|
||||
// var sort;
|
||||
// if (lastCard === null) {
|
||||
// sort = 0;
|
||||
// } else {
|
||||
// sort = Blaze.getData(lastCard).sort + 1;
|
||||
// }
|
||||
// // debugger
|
||||
|
||||
// // Clear the form in-memory cache
|
||||
// // var inputCacheKey = "addCard-" + this.listId;
|
||||
// // InputsCache.set(inputCacheKey, '');
|
||||
|
||||
// // title trim if not empty then
|
||||
// if ($.trim(title)) {
|
||||
// Cards.insert({
|
||||
// title: title,
|
||||
// listId: Template.currentData().listId,
|
||||
// boardId: Template.currentData().board._id,
|
||||
// sort: sort
|
||||
// }, function(err, _id) {
|
||||
// // In case the filter is active we need to add the newly
|
||||
// // inserted card in the list of exceptions -- cards that are
|
||||
// // not filtered. Otherwise the card will disappear instantly.
|
||||
// // See https://github.com/libreboard/libreboard/issues/80
|
||||
// Filter.addException(_id);
|
||||
// });
|
||||
|
||||
// // empty and focus.
|
||||
// textarea.val('').focus();
|
||||
|
||||
// // focus complete then scroll top
|
||||
// Utils.Scroll(tpl.find('.js-minicards')).top(1000, true);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// Template.cards.events({
|
||||
// 'click .member': Popup.open('cardMember')
|
||||
// });
|
||||
|
||||
Template.cardMemberPopup.events({ |
||||
'click .js-remove-member': function() { |
||||
Cards.update(this.cardId, {$pull: {members: this.userId}}); |
||||
Popup.close(); |
||||
} |
||||
}); |
||||
|
||||
Template.WindowActivityModule.events({ |
||||
'click .js-new-comment:not(.focus)': function(evt) { |
||||
var $this = $(evt.currentTarget); |
||||
$this.addClass('focus'); |
||||
}, |
||||
'submit #CommentForm': function(evt, t) { |
||||
var text = t.$('.js-new-comment-input'); |
||||
if ($.trim(text.val())) { |
||||
CardComments.insert({ |
||||
boardId: this.card.boardId, |
||||
cardId: this.card._id, |
||||
text: text.val() |
||||
}); |
||||
text.val(''); |
||||
$('.focus').removeClass('focus'); |
||||
} |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.WindowSidebarModule.events({ |
||||
'click .js-change-card-members': Popup.open('cardMembers'), |
||||
'click .js-edit-labels': Popup.open('cardLabels'), |
||||
'click .js-archive-card': function(evt) { |
||||
// Update
|
||||
Cards.update(this.card._id, { |
||||
$set: { |
||||
archived: true |
||||
} |
||||
}); |
||||
evt.preventDefault(); |
||||
}, |
||||
'click .js-unarchive-card': function(evt) { |
||||
Cards.update(this.card._id, { |
||||
$set: { |
||||
archived: false |
||||
} |
||||
}); |
||||
evt.preventDefault(); |
||||
}, |
||||
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() { |
||||
Cards.remove(this.card._id); |
||||
|
||||
// redirect board
|
||||
Utils.goBoardId(this.card.board()._id); |
||||
Popup.close(); |
||||
}), |
||||
'click .js-more-menu': Popup.open('cardMore'), |
||||
'click .js-attach': Popup.open('cardAttachments') |
||||
}); |
||||
|
||||
Template.WindowAttachmentsModule.events({ |
||||
'click .js-attach': Popup.open('cardAttachments'), |
||||
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', |
||||
function() { |
||||
Attachments.remove(this._id); |
||||
Popup.close(); |
||||
} |
||||
), |
||||
// If we let this event bubble, Iron-Router will handle it and empty the
|
||||
// page content, see #101.
|
||||
'click .js-open-viewer, click .js-download': function(event) { |
||||
event.stopPropagation(); |
||||
}, |
||||
'click .js-add-cover': function() { |
||||
Cards.update(this.cardId, { $set: { coverId: this._id } }); |
||||
}, |
||||
'click .js-remove-cover': function() { |
||||
Cards.update(this.cardId, { $unset: { coverId: '' } }); |
||||
} |
||||
}); |
||||
|
||||
Template.cardMembersPopup.events({ |
||||
'click .js-select-member': function(evt) { |
||||
var cardId = Template.parentData(2).data._id; |
||||
var memberId = this.userId; |
||||
var operation; |
||||
if (Cards.find({ _id: cardId, members: memberId}).count() === 0) |
||||
operation = '$addToSet'; |
||||
else |
||||
operation = '$pull'; |
||||
|
||||
var query = {}; |
||||
query[operation] = { |
||||
members: memberId |
||||
}; |
||||
Cards.update(cardId, query); |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.cardLabelsPopup.events({ |
||||
'click .js-select-label': function(evt) { |
||||
var cardId = Template.parentData(2).data._id; |
||||
var labelId = this._id; |
||||
var operation; |
||||
if (Cards.find({ _id: cardId, labelIds: labelId}).count() === 0) |
||||
operation = '$addToSet'; |
||||
else |
||||
operation = '$pull'; |
||||
|
||||
var query = {}; |
||||
query[operation] = { |
||||
labelIds: labelId |
||||
}; |
||||
Cards.update(cardId, query); |
||||
evt.preventDefault(); |
||||
}, |
||||
'click .js-edit-label': Popup.open('editLabel'), |
||||
'click .js-add-label': Popup.open('createLabel') |
||||
}); |
||||
|
||||
Template.formLabel.events({ |
||||
'click .js-palette-color': function(evt) { |
||||
var $this = $(evt.currentTarget); |
||||
|
||||
// hide selected ll colors
|
||||
$('.js-palette-select').addClass('hide'); |
||||
|
||||
// show select color
|
||||
$this.find('.js-palette-select').removeClass('hide'); |
||||
} |
||||
}); |
||||
|
||||
Template.createLabelPopup.events({ |
||||
// Create the new label
|
||||
'submit .create-label': function(evt, tpl) { |
||||
var name = tpl.$('#labelName').val().trim(); |
||||
var boardId = Session.get('currentBoard'); |
||||
var selectLabelDom = tpl.$('.js-palette-select:not(.hide)').get(0); |
||||
var selectLabel = Blaze.getData(selectLabelDom); |
||||
Boards.update(boardId, { |
||||
$push: { |
||||
labels: { |
||||
_id: Random.id(6), |
||||
name: name, |
||||
color: selectLabel.color |
||||
} |
||||
} |
||||
}); |
||||
Popup.back(); |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.editLabelPopup.events({ |
||||
'click .js-delete-label': Popup.afterConfirm('deleteLabel', function() { |
||||
var boardId = Session.get('currentBoard'); |
||||
Boards.update(boardId, { |
||||
$pull: { |
||||
labels: { |
||||
_id: this._id |
||||
} |
||||
} |
||||
}); |
||||
Popup.back(2); |
||||
}), |
||||
'submit .edit-label': function(evt, tpl) { |
||||
var name = tpl.$('#labelName').val().trim(); |
||||
var boardId = Session.get('currentBoard'); |
||||
var getLabel = Utils.getLabelIndex(boardId, this._id); |
||||
var selectLabelDom = tpl.$('.js-palette-select:not(.hide)').get(0); |
||||
var selectLabel = Blaze.getData(selectLabelDom); |
||||
var $set = {}; |
||||
|
||||
// set label index
|
||||
$set[getLabel.key('name')] = name; |
||||
|
||||
// set color
|
||||
$set[getLabel.key('color')] = selectLabel.color; |
||||
|
||||
// update
|
||||
Boards.update(boardId, { $set: $set }); |
||||
|
||||
// return to the previous popup view trigger
|
||||
Popup.back(); |
||||
|
||||
evt.preventDefault(); |
||||
}, |
||||
'click .js-select-label': function() { |
||||
Cards.remove(this.cardId); |
||||
|
||||
// redirect board
|
||||
Utils.goBoardId(this.boardId); |
||||
} |
||||
}); |
||||
|
||||
Template.cardMorePopup.events({ |
||||
'click .js-delete': Popup.afterConfirm('cardDelete', function() { |
||||
Cards.remove(this.card._id); |
||||
|
||||
// redirect board
|
||||
Utils.goBoardId(this.card.board()._id); |
||||
}) |
||||
}); |
||||
|
||||
Template.cardAttachmentsPopup.events({ |
||||
'change .js-attach-file': function(evt) { |
||||
var card = this.card; |
||||
FS.Utility.eachFile(evt, function(f) { |
||||
var file = new FS.File(f); |
||||
|
||||
// set Ids
|
||||
file.boardId = card.boardId; |
||||
file.cardId = card._id; |
||||
|
||||
// upload file
|
||||
Attachments.insert(file); |
||||
|
||||
Popup.close(); |
||||
}); |
||||
}, |
||||
'click .js-computer-upload': function(evt, t) { |
||||
t.find('.js-attach-file').click(); |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
@ -0,0 +1,48 @@ |
||||
Template.cardMembersPopup.helpers({ |
||||
isCardMember: function() { |
||||
var cardId = Template.parentData()._id; |
||||
var cardMembers = Cards.findOne(cardId).members || []; |
||||
return _.contains(cardMembers, this.userId); |
||||
}, |
||||
user: function() { |
||||
return Users.findOne(this.userId); |
||||
} |
||||
}); |
||||
|
||||
Template.cardLabelsPopup.helpers({ |
||||
isLabelSelected: function(cardId) { |
||||
return _.contains(Cards.findOne(cardId).labelIds, this._id); |
||||
} |
||||
}); |
||||
|
||||
var labelColors; |
||||
Meteor.startup(function() { |
||||
labelColors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; |
||||
}); |
||||
|
||||
Template.createLabelPopup.helpers({ |
||||
// This is the default color for a new label. We search the first color that
|
||||
// is not already used in the board (although it's not a problem if two
|
||||
// labels have the same color).
|
||||
defaultColor: function() { |
||||
var labels = this.labels || this.card.board().labels; |
||||
var usedColors = _.pluck(labels, 'color'); |
||||
var availableColors = _.difference(labelColors, usedColors); |
||||
return availableColors.length > 1 ? availableColors[0] : 'green'; |
||||
} |
||||
}); |
||||
|
||||
Template.formLabel.helpers({ |
||||
labels: function() { |
||||
return _.map(labelColors, function(color) { |
||||
return { color: color, name: '' }; |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
Blaze.registerHelper('currentCard', function() { |
||||
var cardId = Session.get('currentCard'); |
||||
if (cardId) { |
||||
return Cards.findOne(cardId); |
||||
} |
||||
}); |
@ -0,0 +1,183 @@ |
||||
@import 'nib' |
||||
|
||||
// XXX Use .board-widget-labels as a flexbox container |
||||
.card-label |
||||
background-color: #b3b3b3 |
||||
border-radius: 4px |
||||
color: white |
||||
display: inline-block |
||||
font-weight: 700 |
||||
font-size: 13px |
||||
margin-right: 4px |
||||
padding: 3px 8px |
||||
position:relative |
||||
max-width: 100% |
||||
min-width: 8px |
||||
overflow: ellipsis |
||||
height: 18px |
||||
|
||||
&:hover |
||||
color: white |
||||
|
||||
.card-label-green |
||||
background-color: #3cb500 |
||||
|
||||
.card-label-yellow |
||||
background-color: #fad900 |
||||
|
||||
.card-label-orange |
||||
background-color: #ff9f19 |
||||
|
||||
.card-label-red |
||||
background-color: #eb4646 |
||||
|
||||
.card-label-purple |
||||
background-color: #a632db |
||||
|
||||
.card-label-blue |
||||
background-color: #0079bf |
||||
|
||||
.card-label-pink |
||||
background-color: #ff78cb |
||||
|
||||
.card-label-sky |
||||
background-color: #00c2e0 |
||||
|
||||
.card-label-black |
||||
background-color: #4d4d4d |
||||
|
||||
.card-label-lime |
||||
background-color: #51e898 |
||||
|
||||
.edit-label, |
||||
.create-label |
||||
.card-label |
||||
float: left |
||||
height: 25px |
||||
margin: 0px 3% 7px 0px |
||||
width: 10.5% |
||||
cursor: pointer |
||||
|
||||
.edit-labels |
||||
input[type="text"] |
||||
margin: 4px 0 6px 38px |
||||
width: 243px |
||||
|
||||
.card-label |
||||
height: 30px |
||||
left: 0 |
||||
padding: 1px 5px |
||||
position: absolute |
||||
top: 0 |
||||
width: 24px |
||||
|
||||
.labels-static .card-label |
||||
line-height: 30px |
||||
margin-bottom: 4px |
||||
position: relative |
||||
top: auto |
||||
left: 0 |
||||
width: 260px |
||||
|
||||
.minicard-labels |
||||
position: relative |
||||
z-index: 30 |
||||
top: -6px |
||||
|
||||
.card-label |
||||
border-radius: 0 |
||||
float: left |
||||
height: 4px |
||||
margin-bottom: 1px |
||||
padding: 0 |
||||
width: 40px |
||||
line-height: 100px |
||||
|
||||
.card-detail-item-labels .card-label |
||||
border-radius: 3px |
||||
display: block |
||||
float: left |
||||
height: 20px |
||||
line-height: 20px |
||||
margin: 0 4px 4px 0 |
||||
min-width: 30px |
||||
padding: 5px 10px |
||||
width: auto |
||||
|
||||
.editable-labels .card-label:hover |
||||
cursor: pointer |
||||
opacity: .75 |
||||
|
||||
.edit-labels-pop-over |
||||
margin-bottom: 8px |
||||
|
||||
.edit-labels-pop-over .shortcut |
||||
display: inline-block |
||||
|
||||
.card-label-selectable |
||||
border-radius: 3px |
||||
cursor: pointer |
||||
margin: 0 50px 4px 0 |
||||
min-height: 18px |
||||
padding: 8px |
||||
position: relative |
||||
transition: margin-right .1s |
||||
|
||||
.card-label-selectable-icon |
||||
position: absolute |
||||
top: 8px |
||||
right: -20px |
||||
|
||||
&.active:hover, |
||||
&.active, |
||||
&.active.selected:hover, |
||||
&.active.selected |
||||
margin-right: 38px |
||||
padding-right: 32px |
||||
|
||||
.card-label-selectable-icon |
||||
right: 6px |
||||
|
||||
&.active:hover:hover, |
||||
&.active:hover, |
||||
&.active.selected:hover:hover, |
||||
&.active.selected:hover |
||||
margin-right: 38px |
||||
|
||||
&.selected, |
||||
&:hover |
||||
margin-right: 38px |
||||
opacity: .8 |
||||
|
||||
.active .card-label-selectable |
||||
&, |
||||
&:hover |
||||
margin-right: 0 |
||||
|
||||
.card-label-selectable-icon |
||||
right: 8px |
||||
|
||||
.card-label-edit-button |
||||
border-radius: 3px |
||||
float: right |
||||
padding: 8px |
||||
|
||||
&:hover |
||||
background: #dbdbdb |
||||
|
||||
.card-label-color-select-icon |
||||
left: 14px |
||||
position: absolute |
||||
top: 9px |
||||
|
||||
.phenom .card-label |
||||
display: inline-block |
||||
font-size: 12px |
||||
height: 14px |
||||
line-height: 13px |
||||
padding: 0 4px |
||||
min-width: 16px |
||||
overflow: ellipsis |
||||
|
||||
.board-widget .phenom .card-label |
||||
max-width: 130px |
@ -0,0 +1,136 @@ |
||||
.minicard |
||||
background-color: #fff |
||||
box-shadow: 0 1px 2px rgba(0,0,0,.2) |
||||
border-radius: 2px |
||||
cursor: pointer |
||||
margin-bottom: 9px |
||||
max-width: 300px |
||||
min-height: 20px |
||||
position: relative |
||||
z-index: 0 |
||||
overflow: hidden |
||||
|
||||
a |
||||
color: #4d4d4d |
||||
|
||||
&.active-card |
||||
background-color: #f0f0f0 |
||||
border-bottom-color: #c2c2c2 |
||||
|
||||
.minicard-operation |
||||
display: block |
||||
|
||||
&.draggable-hover-card |
||||
background-color: #f0f0f0 |
||||
border-bottom-color: #c2c2c2 |
||||
|
||||
.minicard-cover |
||||
background-position: center |
||||
background-repeat: no-repeat |
||||
background-size: cover |
||||
height: 145px |
||||
user-select: none |
||||
margin: -6px -8px 6px -8px |
||||
border-radius: top 2px |
||||
|
||||
&.no-preview-size |
||||
background-size: auto |
||||
background-position: center |
||||
|
||||
.minicard-details |
||||
padding: 6px 8px 2px |
||||
position: relative |
||||
z-index: 10 |
||||
|
||||
|
||||
&.is-selected |
||||
.minicard-details |
||||
padding-bottom: 0 |
||||
|
||||
a.minicard-details |
||||
text-decoration:none |
||||
|
||||
.minicard-details-overlay |
||||
background: transparent |
||||
bottom: 0 |
||||
left: 0 |
||||
position: absolute |
||||
right: 0 |
||||
top: 0 |
||||
|
||||
.minicard-dropzone |
||||
display: none |
||||
|
||||
.minicard.drophover .minicard-dropzone |
||||
background: rgba(255, 255, 255, .8) |
||||
// border-radius: 3px |
||||
// bottom: 0 |
||||
// display: block |
||||
// font-weight: 700 |
||||
// line-height: 100% |
||||
// left: 0 |
||||
// margin: 0 |
||||
// opacity: 1 |
||||
// padding: 0 |
||||
// position: absolute |
||||
// right: 0 |
||||
// text-align: center |
||||
// top: 0 |
||||
// z-index: 40 |
||||
|
||||
.minicard-title |
||||
display: block |
||||
font-weight: 400 |
||||
margin: 0 0 4px |
||||
overflow: hidden |
||||
text-decoration: none |
||||
word-wrap: break-word |
||||
|
||||
&::selection |
||||
background: transparent |
||||
|
||||
.minicard-labels |
||||
padding-top: 3px |
||||
margin-top: 4px |
||||
float: right |
||||
|
||||
.minicard-label |
||||
float: right |
||||
width: 8px |
||||
height: @width |
||||
border-radius: 2px |
||||
margin-left: 4px |
||||
|
||||
.minicard-members |
||||
float: right |
||||
margin: 2px -8px -2px 0 |
||||
|
||||
.member |
||||
float: right |
||||
border-radius: 50% |
||||
height: 28px |
||||
width: @height |
||||
|
||||
+ .badges |
||||
margin-top: 10px |
||||
|
||||
.minicard-members:empty |
||||
display: none |
||||
|
||||
.badges |
||||
float: left |
||||
|
||||
&:empty |
||||
display: none |
||||
|
||||
textarea.minicard-composer-textarea, |
||||
textarea.minicard-composer-textarea:focus |
||||
background: none |
||||
border: none |
||||
box-shadow: none |
||||
height: auto |
||||
margin-bottom: 4px |
||||
padding: 0 |
||||
max-height: 162px |
||||
min-height: 54px |
||||
overflow-y: auto |
@ -0,0 +1,12 @@ |
||||
template(name="cardMembersPopup") |
||||
//- input.js-search-mem(autofocus placeholder="Search members…" type="text") |
||||
ul.pop-over-member-list.checkable.js-mem-list |
||||
each board.members |
||||
li.item.js-member-item(class="{{#if isCardMember}}active{{/if}}") |
||||
a.name.js-select-member(href="#") |
||||
+userAvatar(user=user size="small") |
||||
span.full-name |
||||
= user.profile.name |
||||
| (<span class="username">{{ user.username }}</span>) |
||||
if isCardMember |
||||
i.fa.fa-check |
@ -0,0 +1,15 @@ |
||||
Router.route('/boards/:boardId/:slug/:cardId', { |
||||
name: 'Card', |
||||
template: 'board', |
||||
waitOn: function() { |
||||
var params = this.params; |
||||
// XXX We probably shouldn't rely on Session
|
||||
Session.set('currentBoard', params.boardId); |
||||
Session.set('currentCard', params.cardId); |
||||
|
||||
return BoardSubsManager.subscribe('board', params.boardId, params.slug); |
||||
}, |
||||
data: function() { |
||||
return Boards.findOne(this.params.boardId); |
||||
} |
||||
}); |
@ -0,0 +1,336 @@ |
||||
<template name="cardModal"> |
||||
{{ > modal template='cardDetailWindow' card=this board=this.board }} |
||||
</template> |
||||
|
||||
<template name="cardMemberPopup"> |
||||
<div class="board-member-menu"> |
||||
<div class="mini-profile-info"> |
||||
{{> userAvatar user=user }} |
||||
<div class="info"> |
||||
<h3 class="bottom" style="margin-right: 40px;"> |
||||
<a class="js-profile" href="{{ pathFor route='Profile' username=user.username }}">{{ user.profile.name }}</a> |
||||
</h3> |
||||
<p class="quiet bottom">@{{ user.username }}</p> |
||||
</div> |
||||
</div> |
||||
{{# if currentUser.isBoardMember }} |
||||
<ul class="pop-over-list"> |
||||
<li><a class="js-remove-member">{{_ 'remove-member-from-card'}}</a></li> |
||||
</ul> |
||||
{{/ if }} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="cardMorePopup"> |
||||
<p class="quiet bottom"> |
||||
<span class="clearfix"> |
||||
<span>{{_ 'link-card'}}</span> |
||||
<span class="icon-sm fa {{#if card.board.isPublic}}fa-globe{{else}}fa-lock{{/if}}"></span> |
||||
<input class="js-url js-autoselect inline-input" type="text" readonly="readonly" value="{{ card.rootUrl }}"> |
||||
</span> |
||||
{{_ 'added'}} <span class="date" title="{{ card.createdAt }}">{{ moment card.createdAt 'LLL' }}</span> - |
||||
<a class="js-delete" href="#" title="{{_ 'card-delete-notice'}}">{{_ 'delete'}}</a> |
||||
</p> |
||||
</template> |
||||
|
||||
<template name="cardLabelsPopup"> |
||||
<div> |
||||
{{! <input id="labelSearch" name="search" class="js-autofocus js-label-search" placeholder="Search labels…" value="" type="text"> }} |
||||
<ul class="edit-labels-pop-over js-labels-list"> |
||||
{{# each card.board.labels }} |
||||
<li> |
||||
<a href="#" class="card-label-edit-button icon-sm fa fa-pencil js-edit-label"></a> |
||||
<span class="card-label card-label-selectable card-label-{{color}} js-select-label {{# if isLabelSelected ../card._id }}active{{/ if }}"> |
||||
{{name}} |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<span class="card-label-selectable-icon icon-sm fa fa-check light"></span> |
||||
{{/ if }} |
||||
</span> |
||||
</li> |
||||
{{/ each}} |
||||
</ul> |
||||
<a class="quiet-button full js-add-label">{{_ 'label-create'}}</a> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="cardAttachmentsPopup"> |
||||
<div> |
||||
<ul class="pop-over-list"> |
||||
<li> |
||||
<input type="file" name="file" class="js-attach-file hide" multiple> |
||||
<a class="js-computer-upload" href="#"> |
||||
{{_ 'computer'}} |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="formLabel"> |
||||
<div class="colors clearfix"> |
||||
<label for="labelName">{{_ 'name'}}</label> |
||||
<input id="labelName" type="text" name="name" class="js-label-name" value='{{ name }}' autofocus> |
||||
<label>{{_ "select-color"}}</label> |
||||
{{# each labels }} |
||||
<span class="card-label card-label--selectable card-label-{{ color }} palette-color js-palette-color"> |
||||
<span class="card-label-color-select-icon icon-sm fa fa-check light js-palette-select {{#if $neq color ../color}}hide{{/if}}"></span> |
||||
</span> |
||||
{{/each}} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="createLabelPopup"> |
||||
<form class="create-label"> |
||||
{{#with color=defaultColor}} |
||||
{{> formLabel}} |
||||
{{/with}} |
||||
<input type="submit" class="primary wide left" value="{{_ 'create'}}"> |
||||
</form> |
||||
</template> |
||||
|
||||
<template name="editLabelPopup"> |
||||
<form class="edit-label"> |
||||
{{> formLabel}} |
||||
<input type="submit" class="primary wide left" value="{{_ 'save'}}"> |
||||
<span class="right"> |
||||
<input type="submit" value="{{_ 'delete'}}" class="negate js-delete-label"> |
||||
</span> |
||||
</form> |
||||
</template> |
||||
|
||||
<template name="deleteLabelPopup"> |
||||
<p>{{_ "label-delete-pop"}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'delete'}}"> |
||||
</template> |
||||
|
||||
<template name="cardDeletePopup"> |
||||
<p>{{_ "card-delete-pop"}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'delete'}}"> |
||||
</template> |
||||
|
||||
<template name="attachmentDeletePopup"> |
||||
<p>{{_ "attachment-delete-pop"}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'delete'}}"> |
||||
</template> |
||||
|
||||
<template name="cardDetailSidebarOld"> |
||||
<div class="card-detail-window clearfix"> |
||||
{{# if card.cover }} |
||||
<div class="window-cover js-card-cover-box js-open-card-cover-in-viewer has-cover" style="background-image: url({{ card.cover.url }}); background-color: rgb(119, 119, 119); background-size: contain;"> |
||||
</div> |
||||
{{ /if }} |
||||
{{ #if card.archived }} |
||||
<div class="window-archive-banner js-archive-banner"> |
||||
<span class="icon-lg fa fa-archive window-archive-banner-icon"></span> |
||||
<p class="window-archive-banner-text">{{_ "card-archived"}}</p> |
||||
</div> |
||||
{{ /if }} |
||||
<div class="window-header clearfix"> |
||||
<span class="window-header-icon icon-lg fa fa-calendar-o"></span> |
||||
<div class="window-title card-detail-title non-empty inline {{# if currentUser.isBoardMember }}editable{{/ if }}"> |
||||
<h2 class="window-title-text current hide-on-edit js-card-title">{{ card.title }}</h2> |
||||
<div class="edit edit-heavy"> |
||||
<form id="WindowTitleEdit"> |
||||
<textarea type="text" class="field single-line" id="title">{{ card.title }}</textarea> |
||||
<div class="edit-controls clearfix"> |
||||
<input type="submit" class="primary confirm js-title-save-edit" value="{{_ 'save'}}"> |
||||
<a href="#" class="icon-lg fa fa-times dark-hover cancel js-cancel-edit"></a> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
<div class="quiet hide-on-edit window-header-inline-content js-current-list"> |
||||
<p class="inline-block bottom"> |
||||
{{_ 'in-list'}} |
||||
<a href="#" class="{{# if currentUser.isBoardMember }}js-open-move-from-header{{else}}disabled{{/ if }}"><strong>{{ card.list.title }}</strong></a> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="window-main-col clearfix"> |
||||
<div class="card-detail-data gutter clearfix"> |
||||
<div class="card-detail-item card-detail-item-block clear clearfix editable"> |
||||
{{# if card.members }} |
||||
<div class="card-detail-item card-detail-item-members clearfix js-card-detail-members"> |
||||
<h3 class="card-detail-item-header">{{_ 'members'}}</h3> |
||||
<div class="js-card-detail-members-list clearfix"> |
||||
{{# each card.members }} |
||||
{{> userAvatar userId=this size="small" cardId=../card._id }} |
||||
{{/ each }} |
||||
<a class="card-detail-item-add-button dark-hover js-details-edit-members"> |
||||
<span class="icon-sm fa fa-plus"></span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
{{/ if }} |
||||
{{# if card.labels }} |
||||
<div class="card-detail-item card-detail-item-labels clearfix js-card-detail-labels"> |
||||
<h3 class="card-detail-item-header">{{_ 'labels'}}</h3> |
||||
<div class="js-card-detail-labels-list clearfix editable-labels js-edit-label"> |
||||
{{# each card.labels }} |
||||
<span class="card-label card-label-{{color}}" title="{{name}}">{{ name }}</span> |
||||
{{/ each }} |
||||
<a class="card-detail-item-add-button dark-hover js-details-edit-labels"> |
||||
<span class="icon-sm fa fa-plus"></span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
{{/ if }} |
||||
<div class="card-detail-item card-detail-item-block clear clearfix editable" attr="desc"> |
||||
{{# if card.description }} |
||||
<h3 class="card-detail-item-header js-show-with-desc">{{_ 'description'}}</h3> |
||||
{{# if currentUser.isBoardMember }} |
||||
<a href="#" class="card-detail-item-header-edit hide-on-edit js-show-with-desc js-edit-desc">{{_ 'edit'}}</a> |
||||
{{/ if }} |
||||
<div class="current markeddown hide-on-edit js-card-desc js-show-with-desc"> |
||||
{{#viewer}}{{ card.description }}{{/viewer}} |
||||
</div> |
||||
{{ else }} |
||||
{{# if currentUser.isBoardMember }} |
||||
<p class="bottom"> |
||||
<a href="#" class="hide-on-edit quiet-button w-img js-edit-desc js-hide-with-desc"> |
||||
<span class="icon-sm fa fa-align-left"></span> |
||||
{{_ 'edit-description'}} |
||||
</a> |
||||
</p> |
||||
{{/ if }} |
||||
{{/ if }} |
||||
<div class="card-detail-edit edit"> |
||||
<form id="WindowDescEdit"> |
||||
{{#editor class="field single-line2" id="desc"}}{{ card.description }}{{/editor}} |
||||
<div class="edit-controls clearfix"> |
||||
<input type="submit" class="primary confirm js-title-save-edit" value="{{_ 'save'}}"> |
||||
<a href="#" class="icon-lg fa fa-times dark-hover cancel js-cancel-edit"></a> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{# if card.attachments.count }} |
||||
{{ > WindowAttachmentsModule card=card }} |
||||
{{/ if}} |
||||
{{ > WindowActivityModule card=card }} |
||||
</div> |
||||
{{# if currentUser.isBoardMember }} |
||||
{{ > WindowSidebarModule card=card }} |
||||
{{/if}} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="WindowActivityModule"> |
||||
<div class="card-detailwindow-module"> |
||||
<div class="window-module-title window-module-title-no-divider"> |
||||
<span class="window-module-title-icon icon-lg fa fa-comments-o"></span> |
||||
<h3>{{ _ 'activity'}}</h3> |
||||
</div> |
||||
{{# if currentUser.isBoardMember }} |
||||
<div class="new-comment js-new-comment"> |
||||
{{> userAvatar user=currentUser size="small" class="member-no-menu" }} |
||||
<form id="CommentForm"> |
||||
{{#editor class="new-comment-input js-new-comment-input"}}{{/editor}} |
||||
<div class="add-controls clearfix"> |
||||
<input type="submit" class="primary confirm clear js-add-comment" value="{{_ 'comment'}}" tabindex="2"> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
{{/ if }} |
||||
{{ > activities mode="card" }} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="WindowAttachmentsModule"> |
||||
<div class="window-module js-attachments-section clearfix"> |
||||
<div class="window-module-title window-module-title-no-divider"> |
||||
<span class="window-module-title-icon icon-lg fa fa-paperclip"></span> |
||||
<h3 class="inline-block">{{_ 'attachments'}}</h3> |
||||
</div> |
||||
<div class="gutter"> |
||||
<div class="clearfix js-attachment-list"> |
||||
{{# each card.attachments }} |
||||
<div class="attachment-thumbnail"> |
||||
{{# if isUploaded }} |
||||
<a href="{{ url download=true }}" class="attachment-thumbnail-preview js-open-viewer attachment-thumbnail-preview-is-cover"> |
||||
{{# if isImage }} |
||||
<img src="{{ url }}"> |
||||
{{ else }} |
||||
<span class="attachment-thumbnail-preview-ext">{{ extension }}</span> |
||||
{{ /if }} |
||||
</a> |
||||
<p class="attachment-thumbnail-details js-open-viewer"> |
||||
<a href="" class="attachment-thumbnail-details-title js-attachment-thumbnail-details"> |
||||
{{ name }} |
||||
<span class="block quiet"> |
||||
{{_ 'added'}} <span class="date">{{ moment uploadedAt }}</span> |
||||
</span> |
||||
</a> |
||||
<span class="quiet attachment-thumbnail-details-options"> |
||||
<a href="{{ url download=true }}" class="attachment-thumbnail-details-options-item dark-hover js-download"> |
||||
<span class="icon-sm fa fa-download"></span> |
||||
<span class="attachment-thumbnail-details-options-item-text">{{_ 'download'}}</span> |
||||
</a> |
||||
{{# if isImage }} |
||||
<a class="attachment-thumbnail-details-options-item dark-hover {{#if $eq ../card.coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}"> |
||||
<span class="icon-sm fa fa-thumb-tack"></span> |
||||
<span class="attachment-thumbnail-details-options-item-text">{{#if $eq ../card.coverId _id}}{{_ 'remove-cover'}}{{else}}{{_ 'add-cover'}}{{/if}}</span> |
||||
</a> |
||||
{{/if}} |
||||
<a href="#" class="attachment-thumbnail-details-options-item attachment-thumbnail-details-options-item-delete dark-hover js-confirm-delete"> |
||||
<span class="icon-sm fa fa-close"></span> |
||||
<span class="attachment-thumbnail-details-options-item-text">{{_ 'delete'}}</span> |
||||
</a> |
||||
</span> |
||||
</p> |
||||
{{ else }} |
||||
+spinner |
||||
{{/ if }} |
||||
</div> |
||||
{{/each}} |
||||
</div> |
||||
<p> |
||||
<a href="#" class="quiet-button js-attach">{{_ 'add-attachment' }}</a> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="WindowSidebarModule"> |
||||
<div class="window-sidebar" style="position: relative;"> |
||||
<div class="window-module clearfix"> |
||||
<h3>{{_ 'add'}}</h3> |
||||
<div class="clearfix"> |
||||
<a href="#" class="button-link js-change-card-members" title="{{_ 'members-title'}}"> |
||||
<span class="icon-sm fa fa-user"></span> {{_ 'members'}} |
||||
</a> |
||||
<a href="#" class="button-link js-edit-labels" title="{{_ 'labels-title'}}"> |
||||
<span class="icon-sm fa fa-tags"></span> {{_ 'labels'}} |
||||
</a> |
||||
<a href="#" class="button-link js-attach" title="{{_ 'attachment-title'}}"> |
||||
<span class="icon-sm fa fa-paperclip"></span> {{_ 'attachment'}} |
||||
</a> |
||||
</div> |
||||
</div> |
||||
<div class="window-module other-actions clearfix"> |
||||
<h3>{{_ 'actions'}}</h3> |
||||
<div class="clearfix"> |
||||
<hr> |
||||
{{ #if card.archived }} |
||||
<a href="#" class="button-link js-unarchive-card" title="{{_ 'send-to-board-title'}}"> |
||||
<span class="icon-sm fa fa-recycle"></span> {{_ 'send-to-board'}} |
||||
</a> |
||||
<a href="#" class="button-link negate js-delete-card" title="{{_ 'delete-title'}}"> |
||||
<span class="icon-sm fa fa-trash-o"></span> {{_ 'delete'}} |
||||
</a> |
||||
{{ else }} |
||||
<a href="#" class="button-link js-archive-card" title="{{_ 'archive-title'}}"> |
||||
<span class="icon-sm fa fa-archive"></span> {{_ 'archive'}} |
||||
</a> |
||||
{{ /if }} |
||||
</div> |
||||
</div> |
||||
<div class="window-module clearfix"> |
||||
<p class="quiet bottom"> |
||||
<a href="#" class="quiet-button js-more-menu" title="{{_ 'share-and-more-title'}}">{{_ 'share-and-more'}}</a> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,22 @@ |
||||
var emptyValue = ''; |
||||
|
||||
Mixins.CachedValue = BlazeComponent.extendComponent({ |
||||
onCreated: function() { |
||||
this._cachedValue = emptyValue; |
||||
}, |
||||
|
||||
setCache: function(value) { |
||||
this._cachedValue = value; |
||||
}, |
||||
|
||||
getCache: function(defaultValue) { |
||||
if (this._cachedValue === emptyValue) |
||||
return defaultValue || ''; |
||||
else |
||||
return this._cachedValue; |
||||
}, |
||||
|
||||
resetCache: function() { |
||||
this.setCache(''); |
||||
} |
||||
}); |
@ -0,0 +1,636 @@ |
||||
@import 'nib' |
||||
|
||||
textarea, |
||||
input:not([type=file]), |
||||
button |
||||
box-sizing: border-box |
||||
-webkit-appearance: none |
||||
background-color: #ebebeb |
||||
border: 1px solid #ccc |
||||
border-radius: 3px |
||||
display: block |
||||
margin-bottom: 12px |
||||
min-height: 34px |
||||
padding: 7px |
||||
|
||||
&.full |
||||
width: 100% |
||||
|
||||
&.input-error |
||||
background-color: #ece9e9 |
||||
border-color: #ba1212 |
||||
|
||||
&:focus |
||||
outline: 0 |
||||
|
||||
input[type="file"] |
||||
margin-bottom: 16px |
||||
|
||||
input[type="radio"] |
||||
-webkit-appearance: radio |
||||
min-height: inherit |
||||
|
||||
input[type="checkbox"] |
||||
-webkit-appearance: checkbox |
||||
margin-right: 4px |
||||
|
||||
input[type="text"], |
||||
input[type="password"], |
||||
input[type="email"] |
||||
transition: background 85ms ease-in, |
||||
border-color 85ms ease-in |
||||
width: 250px |
||||
|
||||
&.inline-input |
||||
background: none |
||||
border: 0 |
||||
margin: 0 |
||||
padding: 2px |
||||
min-height: 0 |
||||
height: 18px |
||||
width: 200px |
||||
|
||||
input[type="email"]:invalid |
||||
box-shadow: none |
||||
|
||||
input[type="text"], |
||||
input[type="password"], |
||||
input[type="email"], |
||||
textarea |
||||
|
||||
&:hover |
||||
border-color: #999 |
||||
|
||||
&.input-error |
||||
border-color: #ba1212 |
||||
|
||||
&:focus |
||||
background: #fff |
||||
border-color: #318ec4 |
||||
box-shadow: 0 0 2px #318ec4 |
||||
|
||||
&.input-error |
||||
background-color: #f8f7f7 |
||||
border-color: #ba1212 |
||||
box-shadow: 0 0 2px #d11515 |
||||
|
||||
&:disabled |
||||
background-color: #dcdcdc |
||||
border-color: #bfbfbf |
||||
color: #8c8c8c |
||||
-webkit-touch-callout: none |
||||
user-select: none |
||||
|
||||
select |
||||
max-height: 300px |
||||
width: 256px |
||||
margin-bottom: 8px |
||||
|
||||
option[disabled] |
||||
color: #8c8c8c |
||||
|
||||
textarea |
||||
height: 150px |
||||
transition: background 85ms ease-in, |
||||
border-color 85ms ease-in |
||||
resize: vertical |
||||
width: 100% |
||||
|
||||
.button |
||||
border-radius: 3px |
||||
text-decoration: none |
||||
position: relative |
||||
|
||||
input[type="submit"], |
||||
button |
||||
background: #cfcfcf |
||||
background: linear-gradient(#cfcfcf, #c2c2c2) |
||||
border: none |
||||
box-shadow: 0 1px 0 #8c8c8c |
||||
cursor: pointer |
||||
display: inline-block |
||||
font-weight: 700 |
||||
line-height: 22px |
||||
margin: 8px 4px 0 0 |
||||
padding: 7px 20px |
||||
text-align: center |
||||
|
||||
.wide |
||||
padding-left: 30px |
||||
padding-right: 30px |
||||
|
||||
&:hover, |
||||
&:focus |
||||
background: #c2c2c2 |
||||
background: linear-gradient(#c2c2c2, #b5b5b5) |
||||
|
||||
&:active |
||||
background: #b5b5b5 |
||||
background: linear-gradient(#b5b5b5, #a8a8a8) |
||||
box-shadow: inset 0 3px 6px rgba(0, 0, 0, .1) |
||||
|
||||
&:hover, |
||||
&:focus, |
||||
&:active |
||||
background: #e6e6e6 |
||||
background: linear-gradient(#e6e6e6, #e6e6e6) |
||||
|
||||
&.primary |
||||
background: #005377 |
||||
box-shadow: 0 1px 0 #4d4d4d |
||||
color: white |
||||
|
||||
&:hover, |
||||
&:focus |
||||
background: #004766 |
||||
|
||||
&:active |
||||
background: #01628C |
||||
|
||||
&.negate |
||||
&:hover, |
||||
&:focus |
||||
background: #990f0f |
||||
background: linear-gradient(#990f0f, #7d0c0c) |
||||
box-shadow: 0 1px 0 #4d4d4d |
||||
color: #fff |
||||
|
||||
&:active |
||||
background: #7d0c0c |
||||
box-shadow: 0 1px 0 #4d4d4d |
||||
color: #fff |
||||
|
||||
input[type="submit"].disabled, |
||||
input[type="submit"]:disabled, |
||||
input[type="button"].disabled, |
||||
button.disabled, |
||||
.button.disabled |
||||
|
||||
&, |
||||
&:hover, |
||||
&:active |
||||
background: #cfcfcf |
||||
cursor: default |
||||
box-shadow: none |
||||
color: #a8a8a8 |
||||
|
||||
fieldset |
||||
border: 1px solid #bfbfbf |
||||
padding: 15px |
||||
margin-bottom: 15px |
||||
|
||||
input[type="hidden"] |
||||
display: none |
||||
|
||||
input[type="checkbox"], |
||||
input[type="radio"] |
||||
display: inline |
||||
|
||||
.radio-div, |
||||
.check-div |
||||
display: block |
||||
margin: 0 0 4px 20px |
||||
min-height: 20px |
||||
position: relative |
||||
|
||||
input |
||||
left: -18px |
||||
min-height: 0 |
||||
margin: 0 |
||||
padding: 0 |
||||
position: absolute |
||||
top: 2px |
||||
|
||||
label |
||||
font-weight: 400 |
||||
|
||||
label |
||||
display: block |
||||
font-weight: 700 |
||||
margin-bottom: 4px |
||||
|
||||
&.form-error |
||||
color: #ba1212 |
||||
|
||||
input, |
||||
textarea |
||||
&::-webkit-input-placeholder, |
||||
&::-moz-placeholder |
||||
color: #8c8c8c |
||||
|
||||
.edit-controls, |
||||
.add-controls |
||||
margin-top: 0 |
||||
|
||||
button[type=submit] |
||||
float: left |
||||
height: 32px |
||||
margin-top: -2px |
||||
padding-top: 5px |
||||
padding-bottom: 5px |
||||
|
||||
i.fa.fa-times |
||||
font-size: 20px |
||||
|
||||
.option |
||||
border-color: transparent |
||||
border-radius: 3px |
||||
color: #8c8c8c |
||||
display: block |
||||
float: right |
||||
height: 30px |
||||
line-height: 30px |
||||
padding: 0 8px |
||||
margin: 0 2px |
||||
|
||||
&:hover |
||||
background-color: #dbdbdb |
||||
color: #4d4d4d |
||||
|
||||
&:active |
||||
background-color: #ccc |
||||
|
||||
.button-link |
||||
background: #fff |
||||
background: linear-gradient(#fff, #f5f5f5) |
||||
border-radius: 3px |
||||
box-sizing: border-box |
||||
user-select: none |
||||
border: 1px solid #e3e3e3 |
||||
border-bottom-color: #c2c2c2 |
||||
cursor: pointer |
||||
display: block |
||||
font-weight: 700 |
||||
height: 34px |
||||
margin-top: 6px |
||||
max-width: 300px |
||||
padding: 7px |
||||
position: relative |
||||
text-decoration: none |
||||
overflow: ellipsis |
||||
|
||||
.on |
||||
background: #48b512 |
||||
background: linear-gradient(#48b512, #3d990f) |
||||
border-radius: 3px |
||||
color: #fff |
||||
display: none |
||||
font-size: 12px |
||||
font-weight: 700 |
||||
height: 17px |
||||
line-height: @height |
||||
margin: 0 |
||||
padding: 2px 4px |
||||
position: absolute |
||||
right: 5px |
||||
top: 5px |
||||
text-align: center |
||||
|
||||
&.is-on |
||||
padding-right: 30px |
||||
max-width: 196px |
||||
|
||||
.on |
||||
display: block |
||||
|
||||
&.inline |
||||
color: #666 |
||||
padding: 2px 14px |
||||
margin-left: 4px |
||||
|
||||
&.setting |
||||
height: 52px |
||||
float: left |
||||
position: relative |
||||
margin-top: 0 |
||||
|
||||
&.disabled |
||||
background: #fff |
||||
border-color: #e9e9e9 |
||||
color: #8c8c8c |
||||
cursor: default |
||||
|
||||
select |
||||
display: none |
||||
|
||||
&:hover .label |
||||
color: #8c8c8c |
||||
|
||||
&, |
||||
&:hover, |
||||
&:active, |
||||
&.primary, |
||||
&.primary:hover, |
||||
&.primary:active |
||||
background: #cfcfcf |
||||
border-color: #c2c2c2 |
||||
border-bottom-color: #b5b5b5 |
||||
cursor: default |
||||
box-shadow: none |
||||
color: #a8a8a8 |
||||
|
||||
.label |
||||
color: #8c8c8c |
||||
display: block |
||||
font-size: 12px |
||||
line-height: 14px |
||||
margin-bottom: 0 |
||||
|
||||
&:hover .label |
||||
color: #eee |
||||
|
||||
.value |
||||
display: block |
||||
font-size: 18px |
||||
line-height: 24px |
||||
overflow: hidden |
||||
text-overflow: ellipsis |
||||
|
||||
label |
||||
display: none |
||||
|
||||
select |
||||
border: none |
||||
cursor: pointer |
||||
height: 50px |
||||
left: 0 |
||||
margin: 0 |
||||
opacity: 0 |
||||
position: absolute |
||||
top: 0 |
||||
z-index: 2 |
||||
width: 100% |
||||
|
||||
&:hover |
||||
background: #318ec4 |
||||
background: linear-gradient(#318ec4, #2b7cab) |
||||
border-color: #2e85b8 |
||||
color: #fff |
||||
|
||||
.on |
||||
background-image: none |
||||
background-color: rgba(255, 255, 255, .3) |
||||
border-color: transparent |
||||
|
||||
.icon-sm |
||||
color: #fff |
||||
|
||||
&:active |
||||
background: #2e85b8 |
||||
background: linear-gradient(#2e85b8, #28739f) |
||||
border-color: #2b7cab |
||||
color: #fff |
||||
|
||||
.button-link.negate |
||||
|
||||
&:hover |
||||
background: #990f0f |
||||
background: linear-gradient(#990f0f, #7d0c0c) |
||||
border-color: @background |
||||
|
||||
&:active |
||||
background: #7d0c0c |
||||
border-color: #990f0f |
||||
|
||||
|
||||
&.primary |
||||
background: #48b512 |
||||
background: linear-gradient(#48b512, #3d990f) |
||||
border: 1px solid |
||||
border-color: #3d990f |
||||
color: #fff |
||||
|
||||
&:hover |
||||
background: #3d990f |
||||
background: linear-gradient(#3d990f, #327d0c) |
||||
border-color: #3d990f |
||||
|
||||
&.danger |
||||
background: #ba1212 |
||||
background: linear-gradient(#ba1212, #8b0e0e) |
||||
border: 1px solid |
||||
border-color: #a21010 |
||||
color: #fff |
||||
|
||||
&:hover |
||||
background: #a21010 |
||||
background: linear-gradient(#a21010, #740b0b) |
||||
border-color: #8b0e0e |
||||
|
||||
button |
||||
|
||||
&.quiet-button, |
||||
&.loud-text-button |
||||
background: none |
||||
text-align: left |
||||
line-height: normal |
||||
border: none |
||||
box-shadow: none |
||||
|
||||
&:active |
||||
color: #4d4d4d |
||||
background: #d3d3d3 |
||||
box-shadow: none |
||||
|
||||
&.quiet-button |
||||
font-weight: 400 |
||||
text-decoration: underline |
||||
|
||||
&.loud-text-button |
||||
width: 100% |
||||
|
||||
&:hover |
||||
color: #111 |
||||
|
||||
.emphasis-button, |
||||
.quiet-button |
||||
border-radius: 3px |
||||
user-select: none |
||||
color: #8c8c8c |
||||
display: block |
||||
margin: 2px 0 |
||||
padding: 6px 8px |
||||
position: relative |
||||
|
||||
&.w-img |
||||
padding-left: 28px |
||||
|
||||
.icon-sm |
||||
left: 6px |
||||
position: absolute |
||||
top: 6px |
||||
|
||||
&:hover |
||||
color: #4d4d4d |
||||
background: #dcdcdc |
||||
|
||||
&:active |
||||
color: #4d4d4d |
||||
background: #d3d3d3 |
||||
|
||||
.quiet-button-large |
||||
padding: 16px 24px |
||||
|
||||
.emphasis-button |
||||
color: #74663e |
||||
background: #ecdfbb |
||||
|
||||
&:hover |
||||
color: #53492d |
||||
background: #e7d6a7 |
||||
|
||||
&:active |
||||
color: #53492d |
||||
background: #e1cc93 |
||||
|
||||
.big-link |
||||
border-radius: 3px |
||||
display: block |
||||
margin: 6px 0 6px 40px |
||||
padding: 11px |
||||
position: relative |
||||
text-decoration: none |
||||
font-size: 16px |
||||
line-height: 20px |
||||
|
||||
.text |
||||
text-decoration: underline |
||||
|
||||
&:hover |
||||
background: #dcdcdc |
||||
|
||||
&.options |
||||
padding-right: 41px |
||||
|
||||
.option |
||||
height: 30px |
||||
width: @height |
||||
position: absolute |
||||
right: 6px |
||||
top: 6px |
||||
|
||||
&.none |
||||
color: #8c8c8c |
||||
text-decoration: none |
||||
|
||||
&:hover |
||||
background: transparent |
||||
|
||||
&.avatar-changer |
||||
padding-right: 51px |
||||
|
||||
.member |
||||
border: 1px solid #ccc |
||||
border-radius: 3px |
||||
height: 40px |
||||
width: @height |
||||
position: absolute |
||||
right: 0 |
||||
top: 0 |
||||
|
||||
.member-avatar |
||||
height: 40px |
||||
width: @height |
||||
|
||||
.member-initials |
||||
font-size: 16px |
||||
height: 40px |
||||
line-height: @height |
||||
max-height: @height |
||||
|
||||
.show-more |
||||
border-radius: 3px |
||||
color: #8c8c8c |
||||
display: block |
||||
padding: 16px 8px 16px 40px |
||||
margin: 8px 0 |
||||
|
||||
&:hover |
||||
background: #dcdcdc |
||||
text-decoration: underline |
||||
|
||||
&.compact |
||||
padding: 12px 8px |
||||
margin: 8px 0 0 |
||||
text-align: center |
||||
|
||||
.board-widget .show-more |
||||
padding: 12px 8px 12px 40px |
||||
|
||||
.uploader |
||||
clear: both |
||||
cursor: pointer |
||||
position: relative |
||||
height: 34px |
||||
width: 100% |
||||
|
||||
.realfile |
||||
cursor: pointer |
||||
height: 34px |
||||
line-height: @height |
||||
position: absolute |
||||
top: 0 |
||||
left: 0 |
||||
width: 100% |
||||
z-index: 2 |
||||
font-size: 23px |
||||
|
||||
input[type="file"] |
||||
cursor: pointer |
||||
height: 34px |
||||
line-height: @height |
||||
margin: 0 |
||||
opacity: 0 |
||||
padding: 0 |
||||
width: 100% |
||||
z-index: 2 |
||||
font-size: 23px |
||||
|
||||
&:hover .fakefile |
||||
background: #318ec4 |
||||
background: linear-gradient(#318ec4, #2b7cab) |
||||
border-color: #2e85b8 |
||||
color: #fff |
||||
|
||||
.form-grid |
||||
display: flex |
||||
flex-wrap: wrap |
||||
width: 100% |
||||
|
||||
.form-grid-child |
||||
flex: 1 |
||||
margin: 0 0 8px |
||||
|
||||
.form-grid-child-full |
||||
flex: 1 1 100% |
||||
|
||||
.form-grid-child-threequarters |
||||
flex: 3 |
||||
margin-right: 8px |
||||
|
||||
.form-grid-child-twothirds |
||||
flex: 2 |
||||
margin-right: 8px |
||||
|
||||
.dropdown-menu |
||||
border-radius: 2px |
||||
// padding-bottom: 3px |
||||
overflow: hidden |
||||
|
||||
li |
||||
border-top: none |
||||
|
||||
a |
||||
padding: 4px 12px 4px 8px |
||||
|
||||
img |
||||
width: 18px |
||||
height: @width |
||||
margin-right: 5px |
||||
vertical-align: middle |
||||
|
||||
&.active |
||||
background: #005377 |
||||
|
||||
a |
||||
color: white |
@ -0,0 +1,6 @@ |
||||
template(name='inlinedForm') |
||||
if isOpen.get |
||||
form(id=id class=classNames) |
||||
+Template.contentBlock |
||||
else |
||||
+Template.elseBlock |
@ -0,0 +1,93 @@ |
||||
// A inlined form is used to provide a quick edition of single field for a given
|
||||
// document. Clicking on a edit button should display the form to edit the field
|
||||
// value. The form can then be submited, or just closed.
|
||||
//
|
||||
// When the form is closed we save non-submitted values in memory to avoid any
|
||||
// data loss.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// +inlineForm
|
||||
// // the content when the form is open
|
||||
// else
|
||||
// // the content when the form is close (optional)
|
||||
|
||||
// We can only have one inlined form element opened at a time
|
||||
// XXX Could we avoid using a global here ? This is used in Mousetrap
|
||||
// keyboard.js
|
||||
currentlyOpenedForm = new ReactiveVar(null); |
||||
|
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'inlinedForm'; |
||||
}, |
||||
|
||||
mixins: function() { |
||||
return [Mixins.CachedValue]; |
||||
}, |
||||
|
||||
onCreated: function() { |
||||
this.isOpen = new ReactiveVar(false); |
||||
}, |
||||
|
||||
open: function() { |
||||
// Close currently opened form, if any
|
||||
if (currentlyOpenedForm.get() !== null) { |
||||
currentlyOpenedForm.get().close(); |
||||
} |
||||
this.isOpen.set(true); |
||||
currentlyOpenedForm.set(this); |
||||
}, |
||||
|
||||
close: function() { |
||||
this.saveValue(); |
||||
this.isOpen.set(false); |
||||
currentlyOpenedForm.set(null); |
||||
}, |
||||
|
||||
getValue: function() { |
||||
return this.isOpen.get() && this.find('textarea,input[type=text]').value; |
||||
}, |
||||
|
||||
saveValue: function() { |
||||
this.callFirstWith(this, 'setCache', this.getValue()); |
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
'click .js-close-inlined-form': this.close, |
||||
'click .js-open-inlined-form': this.open, |
||||
|
||||
// Close the inlined form by pressing escape.
|
||||
//
|
||||
// Keydown (and not keypress) in necessary here because the `keyCode`
|
||||
// property is consistent in all browsers, (there is not keyCode for the
|
||||
// `keypress` event in firefox)
|
||||
'keydown form input, keydown form textarea': function(evt) { |
||||
if (evt.keyCode === 27) { |
||||
evt.preventDefault(); |
||||
this.close(); |
||||
} |
||||
}, |
||||
|
||||
// Pressing Ctrl+Enter should submit the form
|
||||
'keydown form textarea': function(evt) { |
||||
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { |
||||
$(evt.currentTarget).parents('form:first').submit(); |
||||
} |
||||
}, |
||||
|
||||
// Close the inlined form when after its submission
|
||||
submit: function() { |
||||
var self = this; |
||||
// XXX Swith to an arrow function here when we'll have ES6
|
||||
if (this.currentData().autoclose !== false) { |
||||
Tracker.afterFlush(function() { |
||||
self.close(); |
||||
self.callFirstWith(self, 'resetCache'); |
||||
}); |
||||
} |
||||
} |
||||
}]; |
||||
} |
||||
}).register('inlinedForm'); |
@ -0,0 +1,50 @@ |
||||
template(name="listBody") |
||||
.minicards.clearfix.js-minicards |
||||
if cards.count |
||||
+inlinedForm(autoclose=false position="top") |
||||
+addCardForm |
||||
each cards |
||||
.minicard.card.js-minicard.js-member-droppable( |
||||
class="{{#if isSelected}}is-selected{{/if}}") |
||||
a.minicard-details.clearfix.show(href=absoluteUrl) |
||||
if cover |
||||
.minicard-cover.js-card-cover(style="background-image: url({{cover.url}});") |
||||
if labels |
||||
.minicard-labels |
||||
each labels |
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}") |
||||
.minicard-title= title |
||||
if members |
||||
.minicard-members.js-minicard-members |
||||
each members |
||||
+userAvatar(userId=this size="small" cardId="{{../_id}}") |
||||
.badges |
||||
if comments.count |
||||
.badge(title="{{_ 'card-comments-title' comments.count }}") |
||||
span.badge-icon.icon-sm.fa.fa-comment-o |
||||
.badge-text= comments.count |
||||
if description |
||||
.badge.badge-state-image-only(title=description) |
||||
span.badge-icon.icon-sm.fa.fa-align-left |
||||
if attachments.count |
||||
.badge |
||||
span.badge-icon.icon-sm.fa.fa-paperclip |
||||
span.badge-text= attachments.count |
||||
if currentUser.isBoardMember |
||||
+inlinedForm(autoclose=false position="bottom") |
||||
+addCardForm |
||||
else |
||||
a.open-card-composer.js-open-inlined-form |
||||
i.fa.fa-plus |
||||
| {{_ 'add-card'}} |
||||
|
||||
template(name="addCardForm") |
||||
.minicard.js-composer |
||||
.minicard-labels.js-minicard-composer-labels |
||||
.minicard-details.clearfix |
||||
textarea.minicard-composer-textarea.js-card-title(autofocus) |
||||
= getCache |
||||
.minicard-members.js-minicard-composer-members |
||||
.add-controls.clearfix |
||||
button.primary.confirm(type="submit") {{_ 'add'}} |
||||
a.fa.fa-times.dark-hover.cancel.js-close-inlined-form |
@ -0,0 +1,73 @@ |
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'listBody'; |
||||
}, |
||||
|
||||
isSelected: function() { |
||||
return Session.equals('currentCard', this.currentData()._id); |
||||
}, |
||||
|
||||
addCard: function(evt) { |
||||
evt.preventDefault(); |
||||
var textarea = $(evt.currentTarget).find('textarea'); |
||||
var title = textarea.val(); |
||||
var position = this.currentData().position; |
||||
var sortIndex; |
||||
if (position === 'top') { |
||||
sortIndex = Utils.getSortIndex(null, this.find('.js-minicard:first')); |
||||
} else if (position === 'bottom') { |
||||
sortIndex = Utils.getSortIndex(this.find('.js-minicard:last'), null); |
||||
} |
||||
|
||||
// Clear the form in-memory cache
|
||||
// var inputCacheKey = "addCard-" + this.listId;
|
||||
// InputsCache.set(inputCacheKey, '');
|
||||
|
||||
// title trim if not empty then
|
||||
if ($.trim(title)) { |
||||
Cards.insert({ |
||||
title: title, |
||||
listId: this.data()._id, |
||||
boardId: this.data().board()._id, |
||||
sort: sortIndex |
||||
}, function(err, _id) { |
||||
// In case the filter is active we need to add the newly
|
||||
// inserted card in the list of exceptions -- cards that are
|
||||
// not filtered. Otherwise the card will disappear instantly.
|
||||
// See https://github.com/libreboard/libreboard/issues/80
|
||||
Filter.addException(_id); |
||||
}); |
||||
|
||||
// We keep the form opened, empty it, and scroll to it.
|
||||
textarea.val('').focus(); |
||||
Utils.Scroll(this.find('.js-minicards')).top(1000, true); |
||||
} |
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
submit: this.addCard, |
||||
'keydown form textarea': function(evt) { |
||||
// Pressing Enter should submit the card
|
||||
if (evt.keyCode === 13) { |
||||
evt.preventDefault(); |
||||
$(evt.currentTarget).parents('form:first').submit(); |
||||
|
||||
// Pressing Tab should open the form of the next column, and Maj+Tab go
|
||||
// in the reverse order
|
||||
} else if (evt.keyCode === 9) { |
||||
evt.preventDefault(); |
||||
var isReverse = evt.shiftKey; |
||||
var list = $('#js-list-' + this.data()._id); |
||||
var nextList = list[isReverse ? 'prev' : 'next']('.js-list').get(0) || |
||||
$('.js-list:' + (isReverse ? 'last' : 'first')).get(0); |
||||
var nextListComponent = BlazeComponent.getComponentForElement(nextList); |
||||
|
||||
// XXX Get the real position
|
||||
var position = 'bottom'; |
||||
nextListComponent.openForm({position: position}); |
||||
} |
||||
} |
||||
}]; |
||||
} |
||||
}).register('listBody'); |
@ -0,0 +1,16 @@ |
||||
Template.addlistForm.events({ |
||||
submit: function(event, t) { |
||||
event.preventDefault(); |
||||
var title = t.find('.list-name-input'); |
||||
if ($.trim(title.value)) { |
||||
Lists.insert({ |
||||
title: title.value, |
||||
boardId: Session.get('currentBoard'), |
||||
sort: $('.list').length |
||||
}); |
||||
|
||||
Utils.Scroll('.js-lists').left(270, true); |
||||
title.value = ''; |
||||
} |
||||
} |
||||
}); |
@ -0,0 +1,13 @@ |
||||
template(name="listHeader") |
||||
.list-header.js-list-header |
||||
+inlinedForm |
||||
+editListTitleForm |
||||
else |
||||
h2.list-header-name.js-open-inlined-form= title |
||||
a.list-header-menu-icon.fa.fa-bars.js-open-list-menu |
||||
|
||||
template(name="editListTitleForm") |
||||
input.field.single-line(type="text" value="{{getCache title}}" autofocus) |
||||
.edit-controls.clearfix |
||||
input.primary.confirm(type="submit" value="{{_ 'save'}}") |
||||
a.fa.fa-times.js-close-inlined-form |
@ -0,0 +1,25 @@ |
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'listHeader'; |
||||
}, |
||||
|
||||
editTitle: function(evt) { |
||||
evt.preventDefault(); |
||||
var form = this.componentChildren('inlinedForm')[0]; |
||||
var newTitle = form.getValue(); |
||||
if ($.trim(newTitle)) { |
||||
Lists.update(this.currentData()._id, { |
||||
$set: { |
||||
title: newTitle |
||||
} |
||||
}); |
||||
} |
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
'click .js-open-list-menu': Popup.open('listAction'), |
||||
submit: this.editTitle |
||||
}]; |
||||
} |
||||
}).register('listHeader'); |
@ -0,0 +1,5 @@ |
||||
template(name='list') |
||||
.list.js-list(id="js-list-{{_id}}") |
||||
.list-wrapper |
||||
+listHeader |
||||
+listBody |
@ -0,0 +1,81 @@ |
||||
ListComponent = BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'list'; |
||||
}, |
||||
|
||||
openForm: function(options) { |
||||
options = options || {}; |
||||
options.position = options.position || 'top'; |
||||
|
||||
var listComponent = this.componentChildren('listBody')[0]; |
||||
var forms = listComponent.componentChildren('inlinedForm'); |
||||
|
||||
if (options.position === 'top') { |
||||
forms[0].open(); |
||||
} else { |
||||
forms[forms.length - 1].open(); |
||||
} |
||||
}, |
||||
|
||||
// XXX The jQuery UI sortable plugin is far from ideal here. First we include
|
||||
// all jQuery components but only use one. Second, it modifies the DOM itself,
|
||||
// resulting in Blaze abandoning reactive update of the nodes that have been
|
||||
// moved which result in bugs if multiple users use the board in real time.
|
||||
// I tried sortable:sortable but that was not better. Should we “simply” write
|
||||
// the drag&drop code ourselves?
|
||||
onRendered: function() { |
||||
if (Meteor.user().isBoardMember()) { |
||||
var $cards = this.$('.js-minicards'); |
||||
$cards.sortable({ |
||||
connectWith: ".js-minicards", |
||||
tolerance: 'pointer', |
||||
appendTo: '.js-lists', |
||||
helper: "clone", |
||||
items: '.js-minicard:not(.placeholder, .hide, .js-composer)', |
||||
placeholder: 'minicard placeholder', |
||||
start: function (event, ui) { |
||||
$('.minicard.placeholder').height(ui.item.height()); |
||||
Popup.close(); |
||||
}, |
||||
stop: function(event, ui) { |
||||
// To attribute the new index number, we need to get the dom element of
|
||||
// the previous and the following card -- if any.
|
||||
var cardDomElement = ui.item.get(0); |
||||
var prevCardDomElement = ui.item.prev('.js-minicard').get(0); |
||||
var nextCardDomElement = ui.item.next('.js-minicard').get(0); |
||||
var sort = Utils.getSortIndex(prevCardDomElement, nextCardDomElement); |
||||
var cardId = Blaze.getData(cardDomElement)._id; |
||||
var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; |
||||
Cards.update(cardId, { |
||||
$set: { |
||||
listId: listId, |
||||
sort: sort |
||||
} |
||||
}); |
||||
} |
||||
}).disableSelection(); |
||||
|
||||
Utils.liveEvent('mouseover', function($el) { |
||||
$el.find('.js-member-droppable').droppable({ |
||||
hoverClass: "draggable-hover-card", |
||||
accept: '.js-member', |
||||
drop: function(event, ui) { |
||||
var memberId = Blaze.getData(ui.draggable.get(0)).userId; |
||||
var cardId = Blaze.getData(this)._id; |
||||
Cards.update(cardId, {$addToSet: {members: memberId}}); |
||||
} |
||||
}); |
||||
|
||||
$el.find('.js-member-droppable').droppable({ |
||||
hoverClass: "draggable-hover-card", |
||||
accept: '.js-label', |
||||
drop: function(event, ui) { |
||||
var labelId = Blaze.getData(ui.draggable.get(0))._id; |
||||
var cardId = Blaze.getData(this)._id; |
||||
Cards.update(cardId, {$addToSet: {labelIds: labelId}}); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
}).register('list'); |
@ -0,0 +1,136 @@ |
||||
@import 'nib' |
||||
|
||||
.list |
||||
box-sizing: border-box |
||||
display: flex |
||||
flex-direction: column |
||||
flex: 0 0 270px |
||||
position: relative |
||||
// Even if this background color is the same as the body we can't leave it |
||||
// transparent, because that won't work during a list drag. |
||||
background: darken(white, 10%) |
||||
height: 100% |
||||
border-right: 1px solid darken(white, 17%) |
||||
border-left: 1px solid darken(white, 4%) |
||||
padding: 12px 7px 5px |
||||
overflow-y: auto |
||||
|
||||
&:first-child |
||||
margin-left: 5px |
||||
border-left: none |
||||
|
||||
&:last-child |
||||
margin-right: 5px |
||||
border-right: none |
||||
|
||||
&.editable |
||||
cursor: grab |
||||
|
||||
.list-wrapper |
||||
cursor: default |
||||
|
||||
&.add-list |
||||
&.fade |
||||
opacity: 0 |
||||
|
||||
.list-name-input |
||||
background: rgba(0, 0, 0, .05) |
||||
border-color: #aaa |
||||
box-shadow: inset 0 1px 8px rgba(0, 0, 0, .15) |
||||
display: block |
||||
margin: 0 |
||||
transition: margin 85ms ease-in, |
||||
background 85ms ease-in |
||||
width: 100% |
||||
|
||||
.edit-controls |
||||
height: 32px |
||||
transition: margin 85ms ease-in, |
||||
height 85ms ease-in |
||||
overflow: hidden |
||||
margin: 4px 0 0 |
||||
|
||||
input[type=submit] |
||||
margin-top: 0 |
||||
min-height: 30px |
||||
height: 30px |
||||
|
||||
.list-header |
||||
flex: 0 0 auto |
||||
padding: 10px 26px 4px 6px |
||||
position: relative |
||||
min-height: 20px |
||||
|
||||
.list-header-name |
||||
display: inline |
||||
font-size: 16px |
||||
line-height: 17px |
||||
margin: 0 |
||||
font-weight: bold |
||||
min-height: 9px |
||||
min-width: 30px |
||||
overflow: hidden |
||||
text-overflow: ellipsis |
||||
word-wrap: break-word |
||||
|
||||
.list-header-menu-icon |
||||
background-clip: content-box |
||||
background-origin: content-box |
||||
padding: 6px 8px |
||||
position: absolute |
||||
top: 3px |
||||
right: -5px |
||||
color: #a6a6a6 |
||||
|
||||
.list-header-num-cards |
||||
color: #8c8c8c |
||||
margin: 0 |
||||
|
||||
.minicards |
||||
// flex: 1 1 auto |
||||
overflow-y: auto |
||||
overflow-x: hidden |
||||
padding: 4px 4px 1px |
||||
z-index: 1 |
||||
height: 100% |
||||
|
||||
&::-webkit-scrollbar-button |
||||
display: block |
||||
height: 4px |
||||
|
||||
.open-card-composer |
||||
border-top-left-radius: 0 |
||||
border-top-right-radius: 0 |
||||
border-bottom-right-radius: 3px |
||||
border-bottom-left-radius: 3px |
||||
color: #8c8c8c |
||||
display: block |
||||
// flex: 0 0 auto |
||||
margin: 2px -3px -3px |
||||
padding: 7px 10px |
||||
position: relative |
||||
text-decoration: none |
||||
|
||||
&:hover |
||||
background: #c3c3c3 |
||||
color: #222 |
||||
text-decoration: underline |
||||
|
||||
|
||||
&::selection |
||||
background: transparent |
||||
|
||||
.list.placeholder |
||||
background-color: rgba(0, 0, 0, .2) |
||||
border-color: transparent |
||||
box-shadow: none |
||||
height: 100px |
||||
|
||||
.list.ui-sortable-helper |
||||
cursor: grabbing |
||||
box-shadow: -2px 2px 8px rgba(0, 0, 0, .3), 0 0 1px rgba(0, 0, 0, .5) |
||||
transform: rotate(4deg) |
||||
|
||||
|
||||
.list.ui-sortable-helper .list-header-menu-icon |
||||
display: none |
@ -0,0 +1,28 @@ |
||||
template(name="listActionPopup") |
||||
ul.pop-over-list |
||||
li: a.js-add-card {{_ 'add-card'}} |
||||
li: a.highlight-icon.js-list-subscribe {{_ 'subscribe'}} |
||||
if cards.count |
||||
hr |
||||
ul.pop-over-list |
||||
li: a.js-move-cards {{_ 'list-move-cards'}} |
||||
li: a.js-archive-cards {{_ 'list-archive-cards'}} |
||||
hr |
||||
ul.pop-over-list |
||||
li: a.js-close-list {{_ 'archive-list'}} |
||||
|
||||
template(name="listMoveCardsPopup") |
||||
+boardLists |
||||
|
||||
template(name="boardLists") |
||||
ul.pop-over-list |
||||
each currentBoard.lists |
||||
li |
||||
if($eq ../_id _id) |
||||
a.disabled {{title}} ({{_ 'current'}}) |
||||
else |
||||
a.js-select-list= title |
||||
|
||||
template(name="listArchiveCardsPopup") |
||||
p {{_ 'list-archive-cards-pop'}} |
||||
input.js-confirm.negate.full(type="submit" value="{{_ 'archive-all'}}") |
@ -0,0 +1,46 @@ |
||||
Template.listActionPopup.events({ |
||||
'click .js-add-card': function() { |
||||
// XXX We need a better API and architecture here. See
|
||||
// https://github.com/peerlibrary/meteor-blaze-components/issues/19
|
||||
var listDom = document.getElementById('js-list-' + this._id); |
||||
var listComponent = Blaze.getView(listDom).templateInstance().get('component'); |
||||
listComponent.openForm(); |
||||
Popup.close(); |
||||
}, |
||||
'click .js-list-subscribe': function() {}, |
||||
'click .js-move-cards': Popup.open('listMoveCards'), |
||||
'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', function() { |
||||
Cards.find({listId: this._id}).forEach(function(card) { |
||||
Cards.update(card._id, { |
||||
$set: { |
||||
archived: true |
||||
} |
||||
}); |
||||
}); |
||||
Popup.close(); |
||||
}), |
||||
'click .js-close-list': function(evt) { |
||||
evt.preventDefault(); |
||||
Lists.update(this._id, { |
||||
$set: { |
||||
archived: true |
||||
} |
||||
}); |
||||
Popup.close(); |
||||
} |
||||
}); |
||||
|
||||
Template.listMoveCardsPopup.events({ |
||||
'click .js-select-list': function() { |
||||
var fromList = Template.parentData(2).data._id; |
||||
var toList = this._id; |
||||
Cards.find({listId: fromList}).forEach(function(card) { |
||||
Cards.update(card._id, { |
||||
$set: { |
||||
listId: toList |
||||
} |
||||
}); |
||||
}); |
||||
Popup.close(); |
||||
} |
||||
}); |
@ -0,0 +1,8 @@ |
||||
Template.editor.events({ |
||||
// Pressing Ctrl+Enter should submit the form.
|
||||
'keydown textarea': function(event) { |
||||
if (event.keyCode === 13 && (event.metaKey || event.ctrlKey)) { |
||||
$(event.currentTarget).parents('form:first').submit(); |
||||
} |
||||
} |
||||
}); |
@ -0,0 +1,40 @@ |
||||
template(name="header") |
||||
#header(class=currentBoard.colorClass) |
||||
//- |
||||
If the user is connected we display a small "quick-access" top bar that |
||||
list all starred boards with a link to go there. This is inspired by the |
||||
Reddit "subreddit" bar. |
||||
The first link goes to the boards page. |
||||
if currentUser |
||||
#header-quick-access |
||||
ul |
||||
li |
||||
+linkTo(route="Boards") |
||||
span.fa.fa-home |
||||
| All boards |
||||
each currentUser.starredBoards |
||||
li.separator - |
||||
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") |
||||
+linkTo(route="Board" data=this) |
||||
= title |
||||
else |
||||
li.current Star a board to add a shortcut in this bar. |
||||
|
||||
li |
||||
a.js-create-board |
||||
i.fa.fa-plus(title="Create a new board") |
||||
|
||||
+headerUserBar |
||||
|
||||
//- |
||||
The main bar is a colorful bar that provide all the meta-data for the |
||||
current page. This bar is contextual based. |
||||
If the user is not connected we display "sign in" and "log in" buttons. |
||||
#header-main-bar |
||||
if $.Session.get 'currentBoard' |
||||
+headerBoard |
||||
else |
||||
+headerTitle |
||||
|
||||
template(name="headerTitle") |
||||
h1 LibreBoard |
@ -0,0 +1,10 @@ |
||||
Template.header.helpers({ |
||||
// Reactively set the color of the page from the color of the current board.
|
||||
headerTemplate: function() { |
||||
return 'headerBoard'; |
||||
} |
||||
}); |
||||
|
||||
Template.header.events({ |
||||
'click .js-create-board': Popup.open('createBoard') |
||||
}); |
@ -0,0 +1,266 @@ |
||||
@import 'nib' |
||||
|
||||
global-reset() |
||||
|
||||
#header |
||||
color: white |
||||
transition: background-color 0.4s |
||||
background: #27AE60 |
||||
|
||||
#header-quick-access |
||||
background-color: rgba(0, 0, 0, 0.2) |
||||
padding: 4px 10px |
||||
height: 16px |
||||
font-size: 12px |
||||
display: flex |
||||
|
||||
ul li, #header-user-bar |
||||
color: darken(white, 17%) |
||||
|
||||
a |
||||
color: inherit |
||||
text-decoration: none |
||||
|
||||
&:hover |
||||
color: white |
||||
|
||||
ul |
||||
flex: 1 |
||||
transition: opacity 0.2s |
||||
margin-left: 5px |
||||
|
||||
li |
||||
display: block |
||||
float: left |
||||
width: auto |
||||
color: darken(white, 15%) |
||||
padding: 0 4px 1px 4px |
||||
|
||||
&.separator |
||||
padding: 0 2px 1px 2px |
||||
|
||||
&.current |
||||
font-style: italic |
||||
|
||||
&:first-child .fa-home |
||||
margin-right: 5px |
||||
|
||||
#header-main-bar |
||||
height: 30px |
||||
padding: 8px |
||||
|
||||
h1 |
||||
font-size: 19px |
||||
line-height: 1.7em |
||||
margin: 0 20px 0 10px |
||||
float: left |
||||
|
||||
&.header-board-menu |
||||
cursor: pointer |
||||
|
||||
.fa-angle-down |
||||
font-size: 0.8em |
||||
// line-height: 1.1em |
||||
margin-left: 5px |
||||
|
||||
.board-header-starred .fa |
||||
color: yellow |
||||
|
||||
.board-header-members |
||||
float: right |
||||
|
||||
.member |
||||
display: block |
||||
width: 32px |
||||
height: @width |
||||
|
||||
.add-board-member |
||||
color: white |
||||
display: flex |
||||
align-items: center |
||||
justify-content: center |
||||
border: 1px solid white |
||||
height: 32px - 2px |
||||
width: @height |
||||
|
||||
i.fa-plus |
||||
margin-top: 2px |
||||
|
||||
.header-btn:last-child |
||||
margin-right: 0 |
||||
|
||||
|
||||
|
||||
// #header { |
||||
// background: #138871; |
||||
// height: 30px; |
||||
// overflow: hidden; |
||||
// padding: 5px; |
||||
// position: relative; |
||||
// z-index: 10; |
||||
// } |
||||
|
||||
// .header-logo { |
||||
// bottom: 0; |
||||
// display: block; |
||||
// height: 25px; |
||||
// left: 50%; |
||||
// position: absolute; |
||||
// top: 8px; |
||||
// width: 80px; |
||||
// margin-left: - @width/2; |
||||
// text-align: center; |
||||
// z-index: 2; |
||||
// opacity: .5; |
||||
// transition: opacity ease-in 85ms; |
||||
// color: white; |
||||
// font-size: 22px; |
||||
// text-decoration: none; |
||||
// background-image: url('/logos/white_logo.png'); |
||||
|
||||
// &:hover { |
||||
// opacity: .8; |
||||
// color: white; |
||||
// } |
||||
// } |
||||
|
||||
// .header-btn.header-btn-feedback { |
||||
// background: rgba(255, 255, 255, .1); |
||||
// background: linear-gradient(to bottom, rgba(255, 255, 255, .1) 0, rgba(255, 255, 255, .05) 100%); |
||||
// padding-left: 22px; |
||||
// margin-right: 16px; |
||||
|
||||
// .header-btn-icon { |
||||
// top: 1px; |
||||
// } |
||||
// } |
||||
|
||||
.header-btn { |
||||
border-radius: 3px; |
||||
user-select: none; |
||||
background: rgba(255, 255, 255, .3); |
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, .3) 0, rgba(255, 255, 255, .2) 100%); |
||||
color: #f3f3f3; |
||||
display: block; |
||||
float: left; |
||||
font-weight: 700; |
||||
height: 30px; |
||||
line-height: 30px; |
||||
padding: 0 10px; |
||||
position: relative; |
||||
margin-right: 8px; |
||||
min-width: 30px; |
||||
text-decoration: none; |
||||
cursor: pointer; |
||||
|
||||
.header-btn-icon { |
||||
font-size: 16px; |
||||
line-height: 28px; |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
} |
||||
|
||||
&.new-notifications { |
||||
background: #ba1212; |
||||
|
||||
&:hover { |
||||
background: #d11515; |
||||
} |
||||
} |
||||
|
||||
&.header-member .member { |
||||
margin: 0; |
||||
border-top-left-radius: 3px; |
||||
border-top-right-radius: 0; |
||||
border-bottom-right-radius: 0; |
||||
border-bottom-left-radius: 3px; |
||||
|
||||
&:hover .member-avatar { |
||||
opacity: 1; |
||||
} |
||||
} |
||||
|
||||
&:hover { |
||||
background: rgba(255, 255, 255, .4); |
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, .4) 0, rgba(255, 255, 255, .3) 100%); |
||||
color: #fff; |
||||
|
||||
.header-btn-count { |
||||
background: #d11515; |
||||
} |
||||
} |
||||
|
||||
&:active { |
||||
background: rgba(255, 255, 255, .4); |
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, .4) 0, rgba(255, 255, 255, .3) 100%); |
||||
} |
||||
|
||||
&.upgrade { |
||||
margin-right: 16px; |
||||
|
||||
.icon-sm { |
||||
padding: 6px 2px 6px 4px; |
||||
} |
||||
} |
||||
|
||||
&.upgrade, |
||||
&.header-boards { |
||||
padding-left: 4px; |
||||
} |
||||
|
||||
&.header-boards { |
||||
padding-right: 4px; |
||||
} |
||||
|
||||
&.header-login, |
||||
&.header-signup { |
||||
padding: 0 12px; |
||||
} |
||||
|
||||
&.header-signup { |
||||
background: #48b512; |
||||
background: linear-gradient(to bottom, #48b512 0, #3d990f 100%); |
||||
|
||||
&:hover { |
||||
background: #3d990f; |
||||
background: linear-gradient(to bottom, #3d990f 0, #327d0c 100%); |
||||
} |
||||
|
||||
&:active { |
||||
background: #327d0c; |
||||
} |
||||
} |
||||
|
||||
&.header-go-to-boards { |
||||
padding: 0 8px 0 38px; |
||||
} |
||||
|
||||
&.header-go-to-boards .member { |
||||
border-top-left-radius: 3px; |
||||
border-top-right-radius: 0; |
||||
border-bottom-right-radius: 0; |
||||
border-bottom-left-radius: 3px; |
||||
position: absolute; |
||||
left: 0; |
||||
} |
||||
} |
||||
|
||||
// .header-btn-text { |
||||
// padding: 0 8px; |
||||
// } |
||||
|
||||
// .header-notification-list ul { |
||||
// margin-top: 8px; |
||||
// } |
||||
|
||||
// .header-notification-list .action-comment { |
||||
// max-height: 250px; |
||||
// overflow-y: auto; |
||||
// } |
||||
|
||||
// .header-user { |
||||
// position: absolute; |
||||
// top: 5px; |
||||
// right: 0; |
||||
// } |
@ -0,0 +1,63 @@ |
||||
var Helpers = { |
||||
error: function() { |
||||
return Session.get('error'); |
||||
}, |
||||
|
||||
toLowerCase: function(text) { |
||||
return text && text.toLowerCase(); |
||||
}, |
||||
|
||||
toUpperCase: function(text) { |
||||
return text && text.toUpperCase(); |
||||
}, |
||||
|
||||
firstChar: function(text) { |
||||
return text && text[0].toUpperCase(); |
||||
}, |
||||
|
||||
session: function(prop) { |
||||
return Session.get(prop); |
||||
}, |
||||
|
||||
getUser: function(userId) { |
||||
return Users.findOne(userId); |
||||
} |
||||
}; |
||||
|
||||
// Register all Helpers
|
||||
_.each(Helpers, function(fn, name) { Blaze.registerHelper(name, fn); }); |
||||
|
||||
// XXX I believe we should compute a HTML rendered field on the server that
|
||||
// would handle markdown, emojies and user mentions. We can simply have two
|
||||
// fields, one source, and one compiled version (in HTML) and send only the
|
||||
// compiled version to most users -- who don't need to edit.
|
||||
// In the meantime, all the transformation are done on the client using the
|
||||
// Blaze API.
|
||||
var at = HTML.CharRef({html: '@', str: '@'}); |
||||
Blaze.Template.registerHelper('mentions', new Template('mentions', function() { |
||||
var view = this; |
||||
var content = Blaze.toHTML(view.templateContentBlock); |
||||
var currentBoard = Session.get('currentBoard'); |
||||
var knowedUsers = _.map(currentBoard.members, function(member) { |
||||
member.username = Users.findOne(member.userId).username; |
||||
return member; |
||||
}); |
||||
|
||||
var mentionRegex = /\B@(\w*)/gi; |
||||
var currentMention, knowedUser, href, linkClass, linkValue, link; |
||||
while (currentMention = mentionRegex.exec(content)) { |
||||
|
||||
knowedUser = _.findWhere(knowedUsers, { username: currentMention[1] }); |
||||
if (! knowedUser) |
||||
continue; |
||||
|
||||
linkValue = [' ', at, knowedUser.username]; |
||||
href = Router.url('Profile', { username: knowedUser.username }); |
||||
linkClass = 'atMention' + (knowedUser.userId === Meteor.userId() ? ' me' : ''); |
||||
link = HTML.A({ href: href, 'class': linkClass }, linkValue); |
||||
|
||||
content = content.replace(currentMention[0], Blaze.toHTML(link)); |
||||
} |
||||
|
||||
return HTML.Raw(content); |
||||
})); |
@ -0,0 +1,17 @@ |
||||
head |
||||
title LibreBoard |
||||
meta(name="viewport" |
||||
content="maximum-scale=1.0,width=device-width,initial-scale=1.0,user-scalable=0") |
||||
link(rel="shortcut icon" href="/favicon.png") |
||||
|
||||
template(name="userFormsLayout") |
||||
h1.at-form-landing-logo |
||||
img(src="/logo.png" title="LibreBoard") |
||||
+yield |
||||
|
||||
template(name="defaultLayout") |
||||
#surface |
||||
+header |
||||
#content |
||||
+yield |
||||
|
@ -0,0 +1,16 @@ |
||||
Popup.template.events({ |
||||
click: function(evt) { |
||||
if (evt.originalEvent) { |
||||
evt.originalEvent.clickInPopup = true; |
||||
} |
||||
}, |
||||
'click .js-back-view': function() { |
||||
Popup.back(); |
||||
}, |
||||
'click .js-close-popover': function() { |
||||
Popup.close(); |
||||
}, |
||||
'click .js-confirm': function() { |
||||
this.__afterConfirmAction.call(this); |
||||
} |
||||
}); |
@ -0,0 +1,585 @@ |
||||
@import 'nib' |
||||
|
||||
.pop-over |
||||
background: #fff |
||||
border-radius: 3px |
||||
border: 1px solid #dbdbdb |
||||
border-bottom-color: #c2c2c2 |
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, .3) |
||||
display: none |
||||
overflow: hidden |
||||
position: absolute |
||||
width: 300px |
||||
z-index: 99999 |
||||
margin-top: 5px |
||||
|
||||
hr |
||||
margin: 4px -10px |
||||
width: 275px + 2*10px |
||||
|
||||
input[type="text"], |
||||
input[type="email"], |
||||
input[type="password"] |
||||
margin: 4px 0 12px |
||||
width: 100% |
||||
|
||||
input[type="file"] |
||||
width: 240px |
||||
|
||||
select |
||||
width: 100% |
||||
margin-bottom: 14px |
||||
|
||||
textarea |
||||
height: 72px |
||||
margin: 4px 0 12px |
||||
width: 100% |
||||
|
||||
.empty |
||||
margin: 0 |
||||
|
||||
img |
||||
max-width: 270px |
||||
|
||||
.custom-image img |
||||
height: 18px |
||||
left: 9px |
||||
top: 9px |
||||
width: 18px |
||||
|
||||
.title |
||||
line-height: 32px |
||||
|
||||
.header |
||||
height: 36px |
||||
position: relative |
||||
margin-bottom: 8px |
||||
background: #F7F7F7 |
||||
border-bottom: 1px solid #dcdcdc |
||||
color: darken(white, 60%) |
||||
|
||||
.header-title |
||||
display: block |
||||
line-height: 32px |
||||
padding-top: 4px |
||||
margin: 0 10px |
||||
font-weight: bold |
||||
overflow: hidden |
||||
text-overflow: ellipsis |
||||
white-space: nowrap |
||||
|
||||
.back-btn, .close-btn |
||||
&:hover .icon-sm |
||||
color: darken(white, 80%) |
||||
|
||||
.back-btn |
||||
padding: 10px |
||||
float: left |
||||
|
||||
.close-btn |
||||
padding: 10px 10px 10px 4px |
||||
position: absolute |
||||
top: 0 |
||||
right: 0 |
||||
|
||||
.content |
||||
overflow-x: hidden |
||||
overflow-y: auto |
||||
padding: 0 10px 10px |
||||
max-height: 550px |
||||
|
||||
.quiet |
||||
padding: 6px 6px 4px |
||||
|
||||
&.search-over |
||||
background: #f0f0f0 |
||||
min-height: 114px |
||||
|
||||
.header |
||||
display: none |
||||
|
||||
.content |
||||
padding: 8px 4px 8px 10px |
||||
margin-right: 8px |
||||
|
||||
&::-webkit-scrollbar-button |
||||
display: block |
||||
height: 4px |
||||
width: 4px |
||||
|
||||
.select-members-list |
||||
margin-bottom: 8px |
||||
|
||||
.pop-over-list |
||||
|
||||
&.navigable li.not-selectable>a:hover, |
||||
li.not-selectable>a:hover |
||||
color: #8c8c8c |
||||
cursor: default |
||||
|
||||
.icon-sm |
||||
color: #a6a6a6 |
||||
|
||||
li > a |
||||
cursor: pointer |
||||
display: block |
||||
font-weight: 700 |
||||
padding: 6px 10px |
||||
position: relative |
||||
margin: 0 -10px |
||||
text-decoration: none |
||||
|
||||
.item-name |
||||
display: block |
||||
width: auto |
||||
padding-right: 22px |
||||
|
||||
&:hover |
||||
background-color: #005377 |
||||
color: #fff |
||||
|
||||
.sub-name, |
||||
.quiet |
||||
color: #eee |
||||
|
||||
.unread-indicator |
||||
background: #fff |
||||
|
||||
.icon-sm |
||||
color: #fff |
||||
|
||||
.sub-name |
||||
clear: both |
||||
color: #8c8c8c |
||||
display: block |
||||
font-size: 12px |
||||
font-weight: 400 |
||||
line-height: 15px |
||||
margin-top: 4px |
||||
|
||||
&.current |
||||
background-color: #e2e6e9 |
||||
|
||||
.unread-indicator |
||||
background: #2e85b8 |
||||
background: linear-gradient(to bottom, #2e85b8 0, #2b7cab 100%) |
||||
border-radius: 7px |
||||
display: block |
||||
height: 14px |
||||
opacity: 0 |
||||
position: absolute |
||||
right: 16px |
||||
top: 8px |
||||
width: 14px |
||||
|
||||
&.any |
||||
opacity: 1 |
||||
|
||||
&:active |
||||
background-color: #2e85b8 |
||||
|
||||
&.disabled |
||||
color: #8c8c8c |
||||
cursor: default |
||||
|
||||
.vis-icon |
||||
opacity: .35 |
||||
|
||||
.icon-sm |
||||
color: #a6a6a6 |
||||
|
||||
&:hover |
||||
background: none |
||||
|
||||
.sub-name, |
||||
.quiet |
||||
color: #8c8c8c |
||||
|
||||
.icon-sm |
||||
color: #a6a6a6 |
||||
|
||||
&:active |
||||
background: none |
||||
|
||||
&.inset li > a |
||||
border-radius: 3px |
||||
margin: 0 |
||||
|
||||
.pop-over-list.checkable |
||||
|
||||
.icon-check |
||||
display: none |
||||
position: absolute |
||||
top: 6px |
||||
right: 12px |
||||
|
||||
li.active a |
||||
padding-right: 28px |
||||
|
||||
.icon-check |
||||
display: block |
||||
|
||||
&.left-check |
||||
|
||||
.icon-check |
||||
right: auto |
||||
left: 10px |
||||
|
||||
li a |
||||
padding-right: 10px |
||||
padding-left: 30px |
||||
|
||||
li.active a |
||||
padding-right: 10px |
||||
|
||||
&.normal-weight li>a |
||||
font-weight: 400 |
||||
|
||||
&.navigable |
||||
|
||||
li > a:hover |
||||
background-color: transparent |
||||
color: #4d4d4d |
||||
|
||||
.sub-name, |
||||
.quiet |
||||
color: #8c8c8c |
||||
|
||||
.icon-sm |
||||
color: #a6a6a6 |
||||
|
||||
li.selected > a |
||||
background-color: #005377 |
||||
color: #fff |
||||
|
||||
.sub-name, |
||||
.quiet |
||||
color: #eee |
||||
|
||||
li.selected > a |
||||
|
||||
&.current |
||||
background-color: #005377 |
||||
|
||||
.unread-indicator |
||||
background: #fff |
||||
|
||||
.icon-sm |
||||
color: #fff |
||||
|
||||
&:active |
||||
background-color: #005377 |
||||
|
||||
.pop-over.miniprofile |
||||
|
||||
.header |
||||
border-bottom-color: transparent |
||||
height: 30px |
||||
position: absolute |
||||
right: 0 |
||||
top: 0 |
||||
width: 60px |
||||
z-index: 1 |
||||
|
||||
.header-title |
||||
display: none |
||||
|
||||
.pop-over-list |
||||
padding-top: 8px |
||||
|
||||
.mini-profile-info |
||||
margin-top: 8px |
||||
min-height: 56px |
||||
position: relative |
||||
|
||||
.member-large |
||||
position: absolute |
||||
top: 2px |
||||
left: 2px |
||||
|
||||
.info |
||||
margin: 0 0 0 64px |
||||
word-wrap: break-word |
||||
|
||||
h3 a |
||||
text-decoration: none |
||||
|
||||
&:hover |
||||
text-decoration: underline |
||||
|
||||
.pop-over.avdetail .header |
||||
border-bottom-color: transparent |
||||
height: 20px |
||||
position: absolute |
||||
top: 8px |
||||
left: 8px |
||||
right: 8px |
||||
z-index: 0 |
||||
|
||||
.pop-over.avdetail .header-title |
||||
display: none |
||||
|
||||
.pop-over.avdetail .content |
||||
text-align: center |
||||
|
||||
.pop-over.avdetail .mem-info |
||||
margin: 2px 24px 8px |
||||
position: relative |
||||
z-index: 1 |
||||
width: 222px |
||||
|
||||
.pop-over.avdetail .mem-info h3 a |
||||
text-decoration: none |
||||
|
||||
.pop-over.avdetail .mem-info h3 a:hover |
||||
text-decoration: underline |
||||
|
||||
.pop-over-label-list li, |
||||
.pop-over-member-list li |
||||
|
||||
&.disabled a |
||||
cursor:default |
||||
|
||||
&:not(.disabled):hover a |
||||
background-color: #005377 |
||||
color: #fff |
||||
|
||||
|
||||
.pop-over-label-list, |
||||
.pop-over-member-list, |
||||
.pop-over-emoji-list, |
||||
.pop-over-card-list |
||||
li |
||||
a |
||||
border-radius: 3px |
||||
display: block |
||||
height: 30px |
||||
line-height: 30px |
||||
overflow: hidden |
||||
position: relative |
||||
text-overflow: ellipsis |
||||
text-decoration: none |
||||
white-space: nowrap |
||||
padding: 4px |
||||
margin-bottom: 2px |
||||
|
||||
&.multi-line |
||||
line-height: 16px |
||||
|
||||
.member |
||||
margin-right: 8px |
||||
|
||||
.card-label |
||||
float: left |
||||
height: 30px |
||||
margin: 0 8px 0 0 |
||||
padding: 0 |
||||
width: 30px |
||||
|
||||
.option, |
||||
.icon-check |
||||
background-clip: content-box |
||||
background-origin: content-box |
||||
padding: 11px |
||||
position: absolute |
||||
top: 0 |
||||
right: 0 |
||||
|
||||
.sub-name |
||||
font-size: 12px |
||||
|
||||
|
||||
&:last-child a |
||||
margin-bottom: 0 |
||||
|
||||
&.disabled |
||||
opacity: .5 |
||||
|
||||
&.active a, |
||||
&.selected a |
||||
background: none |
||||
color: #4d4d4d |
||||
cursor: default |
||||
|
||||
.quiet |
||||
color: #8c8c8c |
||||
|
||||
&.email-invite |
||||
|
||||
.member |
||||
display: none |
||||
|
||||
a |
||||
padding: 0 10px |
||||
|
||||
&.selected a |
||||
background-color: #005377 |
||||
color: #fff |
||||
|
||||
.quiet |
||||
color: #eee |
||||
|
||||
.card-label |
||||
border-radius: 3px |
||||
|
||||
.icon-check |
||||
color: #fff |
||||
|
||||
&.active a .icon-check |
||||
display: block |
||||
|
||||
&.unconfirmed a.name |
||||
line-height: 16px |
||||
|
||||
&.options li |
||||
|
||||
&.selected a |
||||
padding-right: 28px |
||||
|
||||
.option |
||||
display: block |
||||
opacity: .5 |
||||
|
||||
&:hover |
||||
opacity: 1 |
||||
|
||||
&.disabled.selected a |
||||
padding-right: 0 |
||||
|
||||
.option |
||||
display: none |
||||
|
||||
|
||||
&.no-option.selected a |
||||
padding-right: 6px |
||||
|
||||
.option |
||||
display: none |
||||
|
||||
&.collapsed |
||||
|
||||
&.checkable li.active a |
||||
padding-right: 0 |
||||
|
||||
li |
||||
float: left |
||||
margin: 0 3px 3px 0 |
||||
|
||||
a |
||||
padding: 0 |
||||
margin: 0 |
||||
width: 30px |
||||
|
||||
.member |
||||
opacity: .8 |
||||
|
||||
.full-name |
||||
display: none |
||||
|
||||
&.selected a .member, |
||||
&.active.selected a .member |
||||
border-color: #005377 |
||||
opacity: .9 |
||||
|
||||
&.active a |
||||
|
||||
.member |
||||
border-color: #2e85b8 |
||||
opacity: 1 |
||||
|
||||
.icon-check |
||||
border-radius: 3px |
||||
background-color: #2e85b8 |
||||
bottom: 0 |
||||
color: #fff |
||||
display: block |
||||
padding: 0 |
||||
right: 0 |
||||
top: auto |
||||
|
||||
&.checkable li.active a |
||||
padding-right: 28px |
||||
|
||||
&.filtered li |
||||
display: none |
||||
|
||||
&.matches-filter |
||||
display: block |
||||
|
||||
&.limited li.exceeds-limit |
||||
display: none |
||||
|
||||
.pop-over-emoji-list li > a |
||||
padding: 2px 4px |
||||
|
||||
.emoji |
||||
margin: 0 6px |
||||
|
||||
.pop-over-card-list li > a |
||||
padding: 2px 4px |
||||
|
||||
.login-signup-popover |
||||
padding: 15px |
||||
|
||||
.form-tabs |
||||
display: none |
||||
|
||||
h1 |
||||
margin-bottom: 15px |
||||
|
||||
p |
||||
margin: 8px 0 |
||||
|
||||
.form-parts-container |
||||
position: relative |
||||
|
||||
.active-box |
||||
position: absolute |
||||
top: 0 |
||||
background: #e2e2e2 |
||||
border: 1px solid #c9c9c9 |
||||
border-radius: 3px |
||||
z-index: 1 |
||||
height: 100% |
||||
width: 49% |
||||
transition-property: all |
||||
transition-duration: .4s |
||||
opacity: 1 |
||||
|
||||
&.start |
||||
opacity: 0 |
||||
left: 25% |
||||
|
||||
.signup-form, |
||||
.login-form |
||||
position: relative |
||||
box-sizing: border-box |
||||
padding: 20px |
||||
width: 50% |
||||
z-index: 2 |
||||
opacity: .3 |
||||
transition-property: opacity |
||||
transition-duration: .2s |
||||
|
||||
.active |
||||
opacity: 1 |
||||
|
||||
|
||||
.js-signup-form-pos |
||||
left: 0 |
||||
|
||||
.login-form |
||||
position: absolute |
||||
top: 0 |
||||
|
||||
.login-form .icon-google |
||||
position: absolute |
||||
left: 5px |
||||
top: 3px |
||||
|
||||
.login-form .button.google |
||||
padding-left: 40px |
||||
margin: 0 0 15px 0 |
||||
|
||||
.js-login-form-pos |
||||
left: 50% |
@ -0,0 +1,13 @@ |
||||
.pop-over.clearfix( |
||||
class="{{#unless title}}miniprofile{{/unless}}" |
||||
class=currentBoard.colorClass |
||||
style="display:block; left:{{offset.left}}px; top:{{offset.top}}px;") |
||||
.header.clearfix |
||||
if hasPopupParent |
||||
a.back-btn.js-back-view |
||||
i.fa.fa-chevron-left |
||||
span.header-title= title |
||||
a.close-btn.js-close-popover |
||||
i.fa.fa-times |
||||
.content.clearfix |
||||
+Template.dynamic(template=popupName data=dataContext) |
@ -0,0 +1,40 @@ |
||||
Template.editor.rendered = function() { |
||||
this.$('textarea').textcomplete([ |
||||
// Emojies
|
||||
{ |
||||
match: /\B:([\-+\w]*)$/, |
||||
search: function(term, callback) { |
||||
callback($.map(Emoji.values, function(emoji) { |
||||
return emoji.indexOf(term) === 0 ? emoji : null; |
||||
})); |
||||
}, |
||||
template: function(value) { |
||||
var image = '<img src="' + Emoji.baseImagePath + value + '.png"></img>'; |
||||
return image + value; |
||||
}, |
||||
replace: function(value) { |
||||
return ':' + value + ':'; |
||||
}, |
||||
index: 1 |
||||
}, |
||||
|
||||
// User mentions
|
||||
{ |
||||
match: /\B@(\w*)$/, |
||||
search: function(term, callback) { |
||||
var currentBoard = Boards.findOne(Session.get('currentBoard')); |
||||
callback($.map(currentBoard.members, function(member) { |
||||
var username = Users.findOne(member.userId).username; |
||||
return username.indexOf(term) === 0 ? username : null; |
||||
})); |
||||
}, |
||||
template: function(value) { |
||||
return value; |
||||
}, |
||||
replace: function(username) { |
||||
return '@' + username + ' '; |
||||
}, |
||||
index: 1 |
||||
} |
||||
]); |
||||
}; |
@ -0,0 +1,5 @@ |
||||
Router.route('/', { |
||||
name: 'Home', |
||||
redirectLoggedInUsers: true, |
||||
authenticated: true |
||||
}); |
@ -0,0 +1,45 @@ |
||||
/* |
||||
* From https://github.com/tobiasahlin/SpinKit |
||||
* |
||||
* Usage: |
||||
* |
||||
* <div class="sk-spinner sk-spinner-wave"> |
||||
* <div class="sk-rect1"></div> |
||||
* <div class="sk-rect2"></div> |
||||
* <div class="sk-rect3"></div> |
||||
* <div class="sk-rect4"></div> |
||||
* <div class="sk-rect5"></div> |
||||
* </div> |
||||
* |
||||
*/ |
||||
|
||||
.sk-spinner-wave { |
||||
|
||||
&.sk-spinner { |
||||
width: 50px; |
||||
height: 50px; |
||||
margin: auto; |
||||
margin-top: 30vh; |
||||
text-align: center; |
||||
font-size: 10px; |
||||
} |
||||
|
||||
div { |
||||
background-color: #333; |
||||
height: 100%; |
||||
width: 6px; |
||||
display: inline-block; |
||||
|
||||
animation: sk-waveStretchDelay 1.2s infinite ease-in-out; |
||||
} |
||||
|
||||
.sk-rect2 { animation-delay: -1.1s } |
||||
.sk-rect3 { animation-delay: -1.0s } |
||||
.sk-rect4 { animation-delay: -0.9s } |
||||
.sk-rect5 { animation-delay: -0.8s } |
||||
} |
||||
|
||||
@keyframes sk-waveStretchDelay { |
||||
0%, 40%, 100% { transform: scaleY(0.4) } |
||||
20% { transform: scaleY(1.0) } |
||||
} |
@ -0,0 +1,6 @@ |
||||
.sk-spinner.sk-spinner-wave(class=currentBoard.colorClass) |
||||
.sk-rect1 |
||||
.sk-rect2 |
||||
.sk-rect3 |
||||
.sk-rect4 |
||||
.sk-rect5 |
@ -0,0 +1,18 @@ |
||||
<template name="notfound"> |
||||
{{ > message label='page-not-found'}} |
||||
</template> |
||||
|
||||
<template name='message'> |
||||
<div class="big-message quiet {{ color }}"> |
||||
<h1>{{_ label}}</h1> |
||||
{{#with pathFor route='Login'}} |
||||
<p>{{{_ 'page-maybe-private' this}}}</p> |
||||
{{/with}} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="editor"> |
||||
<textarea class="{{class}}" placeholder="{{_ 'comment-placeholder'}}" id="{{id}}" tabindex="1">{{> UI.contentBlock }}</textarea> |
||||
</template> |
||||
|
||||
<template name="viewer">{{#markdown}}{{#emoji}}{{#mentions}}{{> UI.contentBlock }}{{/mentions}}{{/emoji}}{{/markdown}}</template> |
@ -0,0 +1,14 @@ |
||||
Template.modal.events({ |
||||
'click .window-overlay': function(event) { |
||||
// We only want to catch the event if the user click on the .window-overlay
|
||||
// div itself, not a child (ie, not the overlay window)
|
||||
if (event.target !== event.currentTarget) |
||||
return; |
||||
Utils.goBoardId(this.card.board()._id); |
||||
event.preventDefault(); |
||||
}, |
||||
'click .js-close-window': function(event) { |
||||
Utils.goBoardId(this.card.board()._id); |
||||
event.preventDefault(); |
||||
} |
||||
}); |
@ -0,0 +1,5 @@ |
||||
.window-overlay.show |
||||
.window |
||||
.window-wrapper.clearfix |
||||
a.icon-lg.fa.fa-times.dialog-close-button.js-close-window(title="{{_ 'modal-close-title'}}") |
||||
+UI.dynamic(template=template) |
@ -0,0 +1,93 @@ |
||||
Template.filterSidebar.events({ |
||||
'click .js-toggle-label-filter': function(event) { |
||||
Filter.labelIds.toogle(this._id); |
||||
Filter.resetExceptions(); |
||||
event.preventDefault(); |
||||
}, |
||||
'click .js-toogle-member-filter': function(event) { |
||||
Filter.members.toogle(this._id); |
||||
Filter.resetExceptions(); |
||||
event.preventDefault(); |
||||
}, |
||||
'click .js-clear-all': function(event) { |
||||
Filter.reset(); |
||||
event.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
var getMemberIndex = function(board, searchId) { |
||||
for (var i = 0; i < board.members.length; i++) { |
||||
if (board.members[i].userId === searchId) |
||||
return i; |
||||
} |
||||
throw new Meteor.Error('Member not found'); |
||||
}; |
||||
|
||||
Template.memberPopup.events({ |
||||
'click .js-filter-member': function() { |
||||
Filter.members.toogle(this.userId); |
||||
Popup.close(); |
||||
}, |
||||
'click .js-change-role': Popup.open('changePermissions'), |
||||
'click .js-remove-member': Popup.afterConfirm('removeMember', function() { |
||||
var currentBoard = Boards.findOne(Session.get('currentBoard')); |
||||
var memberIndex = getMemberIndex(currentBoard, this.userId); |
||||
var setQuery = {}; |
||||
setQuery[['members', memberIndex, 'isActive'].join('.')] = false; |
||||
Boards.update(currentBoard._id, { $set: setQuery }); |
||||
Popup.close(); |
||||
}), |
||||
'click .js-leave-member': function() { |
||||
// @TODO
|
||||
Popup.close(); |
||||
} |
||||
}); |
||||
|
||||
Template.membersWidget.events({ |
||||
'click .js-open-manage-board-members': Popup.open('addMember'), |
||||
'click .member': Popup.open('member') |
||||
}); |
||||
|
||||
Template.labelsWidget.events({ |
||||
'click .js-label': Popup.open('editLabel'), |
||||
'click .js-add-label': Popup.open('createLabel') |
||||
}); |
||||
|
||||
// Template.addMemberPopup.events({
|
||||
// 'click .pop-over-member-list li:not(.disabled)': function(event, t) {
|
||||
// var userId = this._id;
|
||||
// var boardId = t.data.board._id;
|
||||
// var currentMembersIds = _.pluck(t.data.board.members, 'userId');
|
||||
// if (currentMembersIds.indexOf(userId) === -1) {
|
||||
// Boards.update(boardId, {
|
||||
// $push: {
|
||||
// members: {
|
||||
// userId: userId,
|
||||
// isAdmin: false,
|
||||
// isActive: true
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// } else {
|
||||
// var memberIndex = getMemberIndex(t.data.board, userId);
|
||||
// var setQuery = {};
|
||||
// setQuery[['members', memberIndex, 'isActive'].join('.')] = true;
|
||||
// Boards.update(boardId, { $set: setQuery });
|
||||
// }
|
||||
// Popup.close();
|
||||
// }
|
||||
// });
|
||||
|
||||
// Template.changePermissionsPopup.events({
|
||||
// 'click .js-set-admin, click .js-set-normal': function(event) {
|
||||
// var currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
// var memberIndex = getMemberIndex(currentBoard, this.user._id);
|
||||
// var isAdmin = $(event.currentTarget).hasClass('js-set-admin');
|
||||
// var setQuery = {};
|
||||
// setQuery[['members', memberIndex, 'isAdmin'].join('.')] = isAdmin;
|
||||
// Boards.update(currentBoard._id, {
|
||||
// $set: setQuery
|
||||
// });
|
||||
// Popup.back(1);
|
||||
// }
|
||||
// });
|
@ -0,0 +1,51 @@ |
||||
var widgetTitles = { |
||||
filter: 'filter-cards', |
||||
background: 'change-background' |
||||
}; |
||||
|
||||
Template.boardSidebar.helpers({ |
||||
currentWidget: function() { |
||||
return Session.get('currentWidget') + 'Sidebar'; |
||||
}, |
||||
currentWidgetTitle: function() { |
||||
return TAPi18n.__(widgetTitles[Session.get('currentWidget')]); |
||||
} |
||||
}); |
||||
|
||||
// Template.addMemberPopup.helpers({
|
||||
// isBoardMember: function() {
|
||||
// var user = Users.findOne(this._id);
|
||||
// return user && user.isBoardMember();
|
||||
// }
|
||||
// });
|
||||
|
||||
Template.memberPopup.helpers({ |
||||
user: function() { |
||||
return Users.findOne(this.userId); |
||||
}, |
||||
memberType: function() { |
||||
var type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal'; |
||||
return TAPi18n.__(type).toLowerCase(); |
||||
} |
||||
}); |
||||
|
||||
// Template.removeMemberPopup.helpers({
|
||||
// user: function() {
|
||||
// return Users.findOne(this.userId)
|
||||
// },
|
||||
// board: function() {
|
||||
// return currentBoard();
|
||||
// }
|
||||
// });
|
||||
|
||||
// Template.changePermissionsPopup.helpers({
|
||||
// isAdmin: function() {
|
||||
// return this.user.isBoardAdmin();
|
||||
// },
|
||||
// isLastAdmin: function() {
|
||||
// if (! this.user.isBoardAdmin())
|
||||
// return false;
|
||||
// var nbAdmins = _.where(currentBoard().members, { isAdmin: true }).length;
|
||||
// return nbAdmins === 1;
|
||||
// }
|
||||
// });
|
@ -0,0 +1,37 @@ |
||||
var peakAnticipation = 200; |
||||
|
||||
Mixins.InfiniteScrolling = BlazeComponent.extendComponent({ |
||||
onCreated: function() { |
||||
this._nextPeak = Infinity; |
||||
}, |
||||
|
||||
setNextPeak: function(v) { |
||||
this._nextPeak = v; |
||||
}, |
||||
|
||||
getNextPeak: function() { |
||||
return this._nextPeak; |
||||
}, |
||||
|
||||
resetNextPeak: function() { |
||||
this._nextPeak = Infinity; |
||||
}, |
||||
|
||||
// To be overwritten by consumers of this mixin
|
||||
reachNextPeak: function() { |
||||
|
||||
}, |
||||
|
||||
events: function() { |
||||
return [{ |
||||
scroll: function(evt) { |
||||
var domElement = evt.currentTarget; |
||||
var altitude = domElement.scrollTop + domElement.offsetHeight; |
||||
altitude += peakAnticipation; |
||||
if (altitude >= this.callFirstWith(null, 'getNextPeak')) { |
||||
this.callFirstWith(null, 'reachNextPeak'); |
||||
} |
||||
} |
||||
}]; |
||||
} |
||||
}); |
@ -0,0 +1,21 @@ |
||||
Template.membersWidget.rendered = function() { |
||||
if (! Meteor.user().isBoardMember()) |
||||
return; |
||||
|
||||
_.each(['.js-member', '.js-label'], function(className) { |
||||
Utils.liveEvent('mouseover', function($this) { |
||||
$this.find(className).draggable({ |
||||
appendTo: 'body', |
||||
helper: 'clone', |
||||
revert: 'invalid', |
||||
revertDuration: 150, |
||||
snap: false, |
||||
snapMode: 'both', |
||||
start: function() { |
||||
Popup.close(); |
||||
} |
||||
}); |
||||
}); |
||||
}); |
||||
}; |
||||
|
@ -0,0 +1,55 @@ |
||||
BlazeComponent.extendComponent({ |
||||
template: function() { |
||||
return 'boardSidebar'; |
||||
}, |
||||
|
||||
mixins: function() { |
||||
return [Mixins.InfiniteScrolling]; |
||||
}, |
||||
|
||||
onCreated: function() { |
||||
this._isOpen = new ReactiveVar(true); |
||||
}, |
||||
|
||||
isOpen: function() { |
||||
return this._isOpen.get(); |
||||
}, |
||||
|
||||
open: function() { |
||||
if (! this._isOpen.get()) { |
||||
this._isOpen.set(true); |
||||
} |
||||
}, |
||||
|
||||
hide: function() { |
||||
if (this._isOpen.get()) { |
||||
this._isOpen.set(false); |
||||
} |
||||
}, |
||||
|
||||
toogle: function() { |
||||
this._isOpen.set(! this._isOpen.get()); |
||||
}, |
||||
|
||||
calculateNextPeak: function() { |
||||
var altitude = this.find('.js-board-sidebar-content').scrollHeight; |
||||
this.callFirstWith(this, 'setNextPeak', altitude); |
||||
}, |
||||
|
||||
reachNextPeak: function() { |
||||
var activitiesComponent = this.componentChildren('activities')[0]; |
||||
activitiesComponent.loadNextPage(); |
||||
}, |
||||
|
||||
isTongueHidden: function() { |
||||
return this.isOpen() && Filter.isActive(); |
||||
}, |
||||
|
||||
events: function() { |
||||
// XXX Hacky, we need some kind of `super`
|
||||
var mixinEvents = this.getMixin(Mixins.InfiniteScrolling).events(); |
||||
return mixinEvents.concat([{ |
||||
'click .js-toogle-sidebar': this.toogle |
||||
}]); |
||||
} |
||||
}).register('boardSidebar'); |
@ -0,0 +1,154 @@ |
||||
@import 'nib' |
||||
|
||||
.sidebar |
||||
.sidebar-content |
||||
padding: 10px 20px |
||||
background: white |
||||
box-shadow: -10px 0px 5px -10px darken(white, 30%) |
||||
z-index: 10 |
||||
position: absolute |
||||
top: 0 |
||||
bottom: 0 |
||||
right: 0 |
||||
left: 0 |
||||
overflow-x: hidden |
||||
overflow-y: auto |
||||
|
||||
h3 |
||||
color: darken(white, 50%) |
||||
|
||||
hr |
||||
margin: 8px 0 |
||||
|
||||
.board-sidebar |
||||
width: 248px |
||||
position: absolute |
||||
top: 0 |
||||
right: -@width |
||||
bottom: 0 |
||||
transition: top .1s, right .1s, width .1s |
||||
|
||||
&.is-open |
||||
right: 0 |
||||
|
||||
.board-widget-nav |
||||
border-radius: 3px |
||||
background: #dcdcdc |
||||
overflow: hidden |
||||
padding: 0 |
||||
position: relative |
||||
|
||||
.toggle-widget-nav |
||||
border-radius: 3px |
||||
color: #8c8c8c |
||||
margin: 0 |
||||
padding: 7px 10px |
||||
position: relative |
||||
cursor: pointer |
||||
|
||||
.toggle-menu-icon |
||||
position: absolute |
||||
top: 8px |
||||
right: 8px |
||||
|
||||
&:hover |
||||
background: #ccc |
||||
color: #4d4d4d |
||||
|
||||
.nav-list |
||||
display: block |
||||
opacity: 1 |
||||
max-height: 400px |
||||
|
||||
hr |
||||
margin: 2px 0 |
||||
color: #ccc |
||||
background: #ccc |
||||
|
||||
.nav-list-item |
||||
display: block |
||||
font-weight: 700 |
||||
line-height: 30px |
||||
overflow: hidden |
||||
padding: 0 8px 0 36px |
||||
position: relative |
||||
text-decoration: none |
||||
|
||||
.icon-type |
||||
left: 10px |
||||
position: absolute |
||||
top: 6px |
||||
|
||||
&:hover |
||||
background: #ccc |
||||
|
||||
.icon-type |
||||
color: #686868 |
||||
|
||||
.nav-list-sub-item |
||||
font-weight: 400 |
||||
color: #666 |
||||
|
||||
&:hover |
||||
color: #4d4d4d |
||||
|
||||
&.collapsed |
||||
|
||||
.nav-list |
||||
max-height: 0 |
||||
opacity: 0 |
||||
|
||||
hr |
||||
margin: 0 |
||||
|
||||
.toggle-widget-nav |
||||
color: #4d4d4d |
||||
|
||||
|
||||
.board-widget-title |
||||
display: block |
||||
min-height: 20px |
||||
margin-bottom: 6px |
||||
|
||||
.board-widget-content |
||||
position: relative |
||||
z-index: 1 |
||||
|
||||
.board-widget h4 |
||||
margin: 5px 0 |
||||
|
||||
.board-widget-activity |
||||
margin-right: -4px |
||||
|
||||
.sidebar-tongue |
||||
display: block |
||||
width: 30px |
||||
height: @width |
||||
left: -@width |
||||
position: absolute |
||||
top: 12px |
||||
z-index: 15 |
||||
background: white |
||||
border-radius: left 3px |
||||
box-shadow: -4px 0px 7px -4px darken(white, 30%) |
||||
color: darken(white, 50%) |
||||
transition: left .1s |
||||
|
||||
i.fa |
||||
margin: 9px |
||||
transition: transform 0.5s |
||||
|
||||
.board-sidebar.is-open & |
||||
left: -@width + 2px |
||||
|
||||
// XXX Bug: we should add a padding left |
||||
&:hover |
||||
left: -@width + 5px |
||||
|
||||
i.fa |
||||
transform: rotate(180deg) |
||||
|
||||
&.is-hidden, |
||||
.board-sidebar.is-open &.is-hidden |
||||
z-index: 0 |
||||
left: 5px |
@ -0,0 +1,307 @@ |
||||
<template name="boardWidgets"> |
||||
<a href="#" class="sidebar-show-btn dark-hover js-show-sidebar"> |
||||
<span class="icon-sm fa fa-chevron-left"></span> |
||||
<span class="text">{{_ 'show-sidebar'}}</span> |
||||
</a> |
||||
<div class="board-widgets {{#if session 'sidebarIsOpen'}}show{{else}}hide{{/if}}"> |
||||
<div> |
||||
<a href="#" class="sidebar-hide-btn dark-hover js-hide-sidebar" title="{{_ 'close-sidebar-title'}}"> |
||||
<span class="icon-sm fa fa-chevron-right"></span> |
||||
</a> |
||||
{{#unless isTrue currentWidget "homeWidget"}} |
||||
<div class="board-widgets-title clearfix"> |
||||
<a href="#" class="board-sidebar-back-btn js-pop-widget-view"> |
||||
<span class="left-arrow"></span>{{_ 'back'}} |
||||
</a> |
||||
<h3 class="text">{{currentWidgetTitle}}</h3> |
||||
<hr> |
||||
</div> |
||||
{{/unless}} |
||||
<div class="board-widgets-content-wrapper"> |
||||
<div class="board-widgets-content default fancy-scrollbar short{{#unless session 'menuWidgetIsOpen'}} short{{/unless}}"> |
||||
{{> UI.dynamic template=currentWidget data=this }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="homeWidget"> |
||||
{{ > menuWidget }} |
||||
{{ > membersWidget }} |
||||
{{ > activityWidget }} |
||||
</template> |
||||
|
||||
<template name="menuWidget"> |
||||
<div class="board-widget board-widget-nav clearfix{{#unless session 'menuWidgetIsOpen'}} collapsed{{/unless}}"> |
||||
<h3 class="dark-hover toggle-widget-nav js-toggle-widget-nav">{{_ 'menu'}} |
||||
<span class="icon-sm fa fa-chevron-circle-down toggle-menu-icon"></span> |
||||
</h3> |
||||
<ul class="nav-list"> |
||||
<hr style="margin-top: 0;"> |
||||
<li> |
||||
<a href="#" class="nav-list-item js-open-archive"> |
||||
<span class="icon-sm fa fa-archive icon-type"></span> |
||||
{{_ 'archived-items'}} |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a href="#" class="nav-list-item js-open-card-filter"> |
||||
<span class="icon-sm fa fa-filter icon-type"></span> |
||||
{{_ 'filter-cards'}} |
||||
</a> |
||||
</li> |
||||
{{#if currentUser.isBoardAdmin}} |
||||
<hr> |
||||
<li> |
||||
<a class="nav-list-item nav-list-sub-item board-settings-background js-change-background"> |
||||
<span class="board-settings-background-preview" style="background-color:{{board.background.color}}"></span> |
||||
{{_ 'change-background'}}… |
||||
</a> |
||||
</li> |
||||
{{#unless isSandstorm }} |
||||
<li> |
||||
<a class="nav-list-item nav-list-sub-item js-close-board" href="#">{{_ 'close-board'}}</a> |
||||
</li> |
||||
{{/unless}} |
||||
{{/if}} |
||||
{{! |
||||
XXX Language should be handled by sandstorm, but for now display a language selection link in the board menu. |
||||
This link is normally present in the header bar that is not displayed on sandstorm. |
||||
}} |
||||
{{#if isSandstorm}} |
||||
<hr> |
||||
<li> |
||||
<a class="nav-list-item nav-list-sub-item js-language">{{_ 'language'}}</a> |
||||
</li> |
||||
{{/if}} |
||||
</ul> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="membersWidget"> |
||||
<hr> |
||||
<div class="board-widget board-widget-members clearfix"> |
||||
<div class="board-widget-title"> |
||||
<h3>{{_ 'members'}}</h3> |
||||
</div> |
||||
<div class="board-widget-content"> |
||||
<div class="board-widget-members js-list-board-members clearfix js-list-draggable-board-members"> |
||||
{{# each board.members }} |
||||
{{> userAvatar userId=this.userId draggable=true size="small" showBadges=true}} |
||||
{{/ each }} |
||||
</div> |
||||
{{# unless isSandstrom }} |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<a href="#" class="button-link js-open-manage-board-members"> |
||||
<span class="icon-sm fa fa-user"></span> {{_ 'add-members'}} |
||||
</a> |
||||
{{/ if }} |
||||
{{/ unless }} |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="activityWidget"> |
||||
{{# if board.activities.count }} |
||||
<hr> |
||||
<div class="board-widget board-widget-activity bottom clearfix"> |
||||
<div class="board-widget-title"> |
||||
<h3>{{_ 'activity'}}</h3> |
||||
</div> |
||||
<div class="board-widget-content"> |
||||
<div class="activity-gradient-t"></div> |
||||
<div class="activity-gradient-b"></div> |
||||
<div class="board-actions-list fancy-scrollbar"> |
||||
{{ > activities }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{/if}} |
||||
</template> |
||||
|
||||
<template name="memberPopup"> |
||||
<div class="board-member-menu"> |
||||
<div class="mini-profile-info"> |
||||
{{> userAvatar user=user}} |
||||
<div class="info"> |
||||
<h3 class="bottom" style="margin-right: 40px;"> |
||||
<a class="js-profile" href="{{ pathFor route='Profile' username=user.username }}">{{ user.profile.name }}</a> |
||||
</h3> |
||||
<p class="quiet bottom">@{{ user.username }}</p> |
||||
</div> |
||||
</div> |
||||
{{# if currentUser.isBoardMember }} |
||||
<ul class="pop-over-list"> |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<li> |
||||
<a class="js-change-role" href="#"> |
||||
{{_ 'change-permissions'}} <span class="quiet" style="font-weight: normal;">({{ memberType }})</span> |
||||
</a> |
||||
</li> |
||||
{{/ if }} |
||||
|
||||
<li> |
||||
{{# if currentUser.isBoardAdmin }} |
||||
<a class="js-remove-member">{{_ 'remove-from-board'}}</a> |
||||
{{ else }} |
||||
<a class="js-leave-member">{{_ 'leave-board'}}</a> |
||||
{{/ if }} |
||||
</li> |
||||
</ul> |
||||
{{/ if }} |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="filterWidget"> |
||||
<ul class="pop-over-label-list checkable"> |
||||
{{#each board.labels}} |
||||
<li class="item matches-filter"> |
||||
<a class="name js-toggle-label-filter"> |
||||
<span class="card-label card-label-{{color}}"></span> |
||||
<span class="full-name"> |
||||
{{#if name}} |
||||
{{name}} |
||||
{{else}} |
||||
<span class="quiet">{{_ "label-default" color}}</span> |
||||
{{/if}} |
||||
</span> |
||||
{{#if Filter.labelIds.isSelected _id}} |
||||
<span class="icon-sm fa fa-check"></span> |
||||
{{/if}} |
||||
</a> |
||||
</li> |
||||
{{/each}} |
||||
</ul> |
||||
<hr> |
||||
<ul class="pop-over-member-list checkable"> |
||||
{{#each board.members}} |
||||
{{#with getUser userId}} |
||||
<li class="item js-member-item {{#if Filter.members.isSelected _id}}active{{/if}}"> |
||||
<a href="#" class="name js-toogle-member-filter"> |
||||
{{> userAvatar user=this size="small" }} |
||||
<span class="full-name"> |
||||
{{ profile.name }} |
||||
(<span class="username">{{ username }}</span>) |
||||
</span> |
||||
{{#if Filter.members.isSelected _id}} |
||||
<span class="icon-sm fa fa-check checked-icon"></span> |
||||
{{/if}} |
||||
</a> |
||||
</li> |
||||
{{/with}} |
||||
{{/each}} |
||||
</ul> |
||||
<hr> |
||||
<ul class="pop-over-list inset normal-weight"> |
||||
<li> |
||||
<a class="js-clear-all {{#unless Filter.isActive}}disabled{{/unless}}" style="padding-left: 40px;"> |
||||
{{_ 'filter-clear'}} |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
</template> |
||||
|
||||
<template name="backgroundWidget"> |
||||
<div class="board-widgets-content-wrapper fancy-scrollbar"> |
||||
<div class="board-widgets-content"> |
||||
<div class="board-backgrounds-list clearfix"> |
||||
{{#each backgroundColors}} |
||||
<div class="board-background-select js-select-background"> |
||||
<span class="background-box " style="background-color: {{this}}; "></span> |
||||
</div> |
||||
{{/each}} |
||||
</div> |
||||
{{!-- |
||||
<h2 class="clear">Photos</h2> |
||||
<div class="board-backgrounds-list relative clearfix js-gold-photos-list disabled"> |
||||
<div class="board-background-select js-select-background"> |
||||
<span class="background-box " style="background-image: url("{{url}}");"> |
||||
<a class="background-option js-background-attribution" href={{href}} target="_blank" title={{title}}> |
||||
<img src="https://d78fikflryjgj.cloudfront.net/images/d906fe5c1274c56c5571d49705547587/cc.png" style="height: 14px; width: 14px; vertical-align: text-top;" title="http://creativecommons.org/licenses/by/2.0/deed.en"> |
||||
<span class="text" style="margin-left: 2px;">{{author}}</span> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
--}} |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="closeBoardPopup"> |
||||
<p>{{_ 'close-board-pop'}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'close'}}"> |
||||
</template> |
||||
|
||||
<template name="removeMemberPopup"> |
||||
<p>{{_ 'remove-member-pop' |
||||
name=user.profile.name |
||||
username=user.username |
||||
boardTitle=board.title}}</p> |
||||
<input type="submit" class="js-confirm negate full" value="{{_ 'remove-member'}}"> |
||||
</template> |
||||
|
||||
<template name="addMemberPopup"> |
||||
<div class="search-with-spinner"> |
||||
{{> esInput index="users" }} |
||||
</div> |
||||
|
||||
<div class="manage-member-section hide js-search-results" style="display: block;"> |
||||
<ul class="pop-over-member-list options js-list"> |
||||
{{# esEach index="users"}} |
||||
<li class="item js-member-item {{# if isBoardMember }}disabled{{/if}}"> |
||||
<a href="#" class="name js-select-member {{# if isBoardMember }}multi-line{{/if}}" title="{{ profile.name }} ({{ username }})"> |
||||
{{> userAvatar user=this size="small" }} |
||||
<span class="full-name"> |
||||
{{ profile.name }} (<span class="username">{{ username }}</span>) |
||||
</span> |
||||
{{# if isBoardMember }} |
||||
<div class="extra-text quiet">({{_ 'joined'}})</div> |
||||
{{/if}} |
||||
<span class="icon-sm fa fa-chevron-right light option js-open-option"></span> |
||||
</a> |
||||
</li> |
||||
{{/esEach }} |
||||
</ul> |
||||
</div> |
||||
|
||||
{{# ifEsIsSearching index='users' }} |
||||
<div class="tac"> |
||||
<span class="tabbed-pane-main-col-loading-spinner spinner"></span> |
||||
</div> |
||||
{{ /ifEsIsSearching }} |
||||
|
||||
{{# ifEsHasNoResults index="users" }} |
||||
<div class="manage-member-section js-no-results"> |
||||
<p class="quiet center" style="padding: 16px 4px;">{{_ 'no-results'}}</p> |
||||
</div> |
||||
{{ /ifEsHasNoResults }} |
||||
|
||||
<div class="manage-member-section js-helper"> |
||||
<p class="bottom quiet" style="padding: 6px;">{{_ 'search-member-desc'}}</p> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="changePermissionsPopup"> |
||||
<ul class="pop-over-list"> |
||||
<li> |
||||
<a class="{{#if isLastAdmin}}disabled{{else}}js-set-admin{{/if}}"> |
||||
{{_ 'admin'}} |
||||
{{#if isAdmin}}<span class="icon-sm fa fa-check"></span>{{/if}} |
||||
<span class="sub-name">{{_ 'admin-desc'}}</span> |
||||
</a> |
||||
</li> |
||||
<li> |
||||
<a class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}"> |
||||
{{_ 'normal'}} |
||||
{{#unless isAdmin}}<span class="icon-sm fa fa-check"></span>{{/unless}} |
||||
<span class="sub-name">{{_ 'normal-desc'}}</span> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
{{#if isLastAdmin}} |
||||
<hr> |
||||
<p class="quiet bottom">{{_ 'last-admin-desc'}}</p> |
||||
{{/if}} |
||||
</template> |
@ -0,0 +1,103 @@ |
||||
template(name="boardSidebar") |
||||
.board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}") |
||||
a.sidebar-tongue.js-toogle-sidebar( |
||||
class="{{#if isTongueHidden}}is-hidden{{/if}}") |
||||
i.fa.fa-chevron-left |
||||
.sidebar-content.js-board-sidebar-content |
||||
//- XXX https://github.com/peerlibrary/meteor-blaze-components/issues/30 |
||||
if Filter.isActive |
||||
+filterSidebar |
||||
else |
||||
+homeSidebar |
||||
|
||||
template(name='homeSidebar') |
||||
+membersWidget |
||||
hr.clear |
||||
+labelsWidget |
||||
hr.clear |
||||
h3 |
||||
i.fa.fa-comments-o |
||||
| {{_ 'activities'}} |
||||
+activities(mode="board") |
||||
|
||||
template(name="filterSidebar") |
||||
ul.pop-over-label-list.checkable |
||||
each currentBoard.labels |
||||
li.item.matches-filter |
||||
a.name.js-toggle-label-filter |
||||
span.card-label(class="card-label-{{color}}") |
||||
span.full-name |
||||
if name |
||||
= name |
||||
else |
||||
span.quiet {{_ "label-default" color}} |
||||
if Filter.labelIds.isSelected _id}} |
||||
span.icon-sm.fa.fa-check |
||||
hr |
||||
ul.pop-over-member-list.checkable |
||||
each currentBoard.members |
||||
if isActive |
||||
with getUser userId |
||||
li.item.js-member-item( |
||||
class="{{#if Filter.members.isSelected _id}}active{{/if}}") |
||||
a.name.js-toogle-member-filter |
||||
+userAvatar(user=this size="small") |
||||
span.full-name |
||||
= profile.name |
||||
| (<span class="username">{{ username }}</span>) |
||||
if Filter.members.isSelected _id |
||||
span.icon-sm.fa.fa-check |
||||
hr |
||||
a.js-clear-all(class="{{#unless Filter.isActive}}disabled{{/unless}}") |
||||
| {{_ 'filter-clear'}} |
||||
|
||||
template(name="membersWidget") |
||||
.board-widget.board-widget-members |
||||
h3 |
||||
i.fa.fa-user |
||||
| {{_ 'members'}} |
||||
.board-widget-content |
||||
each currentBoard.members |
||||
+userAvatar( |
||||
userId=this.userId |
||||
draggable=true |
||||
size="small" |
||||
showBadges=true) |
||||
unless isSandstorm |
||||
if currentUser.isBoardAdmin |
||||
a.js-open-manage-board-members |
||||
|
||||
template(name="labelsWidget") |
||||
.board-widget.board-widget-labels |
||||
h3 |
||||
i.fa.fa-tags |
||||
| {{_ 'labels'}} |
||||
.board-widget-content |
||||
each currentBoard.labels |
||||
a.card-label(class="card-label-{{color}}").js-label |
||||
span.card-label-name= name |
||||
a.card-label.js-add-label |
||||
i.fa.fa-plus |
||||
|
||||
template(name="memberPopup") |
||||
.board-member-menu: .mini-profile-info |
||||
+userAvatar(user=user) |
||||
.info |
||||
h3.bottom |
||||
a.js-profile(href="{{pathFor route='Profile' username=user.username}}") |
||||
= user.profile.name |
||||
p.quiet.bottom @#{user.username} |
||||
if currentUser.isBoardMember |
||||
ul.pop-over-list |
||||
li |
||||
a.js-filter-member Filter cards |
||||
if currentUser.isBoardAdmin |
||||
li |
||||
a.js-change-role |
||||
| {{_ 'change-permissions'}} |
||||
span.quiet (#{memberType}) |
||||
li |
||||
if currentUser.isBoardAdmin |
||||
a.js-remove-member {{_ 'remove-from-board'}} |
||||
else |
||||
a.js-leave-member {{_ 'leave-board'}} |
@ -0,0 +1,7 @@ |
||||
template(name="userAvatar") |
||||
.member(class="{{class}} {{# if draggable }}js-member{{else}}js-member-on-card-menu{{/if}}" |
||||
title="{{userData.profile.name}} ({{userData.username}})") |
||||
+avatar(user=userData size=size) |
||||
if showBadges |
||||
span.member-status(class="{{# if userData.profile.status}}active{{/if}}") |
||||
span.member-type(class=memberType) |
@ -0,0 +1,59 @@ |
||||
// XXX This should be handled by default (and in a better way) by useraccounts.
|
||||
// See https://github.com/meteor-useraccounts/core/issues/384
|
||||
Template.atForm.onRendered(function() { |
||||
this.find('input').focus(); |
||||
}); |
||||
|
||||
Template.memberMenuPopup.events({ |
||||
'click .js-language': Popup.open('setLanguage'), |
||||
'click .js-logout': function(evt) { |
||||
evt.preventDefault(); |
||||
|
||||
Meteor.logout(function() { |
||||
Router.go('Home'); |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
Template.setLanguagePopup.events({ |
||||
'click .js-set-language': function(evt) { |
||||
Users.update(Meteor.userId(), { |
||||
$set: { |
||||
'profile.language': this.tag |
||||
} |
||||
}); |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.profileEditForm.events({ |
||||
'click .js-edit-profile': function() { |
||||
Session.set('ProfileEditForm', true); |
||||
}, |
||||
'click .js-cancel-edit-profile': function() { |
||||
Session.set('ProfileEditForm', false); |
||||
}, |
||||
'submit #ProfileEditForm': function(evt, t) { |
||||
var name = t.find('#name').value; |
||||
var bio = t.find('#bio').value; |
||||
|
||||
// trim and update
|
||||
if ($.trim(name)) { |
||||
Users.update(this.profile()._id, { |
||||
$set: { |
||||
'profile.name': name, |
||||
'profile.bio': bio |
||||
} |
||||
}, function() { |
||||
|
||||
// update complete close profileEditForm
|
||||
Session.set('ProfileEditForm', false); |
||||
}); |
||||
} |
||||
evt.preventDefault(); |
||||
} |
||||
}); |
||||
|
||||
Template.memberName.events({ |
||||
'click .js-show-mem-menu': Popup.open('user') |
||||
}); |
@ -0,0 +1,50 @@ |
||||
.at-form-landing-logo |
||||
width: 275px |
||||
margin: auto |
||||
margin-top: 50px |
||||
margin-top: 17vh |
||||
|
||||
img |
||||
width: 275px |
||||
|
||||
|
||||
.at-form |
||||
margin: auto |
||||
width: 275px |
||||
padding: 25px |
||||
margin-top: 20px |
||||
padding-bottom: 10px |
||||
background: #fff |
||||
border-radius: 3px |
||||
border: 1px solid #dbdbdb |
||||
border-bottom-color: #c2c2c2 |
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, .3) |
||||
|
||||
.at-link |
||||
color: darken(#27AE60, 40%) |
||||
|
||||
label |
||||
margin-bottom: 3px |
||||
|
||||
input |
||||
width: 100% |
||||
|
||||
.at-title |
||||
background: #F7F7F7 |
||||
margin: -25px |
||||
padding: 15px 25px 5px |
||||
margin-bottom: 20px |
||||
border-bottom: 1px solid #dcdcdc |
||||
color: darken(white, 70%) |
||||
font-weight: bold |
||||
|
||||
.at-signup-link, |
||||
.at-signin-link, |
||||
.at-forgotPwd |
||||
font-size: 0.9em |
||||
margin-top: 15px |
||||
color: darken(white, 70%) |
||||
|
||||
.at-signUp, |
||||
.at-signIn |
||||
font-weight: bold |
@ -0,0 +1,27 @@ |
||||
template(name="headerUserBar") |
||||
#header-user-bar |
||||
if currentUser |
||||
a.js-open-header-member-menu |
||||
if currentUser.profile.name |
||||
= currentUser.profile.name |
||||
else |
||||
= currentUser.username |
||||
i.fa.fa-chevron-down |
||||
else |
||||
a(href="{{pathFor route='signUp'}}") Sign in |
||||
span.separator - |
||||
a(href="{{pathFor route='signIn'}}") Log in |
||||
|
||||
template(name="memberHeader") |
||||
a.header-member.js-open-header-member-menu |
||||
span= currentUser.profile.name |
||||
+userAvatar(user=currentUser size="small") |
||||
|
||||
template(name="memberMenuPopup") |
||||
ul.pop-over-list |
||||
li: a(href="{{pathFor route='Profile' username=currentUser.username}}") {{_ 'profile'}} |
||||
li: a.js-language {{_ 'language'}} |
||||
li: a(href = "{{pathFor route='Settings'}}") {{_ 'settings'}} |
||||
hr |
||||
ul.pop-over-list |
||||
li: a.js-logout {{_ 'log-out'}} |
@ -0,0 +1,5 @@ |
||||
Template.headerUserBar.events({ |
||||
'click .js-sign-in': Popup.open('signup'), |
||||
'click .js-log-in': Popup.open('login'), |
||||
'click .js-open-header-member-menu': Popup.open('memberMenu') |
||||
}); |
@ -0,0 +1,27 @@ |
||||
Template.userAvatar.helpers({ |
||||
userData: function() { |
||||
if (! this.user) { |
||||
this.user = Users.findOne(this.userId); |
||||
} |
||||
return this.user; |
||||
}, |
||||
memberType: function() { |
||||
var userId = this.userId || this.user._id; |
||||
var user = Users.findOne(userId); |
||||
return user && user.isBoardAdmin() ? 'admin' : 'normal'; |
||||
} |
||||
}); |
||||
|
||||
Template.setLanguagePopup.helpers({ |
||||
languages: function() { |
||||
return _.map(TAPi18n.getLanguages(), function(lang, tag) { |
||||
return { |
||||
tag: tag, |
||||
name: lang.name |
||||
}; |
||||
}); |
||||
}, |
||||
isCurrentLanguage: function() { |
||||
return this.tag === TAPi18n.getLanguage(); |
||||
} |
||||
}); |
@ -0,0 +1,107 @@ |
||||
@import 'nib' |
||||
|
||||
avatar-radius = 50% |
||||
|
||||
.member |
||||
border-radius: 3px |
||||
display: block |
||||
float: left |
||||
height: 30px |
||||
width: @height |
||||
margin: 0 4px 4px 0 |
||||
position: relative |
||||
cursor: pointer |
||||
user-select: none |
||||
z-index: 1 |
||||
text-decoration: none |
||||
border-radius: avatar-radius |
||||
|
||||
.avatar |
||||
height: 100% |
||||
width: @height |
||||
display: flex |
||||
align-items: center |
||||
justify-content: center |
||||
overflow: hidden |
||||
border-radius: avatar-radius |
||||
|
||||
.avatar-initials |
||||
font-weight: bold |
||||
max-width: 100% |
||||
max-height: 100% |
||||
font-size: 14px |
||||
line-height: 200% |
||||
background-color: #dbdbdb |
||||
color: #444444 |
||||
|
||||
.avatar-image |
||||
max-width: 100% |
||||
max-height: 100% |
||||
|
||||
.member-status |
||||
background-color: #b3b3b3 |
||||
border: 1px solid #fff |
||||
border-radius: 50% |
||||
height: 8px |
||||
width: @height |
||||
position: absolute |
||||
right: 0px |
||||
bottom: 0px |
||||
border: 1px solid white |
||||
|
||||
&.active |
||||
background: #64c464 |
||||
border-color: #daf1da |
||||
|
||||
&.idle |
||||
background: #e4e467 |
||||
border-color: #f7f7d4 |
||||
|
||||
&.disconnected |
||||
background: #bdbdbd |
||||
border-color: #ededed |
||||
|
||||
&.extra-small |
||||
.avatar-initials |
||||
font-size: 9px |
||||
width: 18px |
||||
height: 18px |
||||
line-height: 18px |
||||
|
||||
.avatar-image |
||||
width: 18px |
||||
height: 18px |
||||
|
||||
&.small |
||||
width: 30px |
||||
height: 30px |
||||
|
||||
.avatar-initials |
||||
font-size: 12px |
||||
line-height: 30px |
||||
|
||||
&.large |
||||
height: 85px |
||||
line-height: 85px |
||||
width: 85px |
||||
|
||||
.avatar |
||||
width: 85px |
||||
height: 85px |
||||
|
||||
.avatar-initials |
||||
font-size: 16px |
||||
font-weight: 700 |
||||
line-height: 85px |
||||
width: 85px |
||||
|
||||
.atMention |
||||
background: #dbdbdb |
||||
border-radius: 3px |
||||
padding: 1px 4px |
||||
margin: -1px 0 |
||||
display: inline-block |
||||
|
||||
&.me |
||||
background: #cfdfe8 |
||||
|
@ -0,0 +1,29 @@ |
||||
|
||||
_.each(['signIn', 'signUp', 'resetPwd', |
||||
'forgotPwd', 'enrollAccount', 'changePwd'], function(routeName) { |
||||
AccountsTemplates.configureRoute(routeName, { |
||||
layoutTemplate: 'userFormsLayout' |
||||
}); |
||||
}); |
||||
|
||||
Router.route('/profile/:username', { |
||||
name: 'Profile', |
||||
template: 'profile', |
||||
waitOn: function() { |
||||
return Meteor.subscribe('profile', this.params.username); |
||||
}, |
||||
data: function() { |
||||
var params = this.params; |
||||
return { |
||||
profile: function() { |
||||
return Users.findOne({ username: params.username }); |
||||
} |
||||
}; |
||||
} |
||||
}); |
||||
|
||||
Router.route('/settings', { |
||||
name: 'Settings', |
||||
template: 'settings', |
||||
layoutTemplate: 'AuthLayout' |
||||
}); |
@ -0,0 +1,118 @@ |
||||
<template name="setLanguagePopup"> |
||||
<ul class="pop-over-list"> |
||||
{{#each languages}} |
||||
<li class="{{# if isCurrentLanguage}}active{{/if}}"> |
||||
<a class="js-set-language"> |
||||
{{name}} |
||||
{{# if isCurrentLanguage}} |
||||
<span class="icon-sm fa fa-check"></span> |
||||
{{/if}} |
||||
</a> |
||||
</li> |
||||
{{/each}} |
||||
</ul> |
||||
</template> |
||||
|
||||
<template name='profile'> |
||||
{{ # if profile }} |
||||
<div class="tabbed-pane-header"> |
||||
<div class="tabbed-pane-header-wrapper clearfix"> |
||||
<a class="tabbed-pane-header-image profile-image ed js-change-avatar-profile" href="#"> |
||||
{{> userAvatar user=profile size="large"}} |
||||
</a> |
||||
<div class="tabbed-pane-header-details"> |
||||
<div class="js-current-details"> |
||||
<div class="tabbed-pane-header-details-name"> |
||||
<h1 class="inline"> {{ profile.profile.name }} </h1> |
||||
<p class="window-title-extra quiet"> @{{ profile.username }} </p> |
||||
</div> |
||||
<div class="tabbed-pane-header-details-content"> |
||||
<p>{{ profile.profile.bio }}</p> |
||||
</div> |
||||
<div class="tabbed-pane-header-details-content"></div> |
||||
</div> |
||||
{{ > profileEditForm }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{ else }} |
||||
{{ > message label='user-profile-not-found' }} |
||||
{{ /if }} |
||||
</template> |
||||
|
||||
<template name="settings"> |
||||
{{ > profile profile=currentUser }} |
||||
<div class="tabbed-pane-main-col clearfix"> |
||||
<div class="tabbed-pane-main-col-loading hide js-loading-page"> |
||||
<span class="tabbed-pane-main-col-loading-spinner spinner"></span> |
||||
</div> |
||||
<div class="tabbed-pane-main-col-wrapper js-content"> |
||||
<div class="window-module clearfix"> |
||||
<div class="window-module-title"> |
||||
<h3>{{_ "account-details"}}</h3> |
||||
</div> |
||||
<a class="big-link js-change-name-and-bio" href="#"> |
||||
<span class="text">{{_ 'change-name-initials-bio'}}</span> |
||||
</a> |
||||
<a class="big-link js-change-avatar" href="#"> |
||||
<span class="text">{{_ 'change-avatar'}}</span> |
||||
</a> |
||||
<a class="big-link js-change-password" href="#"> |
||||
<span class="text">{{_ 'change-password'}}</span> |
||||
</a> |
||||
<a class="big-link js-change-email" href="#"> |
||||
<span class="text">{{_ 'change-email'}}</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<template name="profileEditForm"> |
||||
{{#if $eq currentUser.username profile.username }} |
||||
{{# if session 'ProfileEditForm' }} |
||||
<form id="ProfileEditForm" class="js-profile-form"> |
||||
<p class="error js-profile-form-error hide"></p> |
||||
<label>{{_ "username"}}</label> |
||||
<input type="text" id="username" value="{{ profile.username }}" disabled> |
||||
<label>{{_ "fullname"}}</label> |
||||
<input type="text" id="name" value="{{ profile.profile.name }}"> |
||||
<label> |
||||
{{_ "bio"}} <span class="quiet">({{_ 'optional'}})</span> |
||||
</label> |
||||
<textarea id="bio">{{ profile.profile.bio }}</textarea> |
||||
<input type="submit" class="primary wide js-submit-profile" value="{{_ 'save'}}"> |
||||
<input type="button" class="js-cancel-edit-profile" value="{{_ 'cancel'}}"> |
||||
</form> |
||||
{{ else }} |
||||
<a class="button-link tabbed-pane-header-details-edit js-edit-profile" href="#"> |
||||
<span class="icon-sm fa fa-pencil"></span> |
||||
{{_ "edit-profile"}} |
||||
</a> |
||||
{{ /if }} |
||||
{{ /if }} |
||||
</template> |
||||
|
||||
<template name="userPopup"> |
||||
<div class="board-member-menu"> |
||||
<div class="mini-profile-info"> |
||||
{{> userAvatar user=user}} |
||||
<div class="info"> |
||||
<h3 class="bottom" style="margin-right: 40px;"> |
||||
<a class="js-profile" href="{{ pathFor route='Profile' username=user.username }}">{{ user.profile.name }}</a> |
||||
</h3> |
||||
<p class="quiet bottom">@{{ user.username }}</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
|
||||
<template name="memberName"> |
||||
<a class="inline-object js-show-mem-menu" href="{{ pathFor route='Profile' username=user.username }}"> |
||||
{{ user.profile.name }} |
||||
{{# if username }} |
||||
({{ user.username }}) |
||||
{{ /if }} |
||||
</a> |
||||
</template> |
@ -0,0 +1,35 @@ |
||||
AccountsTemplates.configure({ |
||||
confirmPassword: false, |
||||
enablePasswordChange: true, |
||||
sendVerificationEmail: true, |
||||
showForgotPasswordLink: true |
||||
}); |
||||
|
||||
AccountsTemplates.removeField('password'); |
||||
AccountsTemplates.removeField('email'); |
||||
AccountsTemplates.addFields([ |
||||
{ |
||||
_id: 'username', |
||||
type: 'text', |
||||
displayName: 'username', |
||||
required: true, |
||||
minLength: 5 |
||||
}, |
||||
{ |
||||
_id: 'email', |
||||
type: 'email', |
||||
required: true, |
||||
displayName: 'email', |
||||
re: /.+@(.+){2,}\.(.+){2,}/, |
||||
errStr: 'Invalid email' |
||||
}, |
||||
{ |
||||
_id: 'password', |
||||
type: 'password', |
||||
placeholder: { |
||||
signUp: 'At least six characters' |
||||
}, |
||||
required: true, |
||||
minLength: 6 |
||||
} |
||||
]); |
@ -0,0 +1,3 @@ |
||||
Avatar.options = { |
||||
fallbackType: 'initials' |
||||
}; |
@ -0,0 +1,28 @@ |
||||
Router.configure({ |
||||
loadingTemplate: 'spinner', |
||||
notFoundTemplate: 'notfound', |
||||
layoutTemplate: 'defaultLayout', |
||||
|
||||
onBeforeAction: function() { |
||||
var options = this.route.options; |
||||
|
||||
// Redirect logged in users to Boards view when they try to open Login or
|
||||
// signup views.
|
||||
if (Meteor.userId() && options.redirectLoggedInUsers) { |
||||
return this.redirect('Boards'); |
||||
} |
||||
|
||||
// Authenticated
|
||||
if (! Meteor.userId() && options.authenticated) { |
||||
return this.redirect('atSignIn'); |
||||
} |
||||
|
||||
// Reset default sessions
|
||||
Session.set('error', false); |
||||
Session.set('warning', false); |
||||
|
||||
Popup.close(); |
||||
|
||||
this.next(); |
||||
} |
||||
}); |
@ -0,0 +1,152 @@ |
||||
Emoji.values = ['+1', '-1', '100', '1234', '8ball', 'a', 'ab', 'abc', 'abcd', |
||||
'accept', 'aerial_tramway', 'airplane', 'alarm_clock', 'alien', 'ambulance', |
||||
'anchor', 'angel', 'anger', 'angry', 'anguished', 'ant', 'apple', 'aquarius', |
||||
'aries', 'arrow_backward', 'arrow_double_down', 'arrow_double_up', 'arrow_down', |
||||
'arrow_down_small', 'arrow_forward', 'arrow_heading_down', 'arrow_heading_up', |
||||
'arrow_left', 'arrow_lower_left', 'arrow_lower_right', 'arrow_right', |
||||
'arrow_right_hook', 'arrow_up', 'arrow_up_down', 'arrow_up_small', |
||||
'arrow_upper_left', 'arrow_upper_right', 'arrows_clockwise', |
||||
'arrows_counterclockwise', 'art', 'articulated_lorry', 'astonished', 'atm', 'b', |
||||
'baby', 'baby_bottle', 'baby_chick', 'baby_symbol', 'baggage_claim', 'balloon', |
||||
'ballot_box_with_check', 'bamboo', 'banana', 'bangbang', 'bank', 'bar_chart', |
||||
'barber', 'baseball', 'basketball', 'bath', 'bathtub', 'battery', 'bear', 'bee', |
||||
'beer', 'beers', 'beetle', 'beginner', 'bell', 'bento', 'bicyclist', 'bike', |
||||
'bikini', 'bird', 'birthday', 'black_circle', 'black_joker', 'black_nib', |
||||
'black_square', 'black_square_button', 'blossom', 'blowfish', 'blue_book', |
||||
'blue_car', 'blue_heart', 'blush', 'boar', 'boat', 'bomb', 'book', 'bookmark', |
||||
'bookmark_tabs', 'books', 'boom', 'boot', 'bouquet', 'bow', 'bowling', 'bowtie', |
||||
'boy', 'bread', 'bride_with_veil', 'bridge_at_night', 'briefcase', |
||||
'broken_heart', 'bug', 'bulb', 'bullettrain_front', 'bullettrain_side', 'bus', |
||||
'busstop', 'bust_in_silhouette', 'busts_in_silhouette', 'cactus', 'cake', |
||||
'calendar', 'calling', 'camel', 'camera', 'cancer', 'candy', 'capital_abcd', |
||||
'capricorn', 'car', 'card_index', 'carousel_horse', 'cat', 'cat2', 'cd', |
||||
'chart', 'chart_with_downwards_trend', 'chart_with_upwards_trend', |
||||
'checkered_flag', 'cherries', 'cherry_blossom', 'chestnut', 'chicken', |
||||
'children_crossing', 'chocolate_bar', 'christmas_tree', 'church', 'cinema', |
||||
'circus_tent', 'city_sunrise', 'city_sunset', 'cl', 'clap', 'clapper', |
||||
'clipboard', 'clock1', 'clock10', 'clock1030', 'clock11', 'clock1130', |
||||
'clock12', 'clock1230', 'clock130', 'clock2', 'clock230', 'clock3', 'clock330', |
||||
'clock4', 'clock430', 'clock5', 'clock530', 'clock6', 'clock630', 'clock7', |
||||
'clock730', 'clock8', 'clock830', 'clock9', 'clock930', 'closed_book', |
||||
'closed_lock_with_key', 'closed_umbrella', 'cloud', 'clubs', 'cn', 'cocktail', |
||||
'coffee', 'cold_sweat', 'collision', 'computer', 'confetti_ball', 'confounded', |
||||
'confused', 'congratulations', 'construction', 'construction_worker', |
||||
'convenience_store', 'cookie', 'cool', 'cop', 'copyright', 'corn', 'couple', |
||||
'couple_with_heart', 'couplekiss', 'cow', 'cow2', 'credit_card', 'crocodile', |
||||
'crossed_flags', 'crown', 'cry', 'crying_cat_face', 'crystal_ball', 'cupid', |
||||
'curly_loop', 'currency_exchange', 'curry', 'custard', 'customs', 'cyclone', |
||||
'dancer', 'dancers', 'dango', 'dart', 'dash', 'date', 'de', 'deciduous_tree', |
||||
'department_store', 'diamond_shape_with_a_dot_inside', 'diamonds', |
||||
'disappointed', 'disappointed_relieved', 'dizzy', 'dizzy_face', 'do_not_litter', |
||||
'dog', 'dog2', 'dollar', 'dolls', 'dolphin', 'donut', 'door', 'doughnut', |
||||
'dragon', 'dragon_face', 'dress', 'dromedary_camel', 'droplet', 'dvd', 'e-mail', |
||||
'ear', 'ear_of_rice', 'earth_africa', 'earth_americas', 'earth_asia', 'egg', |
||||
'eggplant', 'eight', 'eight_pointed_black_star', 'eight_spoked_asterisk', |
||||
'electric_plug', 'elephant', 'email', 'end', 'envelope', 'es', 'euro', |
||||
'european_castle', 'european_post_office', 'evergreen_tree', 'exclamation', |
||||
'expressionless', 'eyeglasses', 'eyes', 'facepunch', 'factory', 'fallen_leaf', |
||||
'family', 'fast_forward', 'fax', 'fearful', 'feelsgood', 'feet', 'ferris_wheel', |
||||
'file_folder', 'finnadie', 'fire', 'fire_engine', 'fireworks', |
||||
'first_quarter_moon', 'first_quarter_moon_with_face', 'fish', 'fish_cake', |
||||
'fishing_pole_and_fish', 'fist', 'five', 'flags', 'flashlight', 'floppy_disk', |
||||
'flower_playing_cards', 'flushed', 'foggy', 'football', 'fork_and_knife', |
||||
'fountain', 'four', 'four_leaf_clover', 'fr', 'free', 'fried_shrimp', 'fries', |
||||
'frog', 'frowning', 'fu', 'fuelpump', 'full_moon', 'full_moon_with_face', |
||||
'game_die', 'gb', 'gem', 'gemini', 'ghost', 'gift', 'gift_heart', 'girl', |
||||
'globe_with_meridians', 'goat', 'goberserk', 'godmode', 'golf', 'grapes', |
||||
'green_apple', 'green_book', 'green_heart', 'grey_exclamation', 'grey_question', |
||||
'grimacing', 'grin', 'grinning', 'guardsman', 'guitar', 'gun', 'haircut', |
||||
'hamburger', 'hammer', 'hamster', 'hand', 'handbag', 'hankey', 'hash', |
||||
'hatched_chick', 'hatching_chick', 'headphones', 'hear_no_evil', 'heart', |
||||
'heart_decoration', 'heart_eyes', 'heart_eyes_cat', 'heartbeat', 'heartpulse', |
||||
'hearts', 'heavy_check_mark', 'heavy_division_sign', 'heavy_dollar_sign', |
||||
'heavy_exclamation_mark', 'heavy_minus_sign', 'heavy_multiplication_x', |
||||
'heavy_plus_sign', 'helicopter', 'herb', 'hibiscus', 'high_brightness', |
||||
'high_heel', 'hocho', 'honey_pot', 'honeybee', 'horse', 'horse_racing', |
||||
'hospital', 'hotel', 'hotsprings', 'hourglass', 'hourglass_flowing_sand', |
||||
'house', 'house_with_garden', 'hurtrealbad', 'hushed', 'ice_cream', 'icecream', |
||||
'id', 'ideograph_advantage', 'imp', 'inbox_tray', 'incoming_envelope', |
||||
'information_desk_person', 'information_source', 'innocent', 'interrobang', |
||||
'iphone', 'it', 'izakaya_lantern', 'jack_o_lantern', 'japan', 'japanese_castle', |
||||
'japanese_goblin', 'japanese_ogre', 'jeans', 'joy', 'joy_cat', 'jp', 'key', |
||||
'keycap_ten', 'kimono', 'kiss', 'kissing', 'kissing_cat', 'kissing_closed_eyes', |
||||
'kissing_face', 'kissing_heart', 'kissing_smiling_eyes', 'koala', 'koko', 'kr', |
||||
'large_blue_circle', 'large_blue_diamond', 'large_orange_diamond', |
||||
'last_quarter_moon', 'last_quarter_moon_with_face', 'laughing', 'leaves', |
||||
'ledger', 'left_luggage', 'left_right_arrow', 'leftwards_arrow_with_hook', |
||||
'lemon', 'leo', 'leopard', 'libra', 'light_rail', 'link', 'lips', 'lipstick', |
||||
'lock', 'lock_with_ink_pen', 'lollipop', 'loop', 'loudspeaker', 'love_hotel', |
||||
'love_letter', 'low_brightness', 'm', 'mag', 'mag_right', 'mahjong', 'mailbox', |
||||
'mailbox_closed', 'mailbox_with_mail', 'mailbox_with_no_mail', 'man', |
||||
'man_with_gua_pi_mao', 'man_with_turban', 'mans_shoe', 'maple_leaf', 'mask', |
||||
'massage', 'meat_on_bone', 'mega', 'melon', 'memo', 'mens', 'metal', 'metro', |
||||
'microphone', 'microscope', 'milky_way', 'minibus', 'minidisc', |
||||
'mobile_phone_off', 'money_with_wings', 'moneybag', 'monkey', 'monkey_face', |
||||
'monorail', 'moon', 'mortar_board', 'mount_fuji', 'mountain_bicyclist', |
||||
'mountain_cableway', 'mountain_railway', 'mouse', 'mouse2', 'movie_camera', |
||||
'moyai', 'muscle', 'mushroom', 'musical_keyboard', 'musical_note', |
||||
'musical_score', 'mute', 'nail_care', 'name_badge', 'neckbeard', 'necktie', |
||||
'negative_squared_cross_mark', 'neutral_face', 'new', 'new_moon', |
||||
'new_moon_with_face', 'newspaper', 'ng', 'nine', 'no_bell', 'no_bicycles', |
||||
'no_entry', 'no_entry_sign', 'no_good', 'no_mobile_phones', 'no_mouth', |
||||
'no_pedestrians', 'no_smoking', 'non-potable_water', 'nose', 'notebook', |
||||
'notebook_with_decorative_cover', 'notes', 'nut_and_bolt', 'o', 'o2', 'ocean', |
||||
'octocat', 'octopus', 'oden', 'office', 'ok', 'ok_hand', 'ok_woman', |
||||
'older_man', 'older_woman', 'on', 'oncoming_automobile', 'oncoming_bus', |
||||
'oncoming_police_car', 'oncoming_taxi', 'one', 'open_file_folder', 'open_hands', |
||||
'open_mouth', 'ophiuchus', 'orange_book', 'outbox_tray', 'ox', 'page_facing_up', |
||||
'page_with_curl', 'pager', 'palm_tree', 'panda_face', 'paperclip', 'parking', |
||||
'part_alternation_mark', 'partly_sunny', 'passport_control', 'paw_prints', |
||||
'peach', 'pear', 'pencil', 'pencil2', 'penguin', 'pensive', 'performing_arts', |
||||
'persevere', 'person_frowning', 'person_with_blond_hair', |
||||
'person_with_pouting_face', 'phone', 'pig', 'pig2', 'pig_nose', 'pill', |
||||
'pineapple', 'pisces', 'pizza', 'plus1', 'point_down', 'point_left', |
||||
'point_right', 'point_up', 'point_up_2', 'police_car', 'poodle', 'poop', |
||||
'post_office', 'postal_horn', 'postbox', 'potable_water', 'pouch', |
||||
'poultry_leg', 'pound', 'pouting_cat', 'pray', 'princess', 'punch', |
||||
'purple_heart', 'purse', 'pushpin', 'put_litter_in_its_place', 'question', |
||||
'rabbit', 'rabbit2', 'racehorse', 'radio', 'radio_button', 'rage', 'rage1', |
||||
'rage2', 'rage3', 'rage4', 'railway_car', 'rainbow', 'raised_hand', |
||||
'raised_hands', 'raising_hand', 'ram', 'ramen', 'rat', 'recycle', 'red_car', |
||||
'red_circle', 'registered', 'relaxed', 'relieved', 'repeat', 'repeat_one', |
||||
'restroom', 'revolving_hearts', 'rewind', 'ribbon', 'rice', 'rice_ball', |
||||
'rice_cracker', 'rice_scene', 'ring', 'rocket', 'roller_coaster', 'rooster', |
||||
'rose', 'rotating_light', 'round_pushpin', 'rowboat', 'ru', 'rugby_football', |
||||
'runner', 'running', 'running_shirt_with_sash', 'sa', 'sagittarius', 'sailboat', |
||||
'sake', 'sandal', 'santa', 'satellite', 'satisfied', 'saxophone', 'school', |
||||
'school_satchel', 'scissors', 'scorpius', 'scream', 'scream_cat', 'scroll', |
||||
'seat', 'secret', 'see_no_evil', 'seedling', 'seven', 'shaved_ice', 'sheep', |
||||
'shell', 'ship', 'shipit', 'shirt', 'shit', 'shoe', 'shower', 'signal_strength', |
||||
'six', 'six_pointed_star', 'ski', 'skull', 'sleeping', 'sleepy', 'slot_machine', |
||||
'small_blue_diamond', 'small_orange_diamond', 'small_red_triangle', |
||||
'small_red_triangle_down', 'smile', 'smile_cat', 'smiley', 'smiley_cat', |
||||
'smiling_imp', 'smirk', 'smirk_cat', 'smoking', 'snail', 'snake', 'snowboarder', |
||||
'snowflake', 'snowman', 'sob', 'soccer', 'soon', 'sos', 'sound', |
||||
'space_invader', 'spades', 'spaghetti', 'sparkler', 'sparkles', |
||||
'sparkling_heart', 'speak_no_evil', 'speaker', 'speech_balloon', 'speedboat', |
||||
'squirrel', 'star', 'star2', 'stars', 'station', 'statue_of_liberty', |
||||
'steam_locomotive', 'stew', 'straight_ruler', 'strawberry', 'stuck_out_tongue', |
||||
'stuck_out_tongue_closed_eyes', 'stuck_out_tongue_winking_eye', 'sun_with_face', |
||||
'sunflower', 'sunglasses', 'sunny', 'sunrise', 'sunrise_over_mountains', |
||||
'surfer', 'sushi', 'suspect', 'suspension_railway', 'sweat', 'sweat_drops', |
||||
'sweat_smile', 'sweet_potato', 'swimmer', 'symbols', 'syringe', 'tada', |
||||
'tanabata_tree', 'tangerine', 'taurus', 'taxi', 'tea', 'telephone', |
||||
'telephone_receiver', 'telescope', 'tennis', 'tent', 'thought_balloon', 'three', |
||||
'thumbsdown', 'thumbsup', 'ticket', 'tiger', 'tiger2', 'tired_face', 'tm', |
||||
'toilet', 'tokyo_tower', 'tomato', 'tongue', 'top', 'tophat', 'tractor', |
||||
'traffic_light', 'train', 'train2', 'tram', 'triangular_flag_on_post', |
||||
'triangular_ruler', 'trident', 'triumph', 'trolleybus', 'trollface', 'trophy', |
||||
'tropical_drink', 'tropical_fish', 'truck', 'trumpet', 'tshirt', 'tulip', |
||||
'turtle', 'tv', 'twisted_rightwards_arrows', 'two', 'two_hearts', |
||||
'two_men_holding_hands', 'two_women_holding_hands', 'u5272', 'u5408', 'u55b6', |
||||
'u6307', 'u6708', 'u6709', 'u6e80', 'u7121', 'u7533', 'u7981', 'u7a7a', 'uk', |
||||
'umbrella', 'unamused', 'underage', 'unlock', 'up', 'us', 'v', |
||||
'vertical_traffic_light', 'vhs', 'vibration_mode', 'video_camera', 'video_game', |
||||
'violin', 'virgo', 'volcano', 'vs', 'walking', 'waning_crescent_moon', |
||||
'waning_gibbous_moon', 'warning', 'watch', 'water_buffalo', 'watermelon', |
||||
'wave', 'wavy_dash', 'waxing_crescent_moon', 'waxing_gibbous_moon', 'wc', |
||||
'weary', 'wedding', 'whale', 'whale2', 'wheelchair', 'white_check_mark', |
||||
'white_circle', 'white_flower', 'white_square', 'white_square_button', |
||||
'wind_chime', 'wine_glass', 'wink', 'wolf', 'woman', 'womans_clothes', |
||||
'womans_hat', 'womens', 'worried', 'wrench', 'x', 'yellow_heart', 'yen', 'yum', |
||||
'zap', 'zero', 'zzz']; |
@ -0,0 +1,133 @@ |
||||
// Filtered view manager
|
||||
// We define local filter objects for each different type of field (SetFilter,
|
||||
// RangeFilter, dateFilter, etc.). We then define a global `Filter` object whose
|
||||
// goal is to filter complete documents by using the local filters for each
|
||||
// fields.
|
||||
|
||||
// Use a "set" filter for a field that is a set of documents uniquely
|
||||
// identified. For instance `{ labels: ['labelA', 'labelC', 'labelD'] }`.
|
||||
var SetFilter = function() { |
||||
this._dep = new Tracker.Dependency(); |
||||
this._selectedElements = []; |
||||
}; |
||||
|
||||
_.extend(SetFilter.prototype, { |
||||
isSelected: function(val) { |
||||
this._dep.depend(); |
||||
return this._selectedElements.indexOf(val) > -1; |
||||
}, |
||||
|
||||
add: function(val) { |
||||
if (this.indexOfVal(val) === -1) { |
||||
this._selectedElements.push(val); |
||||
this._dep.changed(); |
||||
} |
||||
}, |
||||
|
||||
remove: function(val) { |
||||
var indexOfVal = this._indexOfVal(val); |
||||
if (this.indexOfVal(val) !== -1) { |
||||
this._selectedElements.splice(indexOfVal, 1); |
||||
this._dep.changed(); |
||||
} |
||||
}, |
||||
|
||||
toogle: function(val) { |
||||
var indexOfVal = this._indexOfVal(val); |
||||
if (indexOfVal === -1) { |
||||
this._selectedElements.push(val); |
||||
} else { |
||||
this._selectedElements.splice(indexOfVal, 1); |
||||
} |
||||
|
||||
this._dep.changed(); |
||||
}, |
||||
|
||||
reset: function() { |
||||
this._selectedElements = []; |
||||
this._dep.changed(); |
||||
}, |
||||
|
||||
_indexOfVal: function(val) { |
||||
return this._selectedElements.indexOf(val); |
||||
}, |
||||
|
||||
_isActive: function() { |
||||
this._dep.depend(); |
||||
return this._selectedElements.length !== 0; |
||||
}, |
||||
|
||||
_getMongoSelector: function() { |
||||
this._dep.depend(); |
||||
return { $in: this._selectedElements }; |
||||
} |
||||
}); |
||||
|
||||
// The global Filter object.
|
||||
// XXX It would be possible to re-write this object more elegantly, and removing
|
||||
// the need to provide a list of `_fields`. We also should move methods into the
|
||||
// object prototype.
|
||||
Filter = { |
||||
// XXX I would like to rename this field into `labels` to be consistent with
|
||||
// the rest of the schema, but we need to set some migrations architecture
|
||||
// before changing the schema.
|
||||
labelIds: new SetFilter(), |
||||
members: new SetFilter(), |
||||
|
||||
_fields: ['labelIds', 'members'], |
||||
|
||||
// We don't filter cards that have been added after the last filter change. To
|
||||
// implement this we keep the id of these cards in this `_exceptions` fields
|
||||
// and use a `$or` condition in the mongo selector we return.
|
||||
_exceptions: [], |
||||
_exceptionsDep: new Tracker.Dependency(), |
||||
|
||||
isActive: function() { |
||||
var self = this; |
||||
return _.any(self._fields, function(fieldName) { |
||||
return self[fieldName]._isActive(); |
||||
}); |
||||
}, |
||||
|
||||
getMongoSelector: function() { |
||||
var self = this; |
||||
|
||||
if (! self.isActive()) |
||||
return {}; |
||||
|
||||
var filterSelector = {}; |
||||
_.forEach(self._fields, function(fieldName) { |
||||
var filter = self[fieldName]; |
||||
if (filter._isActive()) |
||||
filterSelector[fieldName] = filter._getMongoSelector(); |
||||
}); |
||||
|
||||
var exceptionsSelector = {_id: {$in: this._exceptions}}; |
||||
this._exceptionsDep.depend(); |
||||
|
||||
return {$or: [filterSelector, exceptionsSelector]}; |
||||
}, |
||||
|
||||
reset: function() { |
||||
var self = this; |
||||
_.forEach(self._fields, function(fieldName) { |
||||
var filter = self[fieldName]; |
||||
filter.reset(); |
||||
}); |
||||
self.resetExceptions(); |
||||
}, |
||||
|
||||
addException: function(_id) { |
||||
if (this.isActive()) { |
||||
this._exceptions.push(_id); |
||||
this._exceptionsDep.changed(); |
||||
} |
||||
}, |
||||
|
||||
resetExceptions: function() { |
||||
this._exceptions = []; |
||||
this._exceptionsDep.changed(); |
||||
} |
||||
}; |
||||
|
||||
Blaze.registerHelper('Filter', Filter); |
@ -0,0 +1,22 @@ |
||||
// We save the user language preference in the user profile, and use that to set
|
||||
// the language reactively. If the user is not connected we use the language
|
||||
// information provided by the browser, and default to english.
|
||||
|
||||
Tracker.autorun(function() { |
||||
var language; |
||||
var currentUser = Meteor.user(); |
||||
if (currentUser) { |
||||
language = currentUser.profile && currentUser.profile.language; |
||||
} else { |
||||
language = navigator.language || navigator.userLanguage; |
||||
} |
||||
|
||||
if (language) { |
||||
|
||||
TAPi18n.setLanguage(language); |
||||
|
||||
// XXX
|
||||
var shortLanguage = language.split('-')[0]; |
||||
T9n.setLanguage(shortLanguage); |
||||
} |
||||
}); |
@ -0,0 +1,55 @@ |
||||
// XXX Pressing `?` should display a list of all shortcuts available.
|
||||
//
|
||||
// XXX There is no reason to define these shortcuts globally, they should be
|
||||
// attached to a template (most of them will go in the `board` template).
|
||||
|
||||
// Pressing `Escape` should close the last opened “element” and only the last
|
||||
// one -- curently we handle popups and the card detailed view of the sidebar.
|
||||
Mousetrap.bind('esc', function() { |
||||
if (currentlyOpenedForm.get() !== null) { |
||||
currentlyOpenedForm.get().close(); |
||||
|
||||
} else if (Popup.isOpen()) { |
||||
Popup.back(); |
||||
|
||||
// XXX We should have a higher level API
|
||||
} else if (Session.get('currentCard')) { |
||||
Utils.goBoardId(Session.get('currentBoard')); |
||||
} |
||||
}); |
||||
|
||||
Mousetrap.bind('w', function() { |
||||
if (! Session.get('currentCard')) { |
||||
Sidebar.toogle(); |
||||
} else { |
||||
Utils.goBoardId(Session.get('currentBoard')); |
||||
Sidebar.hide(); |
||||
} |
||||
}); |
||||
|
||||
Mousetrap.bind('q', function() { |
||||
var currentBoardId = Session.get('currentBoard'); |
||||
var currentUserId = Meteor.userId(); |
||||
if (currentBoardId && currentUserId) { |
||||
Filter.members.toogle(currentUserId); |
||||
} |
||||
}); |
||||
|
||||
Mousetrap.bind('x', function() { |
||||
if (Filter.isActive()) { |
||||
Filter.reset(); |
||||
} |
||||
}); |
||||
|
||||
Mousetrap.bind(['down', 'up'], function(evt, key) { |
||||
if (! Session.get('currentCard')) { |
||||
return; |
||||
} |
||||
|
||||
var nextFunc = (key === 'down' ? 'next' : 'prev'); |
||||
var nextCard = $('.js-minicard.is-selected')[nextFunc]('.js-minicard').get(0); |
||||
if (nextCard) { |
||||
var nextCardId = Blaze.getData(nextCard)._id; |
||||
Utils.goCardId(nextCardId); |
||||
} |
||||
}); |
@ -0,0 +1 @@ |
||||
Mixins = {}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue