mirror of
https://github.com/wekan/wekan.git
synced 2025-09-22 01:50:48 +02:00
Renaissance
_,,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 8888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888888888
This commit is contained in:
commit
2dbea30842
128 changed files with 10521 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
.meteor-spk
|
||||||
|
.tx/
|
||||||
|
*.sublime-workspace
|
77
.jscsrc
Normal file
77
.jscsrc
Normal file
|
@ -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
|
||||||
|
}
|
82
.jshintrc
Normal file
82
.jshintrc
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
8
.meteor/.finished-upgraders
Normal file
8
.meteor/.finished-upgraders
Normal file
|
@ -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
|
1
.meteor/.gitignore
vendored
Normal file
1
.meteor/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
local
|
7
.meteor/.id
Normal file
7
.meteor/.id
Normal file
|
@ -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
|
1
.meteor/cordova-plugins
Normal file
1
.meteor/cordova-plugins
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
53
.meteor/packages
Normal file
53
.meteor/packages
Normal file
|
@ -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
|
2
.meteor/platforms
Normal file
2
.meteor/platforms
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
server
|
||||||
|
browser
|
1
.meteor/release
Normal file
1
.meteor/release
Normal file
|
@ -0,0 +1 @@
|
||||||
|
METEOR@1.1.0.2
|
120
.meteor/versions
Normal file
120
.meteor/versions
Normal file
|
@ -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
|
7
.travis.yml
Normal file
7
.travis.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "0.10"
|
||||||
|
before_install:
|
||||||
|
- "curl -L http://git.io/ejPSng | /bin/sh"
|
||||||
|
services:
|
||||||
|
- mongodb
|
57
Contributing.md
Normal file
57
Contributing.md
Normal file
|
@ -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
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
|
@ -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
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -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.
|
25
README.md
Normal file
25
README.md
Normal file
|
@ -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
|
8
client/components/activities/activities.jade
Normal file
8
client/components/activities/activities.jade
Normal file
|
@ -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
|
77
client/components/activities/activities.js
Normal file
77
client/components/activities/activities.js
Normal file
|
@ -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
client/components/activities/comments.jade
Normal file
0
client/components/activities/comments.jade
Normal file
0
client/components/activities/comments.js
Normal file
0
client/components/activities/comments.js
Normal file
30
client/components/activities/events.js
Normal file
30
client/components/activities/events.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
154
client/components/activities/templates.html
Normal file
154
client/components/activities/templates.html
Normal file
|
@ -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>
|
33
client/components/boards/body.jade
Normal file
33
client/components/boards/body.jade
Normal file
|
@ -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'}}
|
70
client/components/boards/body.js
Normal file
70
client/components/boards/body.js
Normal file
|
@ -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');
|
54
client/components/boards/body.styl
Normal file
54
client/components/boards/body.styl
Normal file
|
@ -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
|
34
client/components/boards/colors.styl
Normal file
34
client/components/boards/colors.styl
Normal file
|
@ -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)
|
96
client/components/boards/events.js
Normal file
96
client/components/boards/events.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
87
client/components/boards/header.jade
Normal file
87
client/components/boards/header.jade
Normal file
|
@ -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'}}")
|
7
client/components/boards/header.js
Normal file
7
client/components/boards/header.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Template.headerBoard.helpers({
|
||||||
|
isStarred: function() {
|
||||||
|
var boardId = Session.get('currentBoard');
|
||||||
|
var user = Meteor.user();
|
||||||
|
return boardId && user && user.hasStarred(boardId);
|
||||||
|
}
|
||||||
|
});
|
137
client/components/boards/header.styl
Normal file
137
client/components/boards/header.styl
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
client/components/boards/helpers.js
Normal file
45
client/components/boards/helpers.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
14
client/components/boards/list.jade
Normal file
14
client/components/boards/list.jade
Normal file
|
@ -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'}}
|
85
client/components/boards/list.styl
Normal file
85
client/components/boards/list.styl
Normal file
|
@ -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
|
34
client/components/boards/router.js
Normal file
34
client/components/boards/router.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
47
client/components/cards/details.jade
Normal file
47
client/components/cards/details.jade
Normal file
|
@ -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
|
103
client/components/cards/details.js
Normal file
103
client/components/cards/details.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
161
client/components/cards/details.styl
Normal file
161
client/components/cards/details.styl
Normal file
|
@ -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%
|
285
client/components/cards/events.js
Normal file
285
client/components/cards/events.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
48
client/components/cards/helpers.js
Normal file
48
client/components/cards/helpers.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
183
client/components/cards/labels.styl
Normal file
183
client/components/cards/labels.styl
Normal file
|
@ -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
|
136
client/components/cards/minicard.styl
Normal file
136
client/components/cards/minicard.styl
Normal file
|
@ -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
|
12
client/components/cards/popups.jade
Normal file
12
client/components/cards/popups.jade
Normal file
|
@ -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
|
15
client/components/cards/router.js
Normal file
15
client/components/cards/router.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
336
client/components/cards/templates.html
Normal file
336
client/components/cards/templates.html
Normal file
|
@ -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>
|
22
client/components/forms/cachedValue.js
Normal file
22
client/components/forms/cachedValue.js
Normal file
|
@ -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('');
|
||||||
|
}
|
||||||
|
});
|
636
client/components/forms/forms.styl
Normal file
636
client/components/forms/forms.styl
Normal file
|
@ -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
|
6
client/components/forms/inlinedform.jade
Normal file
6
client/components/forms/inlinedform.jade
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
template(name='inlinedForm')
|
||||||
|
if isOpen.get
|
||||||
|
form(id=id class=classNames)
|
||||||
|
+Template.contentBlock
|
||||||
|
else
|
||||||
|
+Template.elseBlock
|
93
client/components/forms/inlinedform.js
Normal file
93
client/components/forms/inlinedform.js
Normal file
|
@ -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');
|
50
client/components/lists/body.jade
Normal file
50
client/components/lists/body.jade
Normal file
|
@ -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
|
73
client/components/lists/body.js
Normal file
73
client/components/lists/body.js
Normal file
|
@ -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');
|
16
client/components/lists/events.js
Normal file
16
client/components/lists/events.js
Normal file
|
@ -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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
13
client/components/lists/header.jade
Normal file
13
client/components/lists/header.jade
Normal file
|
@ -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
|
25
client/components/lists/header.js
Normal file
25
client/components/lists/header.js
Normal file
|
@ -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');
|
5
client/components/lists/main.jade
Normal file
5
client/components/lists/main.jade
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
template(name='list')
|
||||||
|
.list.js-list(id="js-list-{{_id}}")
|
||||||
|
.list-wrapper
|
||||||
|
+listHeader
|
||||||
|
+listBody
|
81
client/components/lists/main.js
Normal file
81
client/components/lists/main.js
Normal file
|
@ -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');
|
136
client/components/lists/main.styl
Normal file
136
client/components/lists/main.styl
Normal file
|
@ -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
|
28
client/components/lists/menu.jade
Normal file
28
client/components/lists/menu.jade
Normal file
|
@ -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'}}")
|
46
client/components/lists/menu.js
Normal file
46
client/components/lists/menu.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
8
client/components/main/events.js
Normal file
8
client/components/main/events.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
40
client/components/main/header.jade
Normal file
40
client/components/main/header.jade
Normal file
|
@ -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
|
10
client/components/main/header.js
Normal file
10
client/components/main/header.js
Normal file
|
@ -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')
|
||||||
|
});
|
266
client/components/main/header.styl
Normal file
266
client/components/main/header.styl
Normal file
|
@ -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;
|
||||||
|
// }
|
63
client/components/main/helpers.js
Normal file
63
client/components/main/helpers.js
Normal file
|
@ -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);
|
||||||
|
}));
|
17
client/components/main/layouts.jade
Normal file
17
client/components/main/layouts.jade
Normal file
|
@ -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
|
||||||
|
|
16
client/components/main/popup.js
Normal file
16
client/components/main/popup.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
585
client/components/main/popup.styl
Normal file
585
client/components/main/popup.styl
Normal file
|
@ -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%
|
13
client/components/main/popup.tpl.jade
Normal file
13
client/components/main/popup.tpl.jade
Normal file
|
@ -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)
|
40
client/components/main/rendered.js
Normal file
40
client/components/main/rendered.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
};
|
5
client/components/main/router.js
Normal file
5
client/components/main/router.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Router.route('/', {
|
||||||
|
name: 'Home',
|
||||||
|
redirectLoggedInUsers: true,
|
||||||
|
authenticated: true
|
||||||
|
});
|
45
client/components/main/spinner.styl
Normal file
45
client/components/main/spinner.styl
Normal file
|
@ -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) }
|
||||||
|
}
|
6
client/components/main/spinner.tpl.jade
Normal file
6
client/components/main/spinner.tpl.jade
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.sk-spinner.sk-spinner-wave(class=currentBoard.colorClass)
|
||||||
|
.sk-rect1
|
||||||
|
.sk-rect2
|
||||||
|
.sk-rect3
|
||||||
|
.sk-rect4
|
||||||
|
.sk-rect5
|
18
client/components/main/templates.html
Normal file
18
client/components/main/templates.html
Normal file
|
@ -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>
|
14
client/components/modal/events.js
Normal file
14
client/components/modal/events.js
Normal file
|
@ -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
client/components/modal/helpers.js
Normal file
0
client/components/modal/helpers.js
Normal file
5
client/components/modal/modal.tpl.jade
Normal file
5
client/components/modal/modal.tpl.jade
Normal file
|
@ -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)
|
93
client/components/sidebar/events.js
Normal file
93
client/components/sidebar/events.js
Normal file
|
@ -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);
|
||||||
|
// }
|
||||||
|
// });
|
51
client/components/sidebar/helpers.js
Normal file
51
client/components/sidebar/helpers.js
Normal file
|
@ -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;
|
||||||
|
// }
|
||||||
|
// });
|
37
client/components/sidebar/infiniteScrolling.js
Normal file
37
client/components/sidebar/infiniteScrolling.js
Normal file
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
});
|
21
client/components/sidebar/rendered.js
Normal file
21
client/components/sidebar/rendered.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
55
client/components/sidebar/sidebar.js
Normal file
55
client/components/sidebar/sidebar.js
Normal file
|
@ -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');
|
154
client/components/sidebar/sidebar.styl
Normal file
154
client/components/sidebar/sidebar.styl
Normal file
|
@ -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
|
307
client/components/sidebar/templates.html.old
Normal file
307
client/components/sidebar/templates.html.old
Normal file
|
@ -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>
|
103
client/components/sidebar/templates.jade
Normal file
103
client/components/sidebar/templates.jade
Normal file
|
@ -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'}}
|
7
client/components/users/avatar.jade
Normal file
7
client/components/users/avatar.jade
Normal file
|
@ -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)
|
59
client/components/users/events.js
Normal file
59
client/components/users/events.js
Normal file
|
@ -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')
|
||||||
|
});
|
50
client/components/users/form.styl
Normal file
50
client/components/users/form.styl
Normal file
|
@ -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
|
27
client/components/users/headerButtons.jade
Normal file
27
client/components/users/headerButtons.jade
Normal file
|
@ -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'}}
|
5
client/components/users/headerButtons.js
Normal file
5
client/components/users/headerButtons.js
Normal file
|
@ -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')
|
||||||
|
});
|
27
client/components/users/helpers.js
Normal file
27
client/components/users/helpers.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
107
client/components/users/member.styl
Normal file
107
client/components/users/member.styl
Normal file
|
@ -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
|
||||||
|
|
29
client/components/users/router.js
Normal file
29
client/components/users/router.js
Normal file
|
@ -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'
|
||||||
|
});
|
118
client/components/users/templates.html
Normal file
118
client/components/users/templates.html
Normal file
|
@ -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>
|
35
client/config/accounts.js
Normal file
35
client/config/accounts.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
]);
|
3
client/config/avatar.js
Normal file
3
client/config/avatar.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Avatar.options = {
|
||||||
|
fallbackType: 'initials'
|
||||||
|
};
|
28
client/config/router.js
Normal file
28
client/config/router.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
152
client/lib/emoji-values.js
Normal file
152
client/lib/emoji-values.js
Normal file
|
@ -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'];
|
133
client/lib/filter.js
Normal file
133
client/lib/filter.js
Normal file
|
@ -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);
|
22
client/lib/i18n.js
Normal file
22
client/lib/i18n.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
55
client/lib/keyboard.js
Normal file
55
client/lib/keyboard.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
1
client/lib/mixins.js
Normal file
1
client/lib/mixins.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Mixins = {};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue