mirror of
https://github.com/wekan/wekan.git
synced 2026-03-04 04:40:16 +01: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
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 = {};
|
||||
200
client/lib/popup.js
Normal file
200
client/lib/popup.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// A simple tracker dependency that we invalidate every time the window is
|
||||
// resized. This is used to reactively re-calculate the popup position in case
|
||||
// of a window resize.
|
||||
var windowResizeDep = new Tracker.Dependency();
|
||||
$(window).on('resize', function() { windowResizeDep.changed(); });
|
||||
|
||||
Popup = {
|
||||
/// This function returns a callback that can be used in an event map:
|
||||
///
|
||||
/// Template.tplName.events({
|
||||
/// 'click .elementClass': Popup.open("popupName")
|
||||
/// });
|
||||
///
|
||||
/// The popup inherit the data context of its parent.
|
||||
open: function(name) {
|
||||
var self = this;
|
||||
var popupName = name + 'Popup';
|
||||
|
||||
return function(evt) {
|
||||
// If a popup is already openened, clicking again on the opener element
|
||||
// should close it -- and interupt the current `open` function.
|
||||
if (self.isOpen() &&
|
||||
self._getTopStack().openerElement === evt.currentTarget) {
|
||||
return self.close();
|
||||
}
|
||||
|
||||
// We determine the `openerElement` (the DOM element that is being clicked
|
||||
// and the one we take in reference to position the popup) from the event
|
||||
// if the popup has no parent, or from the parent `openerElement` if it
|
||||
// has one. This allows us to position a sub-popup exactly at the same
|
||||
// position than its parent.
|
||||
var openerElement;
|
||||
if (self._hasPopupParent()) {
|
||||
openerElement = self._getTopStack().openerElement;
|
||||
} else {
|
||||
self._stack = [];
|
||||
openerElement = evt.currentTarget;
|
||||
}
|
||||
|
||||
// We modify the event to prevent the popup being closed when the event
|
||||
// bubble up to the document element.
|
||||
evt.originalEvent.clickInPopup = true;
|
||||
evt.preventDefault();
|
||||
|
||||
// We push our popup data to the stack. The top of the stack is always
|
||||
// used as the data source for our current popup.
|
||||
self._stack.push({
|
||||
__isPopup: true,
|
||||
popupName: popupName,
|
||||
hasPopupParent: self._hasPopupParent(),
|
||||
title: self._getTitle(popupName),
|
||||
openerElement: openerElement,
|
||||
offset: self._getOffset(openerElement),
|
||||
dataContext: this.currentData && this.currentData() || this
|
||||
});
|
||||
|
||||
// If there are no popup currently opened we use the Blaze API to render
|
||||
// one into the DOM. We use a reactive function as the data parameter that
|
||||
// just return the top element on the stack and depends on our internal
|
||||
// dependency that is being invalidated every time the top element of the
|
||||
// stack has changed and we want to update the popup.
|
||||
//
|
||||
// Otherwise if there is already a popup open we just need to invalidate
|
||||
// our internal dependency, and since we just changed the top element of
|
||||
// our internal stack, the popup will be updated with the new data.
|
||||
if (! self.isOpen()) {
|
||||
self.current = Blaze.renderWithData(self.template, function() {
|
||||
self._dep.depend();
|
||||
return self._stack[self._stack.length - 1];
|
||||
}, document.body);
|
||||
|
||||
} else {
|
||||
self._dep.changed();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/// This function returns a callback that can be used in an event map:
|
||||
///
|
||||
/// Template.tplName.events({
|
||||
/// 'click .elementClass': Popup.afterConfirm("popupName", function() {
|
||||
/// // What to do after the user has confirmed the action
|
||||
/// })
|
||||
/// });
|
||||
afterConfirm: function(name, action) {
|
||||
var self = this;
|
||||
|
||||
return function(evt, tpl) {
|
||||
var context = this;
|
||||
context.__afterConfirmAction = action;
|
||||
self.open(name).call(context, evt, tpl);
|
||||
};
|
||||
},
|
||||
|
||||
/// The public reactive state of the popup.
|
||||
isOpen: function() {
|
||||
this._dep.changed();
|
||||
return !! this.current;
|
||||
},
|
||||
|
||||
/// In case the popup was opened from a parent popup we can get back to it
|
||||
/// with this `Popup.back()` function. You can go back several steps at once
|
||||
/// by providing a number to this function, e.g. `Popup.back(2)`. In this case
|
||||
/// intermediate popup won't even be rendered on the DOM. If the number of
|
||||
/// steps back is greater than the popup stack size, the popup will be closed.
|
||||
back: function(n) {
|
||||
n = n || 1;
|
||||
var self = this;
|
||||
if (self._stack.length > n) {
|
||||
_.times(n, function() { self._stack.pop(); });
|
||||
self._dep.changed();
|
||||
} else {
|
||||
self.close();
|
||||
}
|
||||
},
|
||||
|
||||
/// Close the current opened popup.
|
||||
close: function() {
|
||||
if (this.isOpen()) {
|
||||
Blaze.remove(this.current);
|
||||
this.current = null;
|
||||
this._stack = [];
|
||||
}
|
||||
},
|
||||
|
||||
// The template we use for every popup
|
||||
template: Template.popup,
|
||||
|
||||
// We only want to display one popup at a time and we keep the view object in
|
||||
// this `Popup._current` variable. If there is no popup currently opened the
|
||||
// value is `null`.
|
||||
_current: null,
|
||||
|
||||
// It's possible to open a sub-popup B from a popup A. In that case we keep
|
||||
// the data of popup A so we can return back to it. Every time we open a new
|
||||
// popup the stack grows, every time we go back the stack decrease, and if we
|
||||
// close the popup the stack is reseted to the empty stack [].
|
||||
_stack: [],
|
||||
|
||||
// We invalidate this internal dependency every time the top of the stack has
|
||||
// changed and we want to render a popup with the new top-stack data.
|
||||
_dep: new Tracker.Dependency(),
|
||||
|
||||
// An utility fonction that returns the top element of the internal stack
|
||||
_getTopStack: function() {
|
||||
return this._stack[this._stack.length - 1];
|
||||
},
|
||||
|
||||
// We use the blaze API to determine if the current popup has been opened from
|
||||
// a parent popup. The number we give to the `Template.parentData` has been
|
||||
// determined experimentally and is susceptible to change if you modify the
|
||||
// `Popup.template`
|
||||
_hasPopupParent: function() {
|
||||
var tryParentData = Template.parentData(3);
|
||||
return !! (tryParentData && tryParentData.__isPopup);
|
||||
},
|
||||
|
||||
// We automatically calculate the popup offset from the reference element
|
||||
// position and dimensions. We also reactively use the window dimensions to
|
||||
// ensure that the popup is always visible on the screen.
|
||||
_getOffset: function(element) {
|
||||
var $element = $(element);
|
||||
return function() {
|
||||
windowResizeDep.depend();
|
||||
var offset = $element.offset();
|
||||
var popupWidth = 300 + 15;
|
||||
return {
|
||||
left: Math.min(offset.left, $(window).width() - popupWidth),
|
||||
top: offset.top + $element.outerHeight()
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
// We get the title from the translation files. Instead of returning the
|
||||
// result, we return a function that compute the result and since `TAPi18n.__`
|
||||
// is a reactive data source, the title will be changed reactively.
|
||||
_getTitle: function(popupName) {
|
||||
return function() {
|
||||
var translationKey = popupName + '-title';
|
||||
|
||||
// XXX There is no public API to check if there is an available
|
||||
// translation for a given key. So we try to translate the key and if the
|
||||
// translation output equals the key input we deduce that no translation
|
||||
// was available and returns `false`. There is a (small) risk a false
|
||||
// positives.
|
||||
var title = TAPi18n.__(translationKey);
|
||||
return title !== translationKey ? title : false;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// We automatically close a potential opened popup on any left click on the
|
||||
// document. To avoid closing it unexpectedly we modify the bubbled event in
|
||||
// case the click event happen in the popup or in a button that open a popup.
|
||||
$(document).on('click', function(evt) {
|
||||
if (evt.which === 1 && ! (evt.originalEvent &&
|
||||
evt.originalEvent.clickInPopup)) {
|
||||
Popup.close();
|
||||
}
|
||||
});
|
||||
96
client/lib/utils.js
Normal file
96
client/lib/utils.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
Utils = {
|
||||
error: function(err) {
|
||||
Session.set('error', (err && err.message || false));
|
||||
},
|
||||
|
||||
// scroll
|
||||
Scroll: function(selector) {
|
||||
var $el = $(selector);
|
||||
return {
|
||||
top: function(px, add) {
|
||||
var t = $el.scrollTop();
|
||||
$el.animate({ scrollTop: (add ? (t + px) : px) });
|
||||
},
|
||||
left: function(px, add) {
|
||||
var l = $el.scrollLeft();
|
||||
$el.animate({ scrollLeft: (add ? (l + px) : px) });
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
Warning: {
|
||||
get: function() {
|
||||
return Session.get('warning');
|
||||
},
|
||||
open: function(desc) {
|
||||
Session.set('warning', { desc: desc });
|
||||
},
|
||||
close: function() {
|
||||
Session.set('warning', false);
|
||||
}
|
||||
},
|
||||
|
||||
// XXX We should remove these two methods
|
||||
goBoardId: function(_id) {
|
||||
var board = Boards.findOne(_id);
|
||||
return board && Router.go('Board', {
|
||||
_id: board._id,
|
||||
slug: board.slug
|
||||
});
|
||||
},
|
||||
|
||||
goCardId: function(_id) {
|
||||
var card = Cards.findOne(_id);
|
||||
var board = Boards.findOne(card.boardId);
|
||||
return board && Router.go('Card', {
|
||||
cardId: card._id,
|
||||
boardId: board._id,
|
||||
slug: board.slug
|
||||
});
|
||||
},
|
||||
|
||||
liveEvent: function(events, callback) {
|
||||
$(document).on(events, function() {
|
||||
callback($(this));
|
||||
});
|
||||
},
|
||||
|
||||
capitalize: function(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
},
|
||||
|
||||
getLabelIndex: function(boardId, labelId) {
|
||||
var board = Boards.findOne(boardId);
|
||||
var labels = {};
|
||||
_.each(board.labels, function(a, b) {
|
||||
labels[a._id] = b;
|
||||
});
|
||||
return {
|
||||
index: labels[labelId],
|
||||
key: function(key) {
|
||||
return 'labels.' + labels[labelId] + '.' + key;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// Determine the new sort index
|
||||
getSortIndex: function(prevCardDomElement, nextCardDomElement) {
|
||||
// If we drop the card to an empty column
|
||||
if (! prevCardDomElement && ! nextCardDomElement) {
|
||||
return 0;
|
||||
// If we drop the card in the first position
|
||||
} else if (! prevCardDomElement) {
|
||||
return Blaze.getData(nextCardDomElement).sort - 1;
|
||||
// If we drop the card in the last position
|
||||
} else if (! nextCardDomElement) {
|
||||
return Blaze.getData(prevCardDomElement).sort + 1;
|
||||
}
|
||||
// In the general case take the average of the previous and next element
|
||||
// sort indexes.
|
||||
else {
|
||||
var prevSortIndex = Blaze.getData(prevCardDomElement).sort;
|
||||
var nextSortIndex = Blaze.getData(nextCardDomElement).sort;
|
||||
return (prevSortIndex + nextSortIndex) / 2;
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue