summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--api/post.go14
-rw-r--r--api/user.go2
-rw-r--r--doc/developer/tests/test-emoticons.md930
-rw-r--r--doc/install/Production-Ubuntu.md2
-rw-r--r--doc/install/Release-Numbering.md8
-rw-r--r--model/incoming_webhook.go11
-rw-r--r--model/post.go46
-rw-r--r--model/user.go7
-rw-r--r--model/utils.go20
-rw-r--r--store/sql_post_store.go19
-rw-r--r--store/sql_store.go26
-rw-r--r--store/sql_user_store.go4
-rw-r--r--web/react/components/admin_console/user_item.jsx90
-rw-r--r--web/react/components/center_panel.jsx4
-rw-r--r--web/react/components/channel_invite_modal.jsx21
-rw-r--r--web/react/components/channel_members_modal.jsx35
-rw-r--r--web/react/components/create_post.jsx4
-rw-r--r--web/react/components/edit_channel_purpose_modal.jsx9
-rw-r--r--web/react/components/member_list.jsx2
-rw-r--r--web/react/components/member_list_team_item.jsx70
-rw-r--r--web/react/components/post_attachment.jsx295
-rw-r--r--web/react/components/post_attachment_list.jsx32
-rw-r--r--web/react/components/post_body.jsx4
-rw-r--r--web/react/components/post_body_additional_content.jsx56
-rw-r--r--web/react/components/search_autocomplete.jsx157
-rw-r--r--web/react/components/sidebar.jsx2
-rw-r--r--web/react/components/sidebar_header.jsx2
-rw-r--r--web/react/components/team_members.jsx10
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx6
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx6
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx90
-rw-r--r--web/react/stores/browser_store.jsx37
-rw-r--r--web/react/stores/user_store.jsx9
-rw-r--r--web/react/utils/client.jsx1
-rw-r--r--web/react/utils/highlight.jsx51
-rw-r--r--web/react/utils/markdown.jsx351
-rw-r--r--web/react/utils/utils.jsx20
-rw-r--r--web/sass-files/sass/partials/_modal.scss2
-rw-r--r--web/sass-files/sass/partials/_post.scss116
-rw-r--r--web/static/images/bing.mp3bin0 -> 5760 bytes
-rw-r--r--web/static/js/perfect-scrollbar-0.6.7.jquery.js9
-rw-r--r--web/static/js/perfect-scrollbar-0.6.7.jquery.min.js2
-rw-r--r--web/templates/head.html9
-rw-r--r--web/web.go17
45 files changed, 2300 insertions, 310 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index db8c90023..bd571efed 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -46,5 +46,7 @@ git checkout -b <branch name>
- For example, for a ticket ID `PLT-394` start your comment with: `PLT-394:`. See [previously closed pull requests](https://github.com/mattermost/platform/pulls?q=is%3Apr+is%3Aclosed) for examples
3. Once submitted, your pull request will be checked via an automated build process and will be reviewed by at least two members of the Mattermost core team, who may either accept the PR or follow-up with feedback. It would then get merged into `master` for the next release
+ 1. If the build fails, check the error log to narrow down the reason
+ 2. Sometimes one of the multiple build tests will randomly fail due to issues in Travis CI so if you see just one build failure and no clear error message it may be a random issue. Add a comment so the reviewer for your change can re-run the build for you, or close the PR and re-submit and that typically clears the issue
4. If you've included your mailing address in Step 1, you'll be receiving a [Limited Edition Mattermost Mug](http://forum.mattermost.org/t/limited-edition-mattermost-mugs/143) as a thank you gift after your first pull request has been accepted
diff --git a/api/post.go b/api/post.go
index b52db8752..3892d4ee8 100644
--- a/api/post.go
+++ b/api/post.go
@@ -147,7 +147,7 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
return rpost, nil
}
-func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIconUrl string) (*model.Post, *model.AppError) {
+func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string) (*model.Post, *model.AppError) {
// parse links into Markdown format
linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
@@ -155,7 +155,7 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc
linkRegex := regexp.MustCompile(`<\s*(\S*)\s*>`)
text = linkRegex.ReplaceAllString(text, "${1}")
- post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text}
+ post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text, Type: postType}
post.AddProp("from_webhook", "true")
if utils.Cfg.ServiceSettings.EnablePostUsernameOverride {
@@ -174,6 +174,14 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc
}
}
+ if len(props) > 0 {
+ for key, val := range props {
+ if key != "override_icon_url" && key != "override_username" && key != "from_webhook" {
+ post.AddProp(key, val)
+ }
+ }
+ }
+
if _, err := CreatePost(c, post, false); err != nil {
return nil, model.NewAppError("CreateWebhookPost", "Error creating post", "err="+err.Message)
}
@@ -286,7 +294,7 @@ func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team
newContext := &Context{mockSession, model.NewId(), "", c.Path, nil, c.teamURLValid, c.teamURL, c.siteURL, 0}
if text, ok := respProps["text"]; ok {
- if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"]); err != nil {
+ if _, err := CreateWebhookPost(newContext, post.ChannelId, text, respProps["username"], respProps["icon_url"], post.Props, post.Type); err != nil {
l4g.Error("Failed to create response post, err=%v", err)
}
}
diff --git a/api/user.go b/api/user.go
index c871d7c79..774ceddbf 100644
--- a/api/user.go
+++ b/api/user.go
@@ -114,7 +114,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
sendWelcomeEmail = false
}
- if len(user.AuthData) > 0 && len(user.AuthService) > 0 {
+ if user.IsSSOUser() {
user.EmailVerified = true
}
diff --git a/doc/developer/tests/test-emoticons.md b/doc/developer/tests/test-emoticons.md
new file mode 100644
index 000000000..9ea1afec0
--- /dev/null
+++ b/doc/developer/tests/test-emoticons.md
@@ -0,0 +1,930 @@
+# Emoticon Testing
+
+The below text can be used to check for regressions in changes to text processing. Paste the below text into messages and check that emoticons continue to render after text processing changes.
+
+```
+# Emoticon - Punctuation
+
+:) :-)
+;) ;-)
+:o :O
+:-o :-O
+:] :-]
+:d :-D
+x-d x-D
+:p :-P
+:@
+:( :-(
+:'(
+:* :-*
+:/ :-/
+:s :-s
+:| :-|
+:$ :-$
+:-x
+<3
+:+1:
+:-1:
+```
+
+```
+# Emoticons - People
+
+:bowtie:
+:smile:
+:laughing:
+:blush:
+:smiley:
+:relaxed:
+:smirk:
+:heart_eyes:
+:kissing_heart:
+:kissing_closed_eyes:
+:flushed:
+:relieved:
+:satisfied:
+:grin:
+:wink:
+:stuck_out_tongue_winking_eye:
+:stuck_out_tongue_closed_eyes:
+:grinning:
+:kissing:
+:kissing_smiling_eyes:
+:stuck_out_tongue:
+:sleeping:
+:worried:
+:frowning:
+:anguished:
+:open_mouth:
+:grimacing:
+:confused:
+:hushed:
+:expressionless:
+:unamused:
+:sweat_smile:
+:sweat:
+:disappointed_relieved:
+:weary:
+:pensive:
+:disappointed:
+:confounded:
+:fearful:
+:cold_sweat:
+:persevere:
+:cry:
+:sob:
+:joy:
+:astonished:
+:scream:
+:neckbeard:
+:tired_face:
+:angry:
+:rage:
+:triumph:
+:sleepy:
+:yum:
+:mask:
+:sunglasses:
+:dizzy_face:
+:imp:
+:smiling_imp:
+:neutral_face:
+:no_mouth:
+:innocent:
+:alien:
+:yellow_heart:
+:blue_heart:
+:purple_heart:
+:heart:
+:green_heart:
+:broken_heart:
+:heartbeat:
+:heartpulse:
+:two_hearts:
+:revolving_hearts:
+:cupid:
+:sparkling_heart:
+:sparkles:
+:star:
+:star2:
+:dizzy:
+:boom:
+:collision:
+:anger:
+:exclamation:
+:question:
+:grey_exclamation:
+:grey_question:
+:zzz:
+:dash:
+:sweat_drops:
+:notes:
+:musical_note:
+:fire:
+:hankey:
+:poop:
+:shit:
+:+1:
+:thumbsup:
+:-1:
+:thumbsdown:
+:ok_hand:
+:punch:
+:facepunch:
+:fist:
+:v:
+:wave:
+:hand:
+:raised_hand:
+:open_hands:
+:point_up:
+:point_down:
+:point_left:
+:point_right:
+:raised_hands:
+:pray:
+:point_up_2:
+:clap:
+:muscle:
+:metal:
+:fu:
+:runner:
+:running:
+:couple:
+:family:
+:two_men_holding_hands:
+:two_women_holding_hands:
+:dancer:
+:dancers:
+:ok_woman:
+:no_good:
+:information_desk_person:
+:raising_hand:
+:bride_with_veil:
+:person_with_pouting_face:
+:person_frowning:
+:bow:
+:couplekiss:
+:couple_with_heart:
+:massage:
+:haircut:
+:nail_care:
+:boy:
+:girl:
+:woman:
+:man:
+:baby:
+:older_woman:
+:older_man:
+:person_with_blond_hair:
+:man_with_gua_pi_mao:
+:man_with_turban:
+:construction_worker:
+:cop:
+:angel:
+:princess:
+:smiley_cat:
+:smile_cat:
+:heart_eyes_cat:
+:kissing_cat:
+:smirk_cat:
+:scream_cat:
+:crying_cat_face:
+:joy_cat:
+:pouting_cat:
+:japanese_ogre:
+:japanese_goblin:
+:see_no_evil:
+:hear_no_evil:
+:speak_no_evil:
+:guardsman:
+:skull:
+:feet:
+:lips:
+:kiss:
+:droplet:
+:ear:
+:eyes:
+:nose:
+:tongue:
+:love_letter:
+:bust_in_silhouette:
+:busts_in_silhouette:
+:speech_balloon:
+:thought_balloon:
+:feelsgood:
+:finnadie:
+:goberserk:
+:godmode:
+:hurtrealbad:
+:rage1:
+:rage2:
+:rage3:
+:rage4:
+:suspect:
+:trollface:
+```
+
+```
+# Emoticons - Nature
+
+:sunny:
+:umbrella:
+:cloud:
+:snowflake:
+:snowman:
+:zap:
+:cyclone:
+:foggy:
+:ocean:
+:cat:
+:dog:
+:mouse:
+:hamster:
+:rabbit:
+:wolf:
+:frog:
+:tiger:
+:koala:
+:bear:
+:pig:
+:pig_nose:
+:cow:
+:boar:
+:monkey_face:
+:monkey:
+:horse:
+:racehorse:
+:camel:
+:sheep:
+:elephant:
+:panda_face:
+:snake:
+:bird:
+:baby_chick:
+:hatched_chick:
+:hatching_chick:
+:chicken:
+:penguin:
+:turtle:
+:bug:
+:honeybee:
+:ant:
+:beetle:
+:snail:
+:octopus:
+:tropical_fish:
+:fish:
+:whale:
+:whale2:
+:dolphin:
+:cow2:
+:ram:
+:rat:
+:water_buffalo:
+:tiger2:
+:rabbit2:
+:dragon:
+:goat:
+:rooster:
+:dog2:
+:pig2:
+:mouse2:
+:ox:
+:dragon_face:
+:blowfish:
+:crocodile:
+:dromedary_camel:
+:leopard:
+:cat2:
+:poodle:
+:paw_prints:
+:bouquet:
+:cherry_blossom:
+:tulip:
+:four_leaf_clover:
+:rose:
+:sunflower:
+:hibiscus:
+:maple_leaf:
+:leaves:
+:fallen_leaf:
+:herb:
+:mushroom:
+:cactus:
+:palm_tree:
+:evergreen_tree:
+:deciduous_tree:
+:chestnut:
+:seedling:
+:blossom:
+:ear_of_rice:
+:shell:
+:globe_with_meridians:
+:sun_with_face:
+:full_moon_with_face:
+:new_moon_with_face:
+:new_moon:
+:waxing_crescent_moon:
+:first_quarter_moon:
+:waxing_gibbous_moon:
+:full_moon:
+:waning_gibbous_moon:
+:last_quarter_moon:
+:waning_crescent_moon:
+:last_quarter_moon_with_face:
+:first_quarter_moon_with_face:
+:crescent_moon:
+:earth_africa:
+:earth_americas:
+:earth_asia:
+:volcano:
+:milky_way:
+:partly_sunny:
+:octocat:
+:squirrel:
+```
+
+```
+# Emoticons - Objects
+
+:bamboo:
+:gift_heart:
+:dolls:
+:school_satchel:
+:mortar_board:
+:flags:
+:fireworks:
+:sparkler:
+:wind_chime:
+:rice_scene:
+:jack_o_lantern:
+:ghost:
+:santa:
+:christmas_tree:
+:gift:
+:bell:
+:no_bell:
+:tanabata_tree:
+:tada:
+:confetti_ball:
+:balloon:
+:crystal_ball:
+:cd:
+:dvd:
+:floppy_disk:
+:camera:
+:video_camera:
+:movie_camera:
+:computer:
+:tv:
+:iphone:
+:phone:
+:telephone:
+:telephone_receiver:
+:pager:
+:fax:
+:minidisc:
+:vhs:
+:sound:
+:speaker:
+:mute:
+:loudspeaker:
+:mega:
+:hourglass:
+:hourglass_flowing_sand:
+:alarm_clock:
+:watch:
+:radio:
+:satellite:
+:loop:
+:mag:
+:mag_right:
+:unlock:
+:lock:
+:lock_with_ink_pen:
+:closed_lock_with_key:
+:key:
+:bulb:
+:flashlight:
+:high_brightness:
+:low_brightness:
+:electric_plug:
+:battery:
+:calling:
+:email:
+:mailbox:
+:postbox:
+:bath:
+:bathtub:
+:shower:
+:toilet:
+:wrench:
+:nut_and_bolt:
+:hammer:
+:seat:
+:moneybag:
+:yen:
+:dollar:
+:pound:
+:euro:
+:credit_card:
+:money_with_wings:
+:e-mail:
+:inbox_tray:
+:outbox_tray:
+:envelope:
+:incoming_envelope:
+:postal_horn:
+:mailbox_closed:
+:mailbox_with_mail:
+:mailbox_with_no_mail:
+:package:
+:door:
+:smoking:
+:bomb:
+:gun:
+:hocho:
+:pill:
+:syringe:
+:page_facing_up:
+:page_with_curl:
+:bookmark_tabs:
+:bar_chart:
+:chart_with_upwards_trend:
+:chart_with_downwards_trend:
+:scroll:
+:clipboard:
+:calendar:
+:date:
+:card_index:
+:file_folder:
+:open_file_folder:
+:scissors:
+:pushpin:
+:paperclip:
+:black_nib:
+:pencil2:
+:straight_ruler:
+:triangular_ruler:
+:closed_book:
+:green_book:
+:blue_book:
+:orange_book:
+:notebook:
+:notebook_with_decorative_cover:
+:ledger:
+:books:
+:bookmark:
+:name_badge:
+:microscope:
+:telescope:
+:newspaper:
+:football:
+:basketball:
+:soccer:
+:baseball:
+:tennis:
+:8ball:
+:8ball:
+:rugby_football:
+:bowling:
+:golf:
+:mountain_bicyclist:
+:bicyclist:
+:horse_racing:
+:snowboarder:
+:swimmer:
+:surfer:
+:ski:
+:spades:
+:hearts:
+:clubs:
+:diamonds:
+:gem:
+:ring:
+:trophy:
+:musical_score:
+:musical_keyboard:
+:violin:
+:space_invader:
+:video_game:
+:black_joker:
+:flower_playing_cards:
+:game_die:
+:dart:
+:mahjong:
+:clapper:
+:memo:
+:pencil:
+:book:
+:art:
+:microphone:
+:headphones:
+:trumpet:
+:saxophone:
+:guitar:
+:shoe:
+:sandal:
+:high_heel:
+:lipstick:
+:boot:
+:shirt:
+:tshirt:
+:necktie:
+:womans_clothes:
+:dress:
+:running_shirt_with_sash:
+:jeans:
+:kimono:
+:bikini:
+:ribbon:
+:tophat:
+:crown:
+:womans_hat:
+:mans_shoe:
+:closed_umbrella:
+:briefcase:
+:handbag:
+:pouch:
+:purse:
+:eyeglasses:
+:fishing_pole_and_fish:
+:coffee:
+:tea:
+:sake:
+:baby_bottle:
+:beer:
+:beers:
+:cocktail:
+:tropical_drink:
+:wine_glass:
+:fork_and_knife:
+:pizza:
+:hamburger:
+:fries:
+:poultry_leg:
+:meat_on_bone:
+:spaghetti:
+:curry:
+:fried_shrimp:
+:bento:
+:sushi:
+:fish_cake:
+:rice_ball:
+:rice_cracker:
+:rice:
+:ramen:
+:stew:
+:oden:
+:dango:
+:egg:
+:bread:
+:doughnut:
+:custard:
+:icecream:
+:ice_cream:
+:shaved_ice:
+:birthday:
+:cake:
+:cookie:
+:chocolate_bar:
+:candy:
+:lollipop:
+:honey_pot:
+:apple:
+:green_apple:
+:tangerine:
+:lemon:
+:cherries:
+:grapes:
+:watermelon:
+:strawberry:
+:peach:
+:melon:
+:banana:
+:pear:
+:pineapple:
+:sweet_potato:
+:eggplant:
+:tomato:
+:corn:
+```
+
+```
+# Emoticons - Places
+
+:house:
+:house_with_garden:
+:school:
+:office:
+:post_office:
+:hospital:
+:bank:
+:convenience_store:
+:love_hotel:
+:hotel:
+:wedding:
+:church:
+:department_store:
+:european_post_office:
+:city_sunrise:
+:city_sunset:
+:japanese_castle:
+:european_castle:
+:tent:
+:factory:
+:tokyo_tower:
+:japan:
+:mount_fuji:
+:sunrise_over_mountains:
+:sunrise:
+:stars:
+:statue_of_liberty:
+:bridge_at_night:
+:carousel_horse:
+:rainbow:
+:ferris_wheel:
+:fountain:
+:roller_coaster:
+:ship:
+:speedboat:
+:boat:
+:sailboat:
+:rowboat:
+:anchor:
+:rocket:
+:airplane:
+:helicopter:
+:steam_locomotive:
+:tram:
+:mountain_railway:
+:bike:
+:aerial_tramway:
+:suspension_railway:
+:mountain_cableway:
+:tractor:
+:blue_car:
+:oncoming_automobile:
+:car:
+:red_car:
+:taxi:
+:oncoming_taxi:
+:articulated_lorry:
+:bus:
+:oncoming_bus:
+:rotating_light:
+:police_car:
+:oncoming_police_car:
+:fire_engine:
+:ambulance:
+:minibus:
+:truck:
+:train:
+:station:
+:train2:
+:bullettrain_front:
+:bullettrain_side:
+:light_rail:
+:monorail:
+:railway_car:
+:trolleybus:
+:ticket:
+:fuelpump:
+:vertical_traffic_light:
+:traffic_light:
+:warning:
+:construction:
+:beginner:
+:atm:
+:slot_machine:
+:busstop:
+:barber:
+:hotsprings:
+:checkered_flag:
+:crossed_flags:
+:izakaya_lantern:
+:moyai:
+:circus_tent:
+:performing_arts:
+:round_pushpin:
+:triangular_flag_on_post:
+:jp:
+:kr:
+:cn:
+:us:
+:fr:
+:es:
+:it:
+:ru:
+:gb:
+:uk:
+:de:
+```
+
+```
+# Emoticons - Symbols
+
+:one:
+:two:
+:three:
+:four:
+:five:
+:six:
+:seven:
+:eight:
+:nine:
+:keycap_ten:
+:1234:
+:zero:
+:hash:
+:symbols:
+:arrow_backward:
+:arrow_down:
+:arrow_forward:
+:arrow_left:
+:capital_abcd:
+:abcd:
+:abc:
+:arrow_lower_left:
+:arrow_lower_right:
+:arrow_right:
+:arrow_up:
+:arrow_upper_left:
+:arrow_upper_right:
+:arrow_double_down:
+:arrow_double_up:
+:arrow_down_small:
+:arrow_heading_down:
+:arrow_heading_up:
+:leftwards_arrow_with_hook:
+:arrow_right_hook:
+:left_right_arrow:
+:arrow_up_down:
+:arrow_up_small:
+:arrows_clockwise:
+:arrows_counterclockwise:
+:rewind:
+:fast_forward:
+:information_source:
+:ok:
+:twisted_rightwards_arrows:
+:repeat:
+:repeat_one:
+:new:
+:top:
+:up:
+:cool:
+:free:
+:ng:
+:cinema:
+:koko:
+:signal_strength:
+:u5272:
+:u5408:
+:u55b6:
+:u6307:
+:u6708:
+:u6709:
+:u6e80:
+:u7121:
+:u7533:
+:u7a7a:
+:u7981:
+:sa:
+:restroom:
+:mens:
+:womens:
+:baby_symbol:
+:no_smoking:
+:parking:
+:wheelchair:
+:metro:
+:baggage_claim:
+:accept:
+:wc:
+:potable_water:
+:put_litter_in_its_place:
+:secret:
+:congratulations:
+:m:
+:passport_control:
+:left_luggage:
+:customs:
+:ideograph_advantage:
+:cl:
+:sos:
+:id:
+:no_entry_sign:
+:underage:
+:no_mobile_phones:
+:do_not_litter:
+:non-potable_water:
+:no_bicycles:
+:no_pedestrians:
+:children_crossing:
+:no_entry:
+:eight_spoked_asterisk:
+:sparkle:
+:eight_pointed_black_star:
+:heart_decoration:
+:vs:
+:vibration_mode:
+:mobile_phone_off:
+:chart:
+:currency_exchange:
+:aries:
+:taurus:
+:gemini:
+:cancer:
+:leo:
+:virgo:
+:libra:
+:scorpius:
+:sagittarius:
+:capricorn:
+:aquarius:
+:pisces:
+:ophiuchus:
+:six_pointed_star:
+:negative_squared_cross_mark:
+:a:
+:b:
+:ab:
+:o2:
+:diamond_shape_with_a_dot_inside:
+:recycle:
+:end:
+:back:
+:on:
+:soon:
+:clock1:
+:clock130:
+:clock10:
+:clock1030:
+:clock11:
+:clock1130:
+:clock12:
+:clock1230:
+:clock2:
+:clock230:
+:clock3:
+:clock330:
+:clock4:
+:clock430:
+:clock5:
+:clock530:
+:clock6:
+:clock630:
+:clock7:
+:clock730:
+:clock8:
+:clock830:
+:clock9:
+:clock930:
+:heavy_dollar_sign:
+:copyright:
+:registered:
+:tm:
+:x:
+:heavy_exclamation_mark:
+:bangbang:
+:interrobang:
+:o:
+:heavy_multiplication_x:
+:heavy_plus_sign:
+:heavy_minus_sign:
+:heavy_division_sign:
+:white_flower:
+:100:
+:heavy_check_mark:
+:ballot_box_with_check:
+:radio_button:
+:link:
+:curly_loop:
+:wavy_dash:
+:part_alternation_mark:
+:trident:
+:black_small_square:
+:white_small_square:
+:black_medium_small_square:
+:white_medium_small_square:
+:black_medium_square:
+:white_medium_square:
+:black_large_square:
+:white_large_square:
+:white_check_mark:
+:black_square_button:
+:white_square_button:
+:black_circle:
+:white_circle:
+:red_circle:
+:large_blue_circle:
+:large_blue_diamond:
+:large_orange_diamond:
+:small_blue_diamond:
+:small_orange_diamond:
+:small_red_triangle:
+:small_red_triangle_down:
+:shipit:
+```
diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md
index 2e02cca38..ec0c1c96b 100644
--- a/doc/install/Production-Ubuntu.md
+++ b/doc/install/Production-Ubuntu.md
@@ -103,7 +103,7 @@ exec bin/platform
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN;
- proxy_pass http://localhost:8065;
+ proxy_pass http://10.10.10.2:8065;
}
}
```
diff --git a/doc/install/Release-Numbering.md b/doc/install/Release-Numbering.md
index 3b0391cff..41aba109f 100644
--- a/doc/install/Release-Numbering.md
+++ b/doc/install/Release-Numbering.md
@@ -1,4 +1,10 @@
-### Mattermost Release Numbering
+# Mattermost Release Schedule and Numbering
+
+## Release Schedule
+
+Mattermost releases stable builds monthly on the 16th in [binary form](https://github.com/mattermost/platform/releases)
+
+## Release Numbering
Mattermost numbers its stable releases based on the following format:
diff --git a/model/incoming_webhook.go b/model/incoming_webhook.go
index be1984244..8ead0da9f 100644
--- a/model/incoming_webhook.go
+++ b/model/incoming_webhook.go
@@ -24,10 +24,13 @@ type IncomingWebhook struct {
}
type IncomingWebhookRequest struct {
- Text string `json:"text"`
- Username string `json:"username"`
- IconURL string `json:"icon_url"`
- ChannelName string `json:"channel"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconURL string `json:"icon_url"`
+ ChannelName string `json:"channel"`
+ Props StringInterface `json:"props"`
+ Attachments interface{} `json:"attachments"`
+ Type string `json:"type"`
}
func (o *IncomingWebhook) ToJson() string {
diff --git a/model/post.go b/model/post.go
index e0074b348..248d40321 100644
--- a/model/post.go
+++ b/model/post.go
@@ -10,27 +10,28 @@ import (
)
const (
- POST_DEFAULT = ""
- POST_JOIN_LEAVE = "join_leave"
+ POST_DEFAULT = ""
+ POST_SLACK_ATTACHMENT = "slack_attachment"
+ POST_JOIN_LEAVE = "join_leave"
)
type Post struct {
- Id string `json:"id"`
- CreateAt int64 `json:"create_at"`
- UpdateAt int64 `json:"update_at"`
- DeleteAt int64 `json:"delete_at"`
- UserId string `json:"user_id"`
- ChannelId string `json:"channel_id"`
- RootId string `json:"root_id"`
- ParentId string `json:"parent_id"`
- OriginalId string `json:"original_id"`
- Message string `json:"message"`
- ImgCount int64 `json:"img_count"`
- Type string `json:"type"`
- Props StringMap `json:"props"`
- Hashtags string `json:"hashtags"`
- Filenames StringArray `json:"filenames"`
- PendingPostId string `json:"pending_post_id" db:"-"`
+ Id string `json:"id"`
+ CreateAt int64 `json:"create_at"`
+ UpdateAt int64 `json:"update_at"`
+ DeleteAt int64 `json:"delete_at"`
+ UserId string `json:"user_id"`
+ ChannelId string `json:"channel_id"`
+ RootId string `json:"root_id"`
+ ParentId string `json:"parent_id"`
+ OriginalId string `json:"original_id"`
+ Message string `json:"message"`
+ ImgCount int64 `json:"img_count"`
+ Type string `json:"type"`
+ Props StringInterface `json:"props"`
+ Hashtags string `json:"hashtags"`
+ Filenames StringArray `json:"filenames"`
+ PendingPostId string `json:"pending_post_id" db:"-"`
}
func (o *Post) ToJson() string {
@@ -103,7 +104,8 @@ func (o *Post) IsValid() *AppError {
return NewAppError("Post.IsValid", "Invalid hashtags", "id="+o.Id)
}
- if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE) {
+ // should be removed once more message types are supported
+ if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT) {
return NewAppError("Post.IsValid", "Invalid type", "id="+o.Type)
}
@@ -128,7 +130,7 @@ func (o *Post) PreSave() {
o.UpdateAt = o.CreateAt
if o.Props == nil {
- o.Props = make(map[string]string)
+ o.Props = make(map[string]interface{})
}
if o.Filenames == nil {
@@ -138,14 +140,14 @@ func (o *Post) PreSave() {
func (o *Post) MakeNonNil() {
if o.Props == nil {
- o.Props = make(map[string]string)
+ o.Props = make(map[string]interface{})
}
if o.Filenames == nil {
o.Filenames = []string{}
}
}
-func (o *Post) AddProp(key string, value string) {
+func (o *Post) AddProp(key string, value interface{}) {
o.MakeNonNil()
diff --git a/model/user.go b/model/user.go
index 871d1bf2d..4365f47d2 100644
--- a/model/user.go
+++ b/model/user.go
@@ -326,6 +326,13 @@ func IsInRole(userRoles string, inRole string) bool {
return false
}
+func (u *User) IsSSOUser() bool {
+ if len(u.AuthData) != 0 && len(u.AuthService) != 0 {
+ return true
+ }
+ return false
+}
+
func (u *User) PreExport() {
u.Password = ""
u.AuthData = ""
diff --git a/model/utils.go b/model/utils.go
index 681ade870..1e71836c1 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -17,6 +17,7 @@ import (
"time"
)
+type StringInterface map[string]interface{}
type StringMap map[string]string
type StringArray []string
type EncryptStringMap map[string]string
@@ -125,6 +126,25 @@ func ArrayFromJson(data io.Reader) []string {
}
}
+func StringInterfaceToJson(objmap map[string]interface{}) string {
+ if b, err := json.Marshal(objmap); err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
+ decoder := json.NewDecoder(data)
+
+ var objmap map[string]interface{}
+ if err := decoder.Decode(&objmap); err != nil {
+ return make(map[string]interface{})
+ } else {
+ return objmap
+ }
+}
+
func IsLower(s string) bool {
if strings.ToLower(s) == s {
return true
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index fdae20f60..3460fca92 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -9,8 +9,10 @@ import (
"strconv"
"strings"
+ l4g "code.google.com/p/log4go"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
+ "time"
)
type SqlPostStore struct {
@@ -30,7 +32,7 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
table.ColMap("Message").SetMaxSize(4000)
table.ColMap("Type").SetMaxSize(26)
table.ColMap("Hashtags").SetMaxSize(1000)
- table.ColMap("Props").SetMaxSize(4000)
+ table.ColMap("Props")
table.ColMap("Filenames").SetMaxSize(4000)
}
@@ -38,6 +40,21 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
}
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
+ colType := s.GetColumnDataType("Posts", "Props")
+ if colType != "text" {
+
+ query := "ALTER TABLE Posts MODIFY COLUMN Props TEXT"
+ if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
+ query = "ALTER TABLE Posts ALTER COLUMN Props TYPE text"
+ }
+
+ _, err := s.GetMaster().Exec(query)
+ if err != nil {
+ l4g.Critical("Failed to alter column Posts.Props to TEXT: " + err.Error())
+ time.Sleep(time.Second)
+ panic("Failed to alter column Posts.Props to TEXT: " + err.Error())
+ }
+ }
}
func (s SqlPostStore) CreateIndexesIfNotExists() {
diff --git a/store/sql_store.go b/store/sql_store.go
index e5c540e06..f348db10b 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -455,6 +455,20 @@ func IsUniqueConstraintError(err string, mysql string, postgres string) bool {
return unique && field
}
+func (ss SqlStore) GetColumnDataType(tableName, columnName string) string {
+ dataType, err := ss.GetMaster().SelectStr("SELECT data_type FROM INFORMATION_SCHEMA.COLUMNS where table_name = :Tablename AND column_name = :Columnname", map[string]interface{}{
+ "Tablename": tableName,
+ "Columnname": columnName,
+ })
+ if err != nil {
+ l4g.Critical("Failed to get data type for column %s from table %s: %v", columnName, tableName, err.Error())
+ time.Sleep(time.Second)
+ panic("Failed to get get data type for column " + columnName + " from table " + tableName + ": " + err.Error())
+ }
+
+ return dataType
+}
+
func (ss SqlStore) GetMaster() *gorp.DbMap {
return ss.master
}
@@ -529,6 +543,8 @@ func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
return model.ArrayToJson(t), nil
case model.EncryptStringMap:
return encrypt([]byte(utils.Cfg.SqlSettings.AtRestEncryptKey), model.MapToJson(t))
+ case model.StringInterface:
+ return model.StringInterfaceToJson(t), nil
}
return val, nil
@@ -572,6 +588,16 @@ func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool)
return json.Unmarshal(b, target)
}
return gorp.CustomScanner{new(string), target, binder}, true
+ case *model.StringInterface:
+ binder := func(holder, target interface{}) error {
+ s, ok := holder.(*string)
+ if !ok {
+ return errors.New("FromDb: Unable to convert StringInterface to *string")
+ }
+ b := []byte(*s)
+ return json.Unmarshal(b, target)
+ }
+ return gorp.CustomScanner{new(string), target, binder}, true
}
return gorp.CustomScanner{}, false
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 3347df08b..686949a4d 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -140,7 +140,9 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
user.DeleteAt = oldUser.DeleteAt
}
- if user.Email != oldUser.Email {
+ if user.IsSSOUser() {
+ user.Email = oldUser.Email
+ } else if user.Email != oldUser.Email {
user.EmailVerified = false
}
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index f7e92672d..2badaf0e5 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -212,50 +212,52 @@ export default class UserItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
- height='36'
- width='36'
- />
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- id='channel_header_dropdown'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'></span>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- {makeSystemAdmin}
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleResetPassword}
- >
- {'Reset Password'}
- </a>
- </li>
- </ul>
- </div>
- {serverError}
- </div>
+ <tr>
+ <td className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${user.update_at}&${Utils.getSessionIndex()}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ {makeSystemAdmin}
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleResetPassword}
+ >
+ {'Reset Password'}
+ </a>
+ </li>
+ </ul>
+ </div>
+ {serverError}
+ </td>
+ </tr>
);
}
}
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index ea0eec747..3ee40bb86 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -21,7 +21,7 @@ export default class CenterPanel extends React.Component {
this.onPreferenceChange = this.onPreferenceChange.bind(this);
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.state = {showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS};
}
componentDidMount() {
@@ -31,7 +31,7 @@ export default class CenterPanel extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
onPreferenceChange() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS});
}
render() {
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 2dc12c9aa..7c1032321 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -54,6 +54,16 @@ export default class ChannelInviteModal extends React.Component {
loading
};
}
+ onShow() {
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
+ }
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
@@ -103,6 +113,11 @@ export default class ChannelInviteModal extends React.Component {
);
}
render() {
+ var maxHeight = 1000;
+ if (Utils.windowHeight() <= 1200) {
+ maxHeight = Utils.windowHeight() - 300;
+ }
+
var inviteError = null;
if (this.state.inviteError) {
inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>);
@@ -129,13 +144,17 @@ export default class ChannelInviteModal extends React.Component {
return (
<Modal
+ dialogClassName='more-modal'
show={this.props.show}
onHide={this.props.onModalDismissed}
>
<Modal.Header closeButton={true}>
<Modal.Title>{'Add New Members to '}<span className='name'>{this.state.channelName}</span></Modal.Title>
</Modal.Header>
- <Modal.Body>
+ <Modal.Body
+ ref='modalBody'
+ style={{maxHeight}}
+ >
{inviteError}
{content}
</Modal.Body>
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index 1df0da9cf..2fa7ae8ff 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -70,6 +70,16 @@ export default class ChannelMembersModal extends React.Component {
channelName
};
}
+ onShow() {
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
+ }
+ }
+ componentDidUpdate(prevProps) {
+ if (this.props.show && !prevProps.show) {
+ this.onShow();
+ }
+ }
componentWillReceiveProps(nextProps) {
if (!this.props.show && nextProps.show) {
ChannelStore.addExtraInfoChangeListener(this.onChange);
@@ -128,6 +138,11 @@ export default class ChannelMembersModal extends React.Component {
);
}
render() {
+ var maxHeight = 1000;
+ if (Utils.windowHeight() <= 1200) {
+ maxHeight = Utils.windowHeight() - 300;
+ }
+
const currentMember = ChannelStore.getCurrentMember();
let isAdmin = false;
if (currentMember) {
@@ -137,6 +152,7 @@ export default class ChannelMembersModal extends React.Component {
return (
<div>
<Modal
+ dialogClassName='more-modal'
show={this.props.show}
onHide={this.props.onModalDismissed}
>
@@ -153,15 +169,16 @@ export default class ChannelMembersModal extends React.Component {
<i className='glyphicon glyphicon-envelope'/>{' Add New Members'}
</a>
</Modal.Header>
- <Modal.Body>
- <div className='col-sm-12'>
- <div className='team-member-list'>
- <MemberList
- memberList={this.state.memberList}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- </div>
+ <Modal.Body
+ ref='modalBody'
+ style={{maxHeight}}
+ >
+ <div className='team-member-list'>
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
</div>
</Modal.Body>
<Modal.Footer>
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 4d1874e18..5a69c9bfb 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -50,7 +50,7 @@ export default class CreatePost extends React.Component {
PostStore.clearDraftUploads();
const draft = this.getCurrentDraft();
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.state = {
channelId: ChannelStore.getCurrentId(),
@@ -336,7 +336,7 @@ export default class CreatePost extends React.Component {
}
}
onPreferenceChange() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
this.setState({
showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER,
ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value
diff --git a/web/react/components/edit_channel_purpose_modal.jsx b/web/react/components/edit_channel_purpose_modal.jsx
index 4cb96a3ff..65e8183de 100644
--- a/web/react/components/edit_channel_purpose_modal.jsx
+++ b/web/react/components/edit_channel_purpose_modal.jsx
@@ -3,6 +3,8 @@
const AsyncClient = require('../utils/async_client.jsx');
const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+
const Modal = ReactBootstrap.Modal;
export default class EditChannelPurposeModal extends React.Component {
@@ -75,11 +77,6 @@ export default class EditChannelPurposeModal extends React.Component {
title = <span>{'Edit Purpose for '}<span className='name'>{this.props.channel.display_name}</span></span>;
}
- let channelTerm = 'Channel';
- if (this.props.channel.channelType === 'P') {
- channelTerm = 'Group';
- }
-
return (
<Modal
className='modal-edit-channel-purpose'
@@ -93,7 +90,7 @@ export default class EditChannelPurposeModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <p>{`Describe how this ${channelTerm} should be used.`}</p>
+ <p>{`Describe how this ${Utils.getChannelTerm(this.props.channel.channelType)} should be used.`}</p>
<textarea
ref='purpose'
className='form-control no-resize'
diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx
index 9c0243291..0238c7920 100644
--- a/web/react/components/member_list.jsx
+++ b/web/react/components/member_list.jsx
@@ -15,7 +15,7 @@ export default class MemberList extends React.Component {
members = this.props.memberList;
}
- var message = '';
+ var message = null;
if (members.length === 0) {
message = <tr><td>No users to add.</td></tr>;
}
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 14db05cdb..1fa369068 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -166,40 +166,42 @@ export default class MemberListTeamItem extends React.Component {
}
return (
- <div className='row member-div'>
- <img
- className='post-profile-img pull-left'
- src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
- height='36'
- width='36'
- />
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- id='channel_header_dropdown'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'></span>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- </ul>
- </div>
- {serverError}
- </div>
+ <tr>
+ <td className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${timestamp}&${Utils.getSessionIndex()}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ </ul>
+ </div>
+ {serverError}
+ </td>
+ </tr>
);
}
}
diff --git a/web/react/components/post_attachment.jsx b/web/react/components/post_attachment.jsx
new file mode 100644
index 000000000..2d6b47f03
--- /dev/null
+++ b/web/react/components/post_attachment.jsx
@@ -0,0 +1,295 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const TextFormatting = require('../utils/text_formatting.jsx');
+
+export default class PostAttachment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getFieldsTable = this.getFieldsTable.bind(this);
+ this.getInitState = this.getInitState.bind(this);
+ this.shouldCollapse = this.shouldCollapse.bind(this);
+ this.toggleCollapseState = this.toggleCollapseState.bind(this);
+ }
+
+ componentDidMount() {
+ $(this.refs.attachment).on('click', '.attachment-link-more', this.toggleCollapseState);
+ }
+
+ componentWillUnmount() {
+ $(this.refs.attachment).off('click', '.attachment-link-more', this.toggleCollapseState);
+ }
+
+ componentWillMount() {
+ this.setState(this.getInitState());
+ }
+
+ getInitState() {
+ const shouldCollapse = this.shouldCollapse();
+ const text = TextFormatting.formatText(this.props.attachment.text || '');
+ const uncollapsedText = text + (shouldCollapse ? '<a class="attachment-link-more" href="#">â–² collapse text</a>' : '');
+ const collapsedText = shouldCollapse ? this.getCollapsedText() : text;
+
+ return {
+ shouldCollapse,
+ collapsedText,
+ uncollapsedText,
+ text: shouldCollapse ? collapsedText : uncollapsedText,
+ collapsed: shouldCollapse
+ };
+ }
+
+ toggleCollapseState(e) {
+ e.preventDefault();
+
+ let state = this.state;
+ state.text = state.collapsed ? state.uncollapsedText : state.collapsedText;
+ state.collapsed = !state.collapsed;
+ this.setState(state);
+ }
+
+ shouldCollapse() {
+ return (this.props.attachment.text.match(/\n/g) || []).length >= 5 || this.props.attachment.text.length > 700;
+ }
+
+ getCollapsedText() {
+ let text = this.props.attachment.text || '';
+ if ((text.match(/\n/g) || []).length >= 5) {
+ text = text.split('\n').splice(0, 5).join('\n');
+ } else if (text.length > 700) {
+ text = text.substr(0, 700);
+ }
+
+ return TextFormatting.formatText(text) + '<a class="attachment-link-more" href="#">â–¼ read more</a>';
+ }
+
+ getFieldsTable() {
+ const fields = this.props.attachment.fields;
+ if (!fields || !fields.length) {
+ return '';
+ }
+
+ const compactTable = fields.filter((field) => field.short).length > 0;
+ let tHead;
+ let tBody;
+
+ if (compactTable) {
+ let headerCols = [];
+ let bodyCols = [];
+
+ fields.forEach((field, i) => {
+ headerCols.push(
+ <th
+ className='attachment___field-caption'
+ key={'attachment__field-caption-' + i}
+ >
+ {field.title}
+ </th>
+ );
+ bodyCols.push(
+ <td
+ className='attachment___field'
+ key={'attachment__field-' + i}
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
+ >
+ </td>
+ );
+ });
+
+ tHead = (
+ <tr>
+ {headerCols}
+ </tr>
+ );
+ tBody = (
+ <tr>
+ {bodyCols}
+ </tr>
+ );
+ } else {
+ tBody = [];
+
+ fields.forEach((field, i) => {
+ tBody.push(
+ <tr key={'attachment__field-' + i}>
+ <td
+ className='attachment___field-caption'
+ >
+ {field.title}
+ </td>
+ <td
+ className='attachment___field'
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
+ >
+ </td>
+ </tr>
+ );
+ });
+ }
+
+ return (
+ <table
+ className='attachment___fields'
+ >
+ <thead>
+ {tHead}
+ </thead>
+ <tbody>
+ {tBody}
+ </tbody>
+ </table>
+ );
+ }
+
+ render() {
+ const data = this.props.attachment;
+
+ let preText;
+ if (data.pretext) {
+ preText = (
+ <div
+ className='attachment__thumb-pretext'
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(data.pretext)}}
+ >
+ </div>
+ );
+ }
+
+ let author = [];
+ if (data.author_name || data.author_icon) {
+ if (data.author_icon) {
+ author.push(
+ <img
+ className='attachment__author-icon'
+ src={data.author_icon}
+ key={'attachment__author-icon'}
+ height='14'
+ width='14'
+ />
+ );
+ }
+ if (data.author_name) {
+ author.push(
+ <span
+ className='attachment__author-name'
+ key={'attachment__author-name'}
+ >
+ {data.author_name}
+ </span>
+ );
+ }
+ }
+ if (data.author_link) {
+ author = (
+ <a
+ href={data.author_link}
+ target='_blank'
+ >
+ {author}
+ </a>
+ );
+ }
+
+ let title;
+ if (data.title) {
+ if (data.title_link) {
+ title = (
+ <h1
+ className='attachment__title'
+ >
+ <a
+ className='attachment__title-link'
+ href={data.title_link}
+ target='_blank'
+ >
+ {data.title}
+ </a>
+ </h1>
+ );
+ } else {
+ title = (
+ <h1
+ className='attachment__title'
+ >
+ {data.title}
+ </h1>
+ );
+ }
+ }
+
+ let text;
+ if (data.text) {
+ text = (
+ <div
+ className='attachment__text'
+ dangerouslySetInnerHTML={{__html: this.state.text}}
+ >
+ </div>
+ );
+ }
+
+ let image;
+ if (data.image_url) {
+ image = (
+ <img
+ className='attachment__image'
+ src={data.image_url}
+ />
+ );
+ }
+
+ let thumb;
+ if (data.thumb_url) {
+ thumb = (
+ <div
+ className='attachment__thumb-container'
+ >
+ <img
+ src={data.thumb_url}
+ />
+ </div>
+ );
+ }
+
+ const fields = this.getFieldsTable();
+
+ let useBorderStyle;
+ if (data.color && data.color[0] === '#') {
+ useBorderStyle = {borderLeftColor: data.color};
+ }
+
+ return (
+ <div
+ className='attachment'
+ ref='attachment'
+ >
+ {preText}
+ <div className='attachment__content'>
+ <div
+ className={useBorderStyle ? 'clearfix attachment__container' : 'clearfix attachment__container attachment__container--' + data.color}
+ style={useBorderStyle}
+ >
+ {author}
+ {title}
+ <div>
+ <div
+ className={thumb ? 'attachment__body' : 'attachment__body attachment__body--no_thumb'}
+ >
+ {text}
+ {image}
+ {fields}
+ </div>
+ {thumb}
+ <div style={{clear: 'both'}}></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+PostAttachment.propTypes = {
+ attachment: React.PropTypes.object.isRequired
+}; \ No newline at end of file
diff --git a/web/react/components/post_attachment_list.jsx b/web/react/components/post_attachment_list.jsx
new file mode 100644
index 000000000..03b866656
--- /dev/null
+++ b/web/react/components/post_attachment_list.jsx
@@ -0,0 +1,32 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const PostAttachment = require('./post_attachment.jsx');
+
+export default class PostAttachmentList extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ let content = [];
+ this.props.attachments.forEach((attachment, i) => {
+ content.push(
+ <PostAttachment
+ attachment={attachment}
+ key={'att_' + i}
+ />
+ );
+ });
+
+ return (
+ <div className='attachment_list'>
+ {content}
+ </div>
+ );
+ }
+}
+
+PostAttachmentList.propTypes = {
+ attachments: React.PropTypes.array.isRequired
+};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index e4094daf3..5a157b792 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -7,6 +7,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const TextFormatting = require('../utils/text_formatting.jsx');
const twemoji = require('twemoji');
+const PostBodyAdditionalContent = require('./post_body_additional_content.jsx');
export default class PostBody extends React.Component {
constructor(props) {
@@ -331,6 +332,9 @@ export default class PostBody extends React.Component {
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.message)}}
/>
</div>
+ <PostBodyAdditionalContent
+ post={post}
+ />
{fileAttachmentHolder}
{embed}
</div>
diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx
new file mode 100644
index 000000000..8189ba2d3
--- /dev/null
+++ b/web/react/components/post_body_additional_content.jsx
@@ -0,0 +1,56 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const PostAttachmentList = require('./post_attachment_list.jsx');
+
+export default class PostBodyAdditionalContent extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getSlackAttachment = this.getSlackAttachment.bind(this);
+ this.getComponent = this.getComponent.bind(this);
+ }
+
+ componentWillMount() {
+ this.setState({type: this.props.post.type, shouldRender: Boolean(this.props.post.type)});
+ }
+
+ getSlackAttachment() {
+ const attachments = this.props.post.props && this.props.post.props.attachments || [];
+ return (
+ <PostAttachmentList
+ key={'post_body_additional_content' + this.props.post.id}
+ attachments={attachments}
+ />
+ );
+ }
+
+ getComponent() {
+ switch (this.state.type) {
+ case 'slack_attachment':
+ return this.getSlackAttachment();
+ }
+ }
+
+ render() {
+ let content = [];
+
+ if (this.state.shouldRender) {
+ const component = this.getComponent();
+
+ if (component) {
+ content = component;
+ }
+ }
+
+ return (
+ <div>
+ {content}
+ </div>
+ );
+ }
+}
+
+PostBodyAdditionalContent.propTypes = {
+ post: React.PropTypes.object.isRequired
+}; \ No newline at end of file
diff --git a/web/react/components/search_autocomplete.jsx b/web/react/components/search_autocomplete.jsx
index 03e14ec49..736919697 100644
--- a/web/react/components/search_autocomplete.jsx
+++ b/web/react/components/search_autocomplete.jsx
@@ -3,14 +3,15 @@
const ChannelStore = require('../stores/channel_store.jsx');
const KeyCodes = require('../utils/constants.jsx').KeyCodes;
+const Popover = ReactBootstrap.Popover;
const UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
const patterns = new Map([
['channels', /\b(?:in|channel):\s*(\S*)$/i],
['users', /\bfrom:\s*(\S*)$/i]
]);
-const Popover = ReactBootstrap.Popover;
export default class SearchAutocomplete extends React.Component {
constructor(props) {
@@ -22,8 +23,13 @@ export default class SearchAutocomplete extends React.Component {
this.handleKeyDown = this.handleKeyDown.bind(this);
this.completeWord = this.completeWord.bind(this);
+ this.getSelection = this.getSelection.bind(this);
+ this.scrollToItem = this.scrollToItem.bind(this);
this.updateSuggestions = this.updateSuggestions.bind(this);
+ this.renderChannelSuggestion = this.renderChannelSuggestion.bind(this);
+ this.renderUserSuggestion = this.renderUserSuggestion.bind(this);
+
this.state = {
show: false,
mode: '',
@@ -37,9 +43,18 @@ export default class SearchAutocomplete extends React.Component {
$(document).on('click', this.handleDocumentClick);
}
- componentDidUpdate() {
- $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content').perfectScrollbar();
- $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content').css('max-height', $(window).height() - 200);
+ componentDidUpdate(prevProps, prevState) {
+ const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');
+
+ if (this.state.show) {
+ if (!prevState.show) {
+ content.perfectScrollbar();
+ content.css('max-height', $(window).height() - 200);
+ }
+
+ // keep the keyboard selection visible when scrolling
+ this.scrollToItem(this.getSelection());
+ }
}
componentWillUnmount() {
@@ -51,7 +66,7 @@ export default class SearchAutocomplete extends React.Component {
}
handleDocumentClick(e) {
- const container = $(ReactDOM.findDOMNode(this.refs.container));
+ const container = $(ReactDOM.findDOMNode(this.refs.searchPopover));
if (!(container.is(e.target) || container.has(e.target).length > 0)) {
this.setState({
@@ -111,15 +126,7 @@ export default class SearchAutocomplete extends React.Component {
} else if (e.which === KeyCodes.ENTER || e.which === KeyCodes.SPACE) {
e.preventDefault();
- this.completeSelectedWord();
- }
- }
-
- completeSelectedWord() {
- if (this.state.mode === 'channels') {
- this.completeWord(this.state.suggestions[this.state.selection].name);
- } else if (this.state.mode === 'users') {
- this.completeWord(this.state.suggestions[this.state.selection].username);
+ this.completeWord(this.getSelection());
}
}
@@ -135,6 +142,40 @@ export default class SearchAutocomplete extends React.Component {
});
}
+ getSelection() {
+ if (this.state.mode === 'channels') {
+ return this.state.suggestions[this.state.selection].name;
+ } else if (this.state.mode === 'users') {
+ return this.state.suggestions[this.state.selection].username;
+ }
+
+ return '';
+ }
+
+ scrollToItem(itemName) {
+ const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');
+ const visibleContentHeight = content[0].clientHeight;
+ const actualContentHeight = content[0].scrollHeight;
+
+ if (this.state.suggestions.length > 0 && visibleContentHeight < actualContentHeight) {
+ const contentTop = content.scrollTop();
+ const contentTopPadding = parseInt(content.css('padding-top'), 10);
+ const contentBottomPadding = parseInt(content.css('padding-top'), 10);
+
+ const item = $(this.refs[itemName]);
+ const itemTop = item[0].offsetTop - parseInt(item.css('margin-top'), 10);
+ const itemBottom = item[0].offsetTop + item.height() + parseInt(item.css('margin-bottom'), 10);
+
+ if (itemTop - contentTopPadding < contentTop) {
+ // the item is off the top of the visible space
+ content.scrollTop(itemTop - contentTopPadding);
+ } else if (itemBottom + contentTopPadding + contentBottomPadding > contentTop + visibleContentHeight) {
+ // the item has gone off the bottom of the visible space
+ content.scrollTop(itemBottom - visibleContentHeight + contentTopPadding + contentBottomPadding);
+ }
+ }
+ }
+
updateSuggestions(mode, filter) {
let suggestions = [];
@@ -193,6 +234,46 @@ export default class SearchAutocomplete extends React.Component {
});
}
+ renderChannelSuggestion(channel) {
+ let className = 'search-autocomplete__item';
+ if (channel.name === this.getSelection()) {
+ className += ' selected';
+ }
+
+ return (
+ <div
+ key={channel.name}
+ ref={channel.name}
+ onClick={this.handleClick.bind(this, channel.name)}
+ className={className}
+ >
+ {channel.name}
+ </div>
+ );
+ }
+
+ renderUserSuggestion(user) {
+ let className = 'search-autocomplete__item';
+ if (user.username === this.getSelection()) {
+ className += ' selected';
+ }
+
+ return (
+ <div
+ key={user.username}
+ ref={user.username}
+ onClick={this.handleClick.bind(this, user.username)}
+ className={className}
+ >
+ <img
+ className='profile-img rounded'
+ src={'/api/v1/users/' + user.id + '/image?time=' + user.update_at}
+ />
+ {user.username}
+ </div>
+ );
+ }
+
render() {
if (!this.state.show || this.state.suggestions.length === 0) {
return null;
@@ -201,45 +282,33 @@ export default class SearchAutocomplete extends React.Component {
let suggestions = [];
if (this.state.mode === 'channels') {
- suggestions = this.state.suggestions.map((channel, index) => {
- let className = 'search-autocomplete__item';
- if (this.state.selection === index) {
- className += ' selected';
- }
-
- return (
+ const publicChannels = this.state.suggestions.filter((channel) => channel.type === Constants.OPEN_CHANNEL);
+ if (publicChannels.length > 0) {
+ suggestions.push(
<div
- key={channel.name}
- ref={channel.name}
- onClick={this.handleClick.bind(this, channel.name)}
- className={className}
+ key='public-channel-divider'
+ className='search-autocomplete__divider'
>
- {channel.name}
+ {'Public ' + Utils.getChannelTerm(Constants.OPEN_CHANNEL) + 's'}
</div>
);
- });
- } else if (this.state.mode === 'users') {
- suggestions = this.state.suggestions.map((user, index) => {
- let className = 'search-autocomplete__item';
- if (this.state.selection === index) {
- className += ' selected';
- }
+ suggestions = suggestions.concat(publicChannels.map(this.renderChannelSuggestion));
+ }
- return (
+ const privateChannels = this.state.suggestions.filter((channel) => channel.type === Constants.PRIVATE_CHANNEL);
+ if (privateChannels.length > 0) {
+ suggestions.push(
<div
- key={user.username}
- ref={user.username}
- onClick={this.handleClick.bind(this, user.username)}
- className={className}
+ key='private-channel-divider'
+ className='search-autocomplete__divider'
>
- <img
- className='profile-img rounded'
- src={'/api/v1/users/' + user.id + '/image?time=' + user.update_at}
- />
- {user.username}
+ {'Private ' + Utils.getChannelTerm(Constants.PRIVATE_CHANNEL) + 's'}
</div>
);
- });
+ suggestions = suggestions.concat(privateChannels.map(this.renderChannelSuggestion));
+ }
+ } else if (this.state.mode === 'users') {
+ suggestions = this.state.suggestions.map(this.renderUserSuggestion);
}
return (
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 8b5f7a381..f5ce5c10e 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -146,7 +146,7 @@ export default class Sidebar extends React.Component {
visibleDirectChannels.sort(this.sortChannelsByDisplayName);
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
return {
activeId: currentChannelId,
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 46730e1e6..bc7f6ba50 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -31,7 +31,7 @@ export default class SidebarHeader extends React.Component {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
}
getStateFromStores() {
- const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'});
+ const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'});
return {showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.MENU_POPOVER};
}
diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx
index 33590c89a..ac1ebf52d 100644
--- a/web/react/components/team_members.jsx
+++ b/web/react/components/team_members.jsx
@@ -79,7 +79,7 @@ export default class TeamMembers extends React.Component {
return (
<div
- className='modal fade'
+ className='modal fade more-modal'
ref='modal'
id='team_members'
tabIndex='-1'
@@ -106,12 +106,10 @@ export default class TeamMembers extends React.Component {
ref='modalBody'
className='modal-body'
>
- <div className='channel-settings'>
- <div className='team-member-list'>
- {renderMembers}
- </div>
- {serverError}
+ <div className='team-member-list'>
+ {renderMembers}
</div>
+ {serverError}
</div>
<div className='modal-footer'>
<button
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 24da106d0..4d594bb1b 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -50,7 +50,7 @@ export default class ImportThemeModal extends React.Component {
theme.sidebarText = colors[5];
theme.sidebarUnreadText = colors[5];
theme.sidebarTextHoverBg = colors[4];
- theme.sidebarTextActiveBg = colors[2];
+ theme.sidebarTextActiveBorder = colors[2];
theme.sidebarTextActiveColor = colors[3];
theme.sidebarHeaderBg = colors[1];
theme.sidebarHeaderTextColor = colors[5];
@@ -59,9 +59,13 @@ export default class ImportThemeModal extends React.Component {
theme.mentionColor = '#ffffff';
theme.centerChannelBg = '#ffffff';
theme.centerChannelColor = '#333333';
+ theme.newMessageSeparator = '#F80';
theme.linkColor = '#2389d7';
theme.buttonBg = '#26a970';
theme.buttonColor = '#ffffff';
+ theme.mentionHighlightBg = '#fff2bb';
+ theme.mentionHighlightLink = '#2f81b7';
+ theme.codeTheme = 'github';
let user = UserStore.getCurrentUser();
user.theme_props = theme;
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index 3c12ead23..43c8d33d1 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -172,13 +172,13 @@ export default class UserSettingsDisplay extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'How should other users be shown in Direct Messages list?'}</div>
+ <div><br/>{'Set what name to display in the Direct Messages list.'}</div>
</div>
];
nameFormatSection = (
<SettingItemMax
- title='Show real names, nick names or usernames?'
+ title='Teammate Name Display'
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
@@ -200,7 +200,7 @@ export default class UserSettingsDisplay extends React.Component {
nameFormatSection = (
<SettingItemMin
- title='Show real names, nick names or usernames?'
+ title='Teammate Name Display'
describe={describe}
updateSection={() => {
this.props.updateSection('name_format');
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 9f0c16194..1bfae6930 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -451,44 +451,60 @@ export default class UserSettingsGeneralTab extends React.Component {
}
}
- inputs.push(
- <div key='emailSetting'>
- <div className='form-group'>
- <label className='col-sm-5 control-label'>{'Primary Email'}</label>
- <div className='col-sm-7'>
- <input
- className='form-control'
- type='text'
- onChange={this.updateEmail}
- value={this.state.email}
- />
+ let submit = null;
+
+ if (this.props.user.auth_service === '') {
+ inputs.push(
+ <div key='emailSetting'>
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>{'Primary Email'}</label>
+ <div className='col-sm-7'>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateEmail}
+ value={this.state.email}
+ />
+ </div>
</div>
</div>
- </div>
- );
-
- inputs.push(
- <div key='confirmEmailSetting'>
- <div className='form-group'>
- <label className='col-sm-5 control-label'>{'Confirm Email'}</label>
- <div className='col-sm-7'>
- <input
- className='form-control'
- type='text'
- onChange={this.updateConfirmEmail}
- value={this.state.confirmEmail}
- />
+ );
+
+ inputs.push(
+ <div key='confirmEmailSetting'>
+ <div className='form-group'>
+ <label className='col-sm-5 control-label'>{'Confirm Email'}</label>
+ <div className='col-sm-7'>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateConfirmEmail}
+ value={this.state.confirmEmail}
+ />
+ </div>
</div>
+ {helpText}
</div>
- {helpText}
- </div>
- );
+ );
+
+ submit = this.submitEmail;
+ } else {
+ inputs.push(
+ <div
+ key='oauthEmailInfo'
+ className='form-group'
+ >
+ <div className='setting-list__hint'>{'Log in occurs through GitLab. Email cannot be updated.'}</div>
+ {helpText}
+ </div>
+ );
+ }
emailSection = (
<SettingItemMax
title='Email'
inputs={inputs}
- submit={this.submitEmail}
+ submit={submit}
server_error={serverError}
client_error={emailError}
updateSection={function clearSection(e) {
@@ -499,15 +515,19 @@ export default class UserSettingsGeneralTab extends React.Component {
);
} else {
let describe = '';
- if (this.state.emailChangeInProgress) {
- const newEmail = UserStore.getCurrentUser().email;
- if (newEmail) {
- describe = 'New Address: ' + newEmail + '\nCheck your email to verify the above address.';
+ if (this.props.user.auth_service === '') {
+ if (this.state.emailChangeInProgress) {
+ const newEmail = UserStore.getCurrentUser().email;
+ if (newEmail) {
+ describe = 'New Address: ' + newEmail + '\nCheck your email to verify the above address.';
+ } else {
+ describe = 'Check your email to verify your new address';
+ }
} else {
- describe = 'Check your email to verify your new address';
+ describe = UserStore.getCurrentUser().email;
}
} else {
- describe = UserStore.getCurrentUser().email;
+ describe = 'Log in done through GitLab';
}
emailSection = (
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index 75fb8aa3c..8e86ce32f 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -24,11 +24,17 @@ class BrowserStoreClass {
this.setLastServerVersion = this.setLastServerVersion.bind(this);
this.clear = this.clear.bind(this);
this.clearAll = this.clearAll.bind(this);
+ this.checkedLocalStorageSupported = '';
+ this.signalLogout = this.signalLogout.bind(this);
var currentVersion = sessionStorage.getItem('storage_version');
if (currentVersion !== global.window.mm_config.Version) {
sessionStorage.clear();
- sessionStorage.setItem('storage_version', global.window.mm_config.Version);
+ try {
+ sessionStorage.setItem('storage_version', global.window.mm_config.Version);
+ } catch (e) {
+ // Do nothing
+ }
}
}
@@ -105,6 +111,13 @@ class BrowserStoreClass {
sessionStorage.setItem('last_server_version', version);
}
+ signalLogout() {
+ if (this.isLocalStorageSupported()) {
+ localStorage.setItem('__logout__', 'yes');
+ localStorage.removeItem('__logout__');
+ }
+ }
+
/**
* Preforms the given action on each item that has the given prefix
* Signature for action is action(key, value)
@@ -147,20 +160,26 @@ class BrowserStoreClass {
}
isLocalStorageSupported() {
+ if (this.checkedLocalStorageSupported !== '') {
+ return this.checkedLocalStorageSupported;
+ }
+
try {
- sessionStorage.setItem('testSession', '1');
- sessionStorage.removeItem('testSession');
+ sessionStorage.setItem('__testSession__', '1');
+ sessionStorage.removeItem('__testSession__');
- localStorage.setItem('testLocal', '1');
- if (localStorage.getItem('testLocal') !== '1') {
- return false;
+ localStorage.setItem('__testLocal__', '1');
+ if (localStorage.getItem('__testLocal__') !== '1') {
+ this.checkedLocalStorageSupported = false;
}
- localStorage.removeItem('testLocal', '1');
+ localStorage.removeItem('__testLocal__', '1');
- return true;
+ this.checkedLocalStorageSupported = true;
} catch (e) {
- return false;
+ this.checkedLocalStorageSupported = false;
}
+
+ return this.checkedLocalStorageSupported;
}
}
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 4fa7224b7..6b7d671fc 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -58,6 +58,8 @@ class UserStoreClass extends EventEmitter {
this.setStatus = this.setStatus.bind(this);
this.getStatuses = this.getStatuses.bind(this);
this.getStatus = this.getStatus.bind(this);
+
+ this.profileCache = null;
}
emitChange(userId) {
@@ -184,6 +186,10 @@ class UserStoreClass extends EventEmitter {
}
getProfiles() {
+ if (this.profileCache !== null) {
+ return this.profileCache;
+ }
+
return BrowserStore.getItem('profiles', {});
}
@@ -218,6 +224,7 @@ class UserStoreClass extends EventEmitter {
saveProfile(profile) {
var ps = this.getProfiles();
ps[profile.id] = profile;
+ this.profileCache = ps;
BrowserStore.setItem('profiles', ps);
}
@@ -226,6 +233,8 @@ class UserStoreClass extends EventEmitter {
if (currentId in profiles) {
delete profiles[currentId];
}
+
+ this.profileCache = profiles;
BrowserStore.setItem('profiles', profiles);
}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 003e24d33..d27fe16cf 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -231,6 +231,7 @@ export function resetPassword(data, success, error) {
export function logout() {
track('api', 'api_users_logout');
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
+ BrowserStore.signalLogout();
BrowserStore.clear();
ErrorStore.storeLastError(null);
window.location.href = currentTeamUrl + '/logout';
diff --git a/web/react/utils/highlight.jsx b/web/react/utils/highlight.jsx
new file mode 100644
index 000000000..68fef7930
--- /dev/null
+++ b/web/react/utils/highlight.jsx
@@ -0,0 +1,51 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const highlightJs = require('highlight.js/lib/highlight.js');
+const highlightJsDiff = require('highlight.js/lib/languages/diff.js');
+const highlightJsApache = require('highlight.js/lib/languages/apache.js');
+const highlightJsMakefile = require('highlight.js/lib/languages/makefile.js');
+const highlightJsHttp = require('highlight.js/lib/languages/http.js');
+const highlightJsJson = require('highlight.js/lib/languages/json.js');
+const highlightJsMarkdown = require('highlight.js/lib/languages/markdown.js');
+const highlightJsJavascript = require('highlight.js/lib/languages/javascript.js');
+const highlightJsCss = require('highlight.js/lib/languages/css.js');
+const highlightJsNginx = require('highlight.js/lib/languages/nginx.js');
+const highlightJsObjectivec = require('highlight.js/lib/languages/objectivec.js');
+const highlightJsPython = require('highlight.js/lib/languages/python.js');
+const highlightJsXml = require('highlight.js/lib/languages/xml.js');
+const highlightJsPerl = require('highlight.js/lib/languages/perl.js');
+const highlightJsBash = require('highlight.js/lib/languages/bash.js');
+const highlightJsPhp = require('highlight.js/lib/languages/php.js');
+const highlightJsCoffeescript = require('highlight.js/lib/languages/coffeescript.js');
+const highlightJsCs = require('highlight.js/lib/languages/cs.js');
+const highlightJsCpp = require('highlight.js/lib/languages/cpp.js');
+const highlightJsSql = require('highlight.js/lib/languages/sql.js');
+const highlightJsGo = require('highlight.js/lib/languages/go.js');
+const highlightJsRuby = require('highlight.js/lib/languages/ruby.js');
+const highlightJsJava = require('highlight.js/lib/languages/java.js');
+const highlightJsIni = require('highlight.js/lib/languages/ini.js');
+
+highlightJs.registerLanguage('diff', highlightJsDiff);
+highlightJs.registerLanguage('apache', highlightJsApache);
+highlightJs.registerLanguage('makefile', highlightJsMakefile);
+highlightJs.registerLanguage('http', highlightJsHttp);
+highlightJs.registerLanguage('json', highlightJsJson);
+highlightJs.registerLanguage('markdown', highlightJsMarkdown);
+highlightJs.registerLanguage('javascript', highlightJsJavascript);
+highlightJs.registerLanguage('css', highlightJsCss);
+highlightJs.registerLanguage('nginx', highlightJsNginx);
+highlightJs.registerLanguage('objectivec', highlightJsObjectivec);
+highlightJs.registerLanguage('python', highlightJsPython);
+highlightJs.registerLanguage('xml', highlightJsXml);
+highlightJs.registerLanguage('perl', highlightJsPerl);
+highlightJs.registerLanguage('bash', highlightJsBash);
+highlightJs.registerLanguage('php', highlightJsPhp);
+highlightJs.registerLanguage('coffeescript', highlightJsCoffeescript);
+highlightJs.registerLanguage('cs', highlightJsCs);
+highlightJs.registerLanguage('cpp', highlightJsCpp);
+highlightJs.registerLanguage('sql', highlightJsSql);
+highlightJs.registerLanguage('go', highlightJsGo);
+highlightJs.registerLanguage('ruby', highlightJsRuby);
+highlightJs.registerLanguage('java', highlightJsJava);
+highlightJs.registerLanguage('ini', highlightJsIni);
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index 3ef09211f..374caf6dc 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -1,38 +1,14 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+require('./highlight.jsx');
const TextFormatting = require('./text_formatting.jsx');
const Utils = require('./utils.jsx');
+const highlightJs = require('highlight.js/lib/highlight.js');
const marked = require('marked');
-const highlightJs = require('highlight.js/lib/highlight.js');
-const highlightJsDiff = require('highlight.js/lib/languages/diff.js');
-const highlightJsApache = require('highlight.js/lib/languages/apache.js');
-const highlightJsMakefile = require('highlight.js/lib/languages/makefile.js');
-const highlightJsHttp = require('highlight.js/lib/languages/http.js');
-const highlightJsJson = require('highlight.js/lib/languages/json.js');
-const highlightJsMarkdown = require('highlight.js/lib/languages/markdown.js');
-const highlightJsJavascript = require('highlight.js/lib/languages/javascript.js');
-const highlightJsCss = require('highlight.js/lib/languages/css.js');
-const highlightJsNginx = require('highlight.js/lib/languages/nginx.js');
-const highlightJsObjectivec = require('highlight.js/lib/languages/objectivec.js');
-const highlightJsPython = require('highlight.js/lib/languages/python.js');
-const highlightJsXml = require('highlight.js/lib/languages/xml.js');
-const highlightJsPerl = require('highlight.js/lib/languages/perl.js');
-const highlightJsBash = require('highlight.js/lib/languages/bash.js');
-const highlightJsPhp = require('highlight.js/lib/languages/php.js');
-const highlightJsCoffeescript = require('highlight.js/lib/languages/coffeescript.js');
-const highlightJsCs = require('highlight.js/lib/languages/cs.js');
-const highlightJsCpp = require('highlight.js/lib/languages/cpp.js');
-const highlightJsSql = require('highlight.js/lib/languages/sql.js');
-const highlightJsGo = require('highlight.js/lib/languages/go.js');
-const highlightJsRuby = require('highlight.js/lib/languages/ruby.js');
-const highlightJsJava = require('highlight.js/lib/languages/java.js');
-const highlightJsIni = require('highlight.js/lib/languages/ini.js');
-
-const Constants = require('../utils/constants.jsx');
-const HighlightedLanguages = Constants.HighlightedLanguages;
+const HighlightedLanguages = require('../utils/constants.jsx').HighlightedLanguages;
function markdownImageLoaded(image) {
image.style.height = 'auto';
@@ -84,30 +60,6 @@ class MattermostMarkdownRenderer extends marked.Renderer {
this.text = this.text.bind(this);
this.formattingOptions = formattingOptions;
-
- highlightJs.registerLanguage('diff', highlightJsDiff);
- highlightJs.registerLanguage('apache', highlightJsApache);
- highlightJs.registerLanguage('makefile', highlightJsMakefile);
- highlightJs.registerLanguage('http', highlightJsHttp);
- highlightJs.registerLanguage('json', highlightJsJson);
- highlightJs.registerLanguage('markdown', highlightJsMarkdown);
- highlightJs.registerLanguage('javascript', highlightJsJavascript);
- highlightJs.registerLanguage('css', highlightJsCss);
- highlightJs.registerLanguage('nginx', highlightJsNginx);
- highlightJs.registerLanguage('objectivec', highlightJsObjectivec);
- highlightJs.registerLanguage('python', highlightJsPython);
- highlightJs.registerLanguage('xml', highlightJsXml);
- highlightJs.registerLanguage('perl', highlightJsPerl);
- highlightJs.registerLanguage('bash', highlightJsBash);
- highlightJs.registerLanguage('php', highlightJsPhp);
- highlightJs.registerLanguage('coffeescript', highlightJsCoffeescript);
- highlightJs.registerLanguage('cs', highlightJsCs);
- highlightJs.registerLanguage('cpp', highlightJsCpp);
- highlightJs.registerLanguage('sql', highlightJsSql);
- highlightJs.registerLanguage('go', highlightJsGo);
- highlightJs.registerLanguage('ruby', highlightJsRuby);
- highlightJs.registerLanguage('java', highlightJsJava);
- highlightJs.registerLanguage('ini', highlightJsIni);
}
code(code, language) {
@@ -204,6 +156,301 @@ class MattermostMarkdownRenderer extends marked.Renderer {
}
}
+class MattermostLexer extends marked.Lexer {
+ token(originalSrc, top, bq) {
+ let src = originalSrc.replace(/^ +$/gm, '');
+
+ while (src) {
+ // newline
+ let cap = this.rules.newline.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ if (cap[0].length > 1) {
+ this.tokens.push({
+ type: 'space'
+ });
+ }
+ }
+
+ // code
+ cap = this.rules.code.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ cap = cap[0].replace(/^ {4}/gm, '');
+ this.tokens.push({
+ type: 'code',
+ text: this.options.pedantic ? cap : cap.replace(/\n+$/, '')
+ });
+ continue;
+ }
+
+ // fences (gfm)
+ cap = this.rules.fences.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'code',
+ lang: cap[2],
+ text: cap[3] || ''
+ });
+ continue;
+ }
+
+ // heading
+ cap = this.rules.heading.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[1].length,
+ text: cap[2]
+ });
+ continue;
+ }
+
+ // table no leading pipe (gfm)
+ cap = this.rules.nptable.exec(src);
+ if (top && cap) {
+ src = src.substring(cap[0].length);
+
+ const item = {
+ type: 'table',
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3].replace(/\n$/, '').split('\n')
+ };
+
+ for (let i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (let i = 0; i < item.cells.length; i++) {
+ item.cells[i] = item.cells[i].split(/ *\| */);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+
+ // lheading
+ cap = this.rules.lheading.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'heading',
+ depth: cap[2] === '=' ? 1 : 2,
+ text: cap[1]
+ });
+ continue;
+ }
+
+ // hr
+ cap = this.rules.hr.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'hr'
+ });
+ continue;
+ }
+
+ // blockquote
+ cap = this.rules.blockquote.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+
+ this.tokens.push({
+ type: 'blockquote_start'
+ });
+
+ cap = cap[0].replace(/^ *> ?/gm, '');
+
+ // Pass `top` to keep the current
+ // "toplevel" state. This is exactly
+ // how markdown.pl works.
+ this.token(cap, top, true);
+
+ this.tokens.push({
+ type: 'blockquote_end'
+ });
+
+ continue;
+ }
+
+ // list
+ cap = this.rules.list.exec(src);
+ if (cap) {
+ const bull = cap[2];
+ let l = cap[0].length;
+
+ // Get each top-level item.
+ cap = cap[0].match(this.rules.item);
+
+ if (cap.length > 1) {
+ src = src.substring(l);
+
+ this.tokens.push({
+ type: 'list_start',
+ ordered: bull.length > 1
+ });
+
+ let next = false;
+ l = cap.length;
+
+ for (let i = 0; i < l; i++) {
+ let item = cap[i];
+
+ // Remove the list item's bullet
+ // so it is seen as the next token.
+ let space = item.length;
+ item = item.replace(/^ *([*+-]|\d+\.) +/, '');
+
+ // Outdent whatever the
+ // list item contains. Hacky.
+ if (~item.indexOf('\n ')) {
+ space -= item.length;
+ item = this.options.pedantic ? item.replace(/^ {1,4}/gm, '') : item.replace(new RegExp('^ \{1,' + space + '\}', 'gm'), '');
+ }
+
+ // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+ if (this.options.smartLists && i !== l - 1) {
+ const bullet = /(?:[*+-]|\d+\.)/;
+ const b = bullet.exec(cap[i + 1])[0];
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+ src = cap.slice(i + 1).join('\n') + src;
+ i = l - 1;
+ }
+ }
+
+ // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+ let loose = next || (/\n\n(?!\s*$)/).test(item);
+ if (i !== l - 1) {
+ next = item.charAt(item.length - 1) === '\n';
+ if (!loose) {
+ loose = next;
+ }
+ }
+
+ this.tokens.push({
+ type: loose ? 'loose_item_start' : 'list_item_start'
+ });
+
+ // Recurse.
+ this.token(item, false, bq);
+
+ this.tokens.push({
+ type: 'list_item_end'
+ });
+ }
+
+ this.tokens.push({
+ type: 'list_end'
+ });
+
+ continue;
+ }
+ }
+
+ // html
+ cap = this.rules.html.exec(src);
+ if (cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: this.options.sanitize ? 'paragraph' : 'html',
+ pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+ text: cap[0]
+ });
+ continue;
+ }
+
+ // def
+ cap = this.rules.def.exec(src);
+ if ((!bq && top) && cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.links[cap[1].toLowerCase()] = {
+ href: cap[2],
+ title: cap[3]
+ };
+ continue;
+ }
+
+ // table (gfm)
+ cap = this.rules.table.exec(src);
+ if (top && cap) {
+ src = src.substring(cap[0].length);
+
+ const item = {
+ type: 'table',
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
+ cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
+ };
+
+ for (let i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = 'right';
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = 'center';
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = 'left';
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (let i = 0; i < item.cells.length; i++) {
+ item.cells[i] = item.cells[i].replace(/^ *\| *| *\| *$/g, '').split(/ *\| */);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+
+ // top-level paragraph
+ cap = this.rules.paragraph.exec(src);
+ if (top && cap) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'paragraph',
+ text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
+ });
+ continue;
+ }
+
+ // text
+ cap = this.rules.text.exec(src);
+ if (cap) {
+ // Top-level should never reach here.
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: 'text',
+ text: cap[0]
+ });
+ continue;
+ }
+
+ if (src) {
+ throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
+ }
+ }
+
+ return this.tokens;
+ }
+}
+
export function format(text, options) {
const markdownOptions = {
renderer: new MattermostMarkdownRenderer(null, options),
@@ -212,7 +459,7 @@ export function format(text, options) {
tables: true
};
- const tokens = marked.lexer(text, markdownOptions);
+ const tokens = new MattermostLexer(markdownOptions).lex(text);
return new MattermostParser(markdownOptions).parse(tokens);
}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 575b6d011..8052c000c 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -151,10 +151,14 @@ export function notifyMe(title, body, channel) {
}
}
+var canDing = true;
+
export function ding() {
- if (!isBrowserFirefox()) {
- var audio = new Audio('/static/images/ding.mp3');
+ if (!isBrowserFirefox() && canDing) {
+ var audio = new Audio('/static/images/bing.mp3');
audio.play();
+ canDing = false;
+ setTimeout(() => canDing = true, 3000);
}
}
@@ -477,6 +481,7 @@ export function applyTheme(theme) {
changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarHeaderBg, 1);
+ changeCss('.attachment .attachment__container', 'border-left-color:' + theme.sidebarHeaderBg, 1);
}
if (theme.sidebarHeaderTextColor) {
@@ -515,6 +520,7 @@ export function applyTheme(theme) {
changeCss('.popover.left>.arrow:after', 'border-left-color:' + theme.centerChannelBg, 1);
changeCss('.popover.top>.arrow:after, .tip-overlay.tip-overlay--chat .arrow', 'border-top-color:' + theme.centerChannelBg, 1);
changeCss('.search-bar__container .search__form .search-bar, .form-control', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.attachment__content', 'background:' + theme.centerChannelBg, 1);
}
if (theme.centerChannelColor) {
@@ -548,6 +554,7 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1);
changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
+ changeCss('.attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro .channel-intro__content, .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
changeCss('.date-separator .separator__hr, .modal-footer, .modal .custom-textarea, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
@@ -1125,3 +1132,12 @@ export function sortByDisplayName(a, b) {
}
return 0;
}
+
+export function getChannelTerm(channelType) {
+ let channelTerm = 'Channel';
+ if (channelType === Constants.PRIVATE_CHANNEL) {
+ channelTerm = 'Group';
+ }
+
+ return channelTerm;
+}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 852d19a29..6270c8608 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -174,7 +174,7 @@
td {
width: 100%;
white-space: nowrap;
- overflow: hidden;
+ @include legacy-pie-clearfix;
text-overflow: ellipsis;
padding: 8px 8px 8px 15px;
&.td--action {
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index ad31e3eb6..b57c51242 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -182,16 +182,16 @@ body.ios {
}
#post-list {
- flex: 1 1 auto;
- position: relative;
- overflow-y: hidden;
+ flex: 1 1 auto;
+ position: relative;
+ overflow-y: hidden;
.post-list-holder-by-time {
background: #fff;
overflow-y: scroll;
width: 100%;
padding: 1em 0 0;
position: absolute;
- height: 100%;
+ height: 100%;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
width: 0px !important;
@@ -212,6 +212,20 @@ body.ios {
.post-list__content {
display: table-cell;
vertical-align: bottom;
+ div {
+ &:last-child {
+ .post {
+ .post-header {
+ .post-header-col.post-header__reply {
+ .dropdown-menu {
+ top: auto;
+ bottom: 25px;
+ }
+ }
+ }
+ }
+ }
+ }
}
}
.more-messages-text {
@@ -387,16 +401,6 @@ body.ios {
}
}
}
- &.post--last {
- .post-header {
- .post-header-col.post-header__reply {
- .dropdown-menu {
- top: auto;
- bottom: 25px;
- }
- }
- }
- }
.post-create-footer {
padding: 0;
}
@@ -445,10 +449,10 @@ body.ios {
&.post-profile-img__container {
float: left;
.post-profile-img {
- width: 36px;
- height: 36px;
+ width: 36px;
+ height: 36px;
margin-right: 10px;
- vertical-align: inherit;
+ vertical-align: inherit;
@include border-radius(50px);
}
}
@@ -606,3 +610,81 @@ body.ios {
font-weight: 600;
margin: 0 0 0 -4px;
}
+
+.attachment {
+ .attachment__content {
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 4px;
+ padding: 2px 5px;
+ margin: 0 0 5px 0;
+ }
+ .attachment__thumb-pretext {
+ border: 0 none;
+ background: transparent;
+ }
+ .attachment__container {
+ border-left-width: 4px;
+ border-left-style: solid;
+ padding: 2px 0 2px 10px;
+ &.attachment__container--good {
+ border-left-color: #00C100;
+ }
+ &.attachment__container--warning {
+ border-left-color: #DEDE01;
+ }
+ &.attachment__container--danger {
+ border-left-color: #E40303;
+ }
+ }
+ .attachment__body {
+ float: left;
+ width: 80%;
+ padding-right: 5px;
+ &.attachment__body--no_thumb {
+ width: 100%;
+ }
+ }
+ .attachment__text p:last-of-type {
+ display: inline-block;
+ }
+ .attachment__thumb-pretext {
+ margin-left: 5px;
+ }
+ .attachment__title {
+ margin: 5px 0;
+ padding: 0;
+ line-height: 16px;
+ font-size: 16px;
+ a {
+ font-size: 16px;
+ }
+ }
+ .attachment__author-icon {
+ @include border-radius(50px);
+ margin-right: 5px;
+ width: 14px;
+ height: 14px;
+ }
+ .attachment__image {
+ max-width: 100%;
+ margin-bottom: 1em;
+ }
+ .attachment__thumb-container {
+ width: 20%;
+ float: right;
+ img {
+ height: 75px;
+ max-width: 100%;
+ }
+ }
+ .attachment___fields {
+ width: 100%;
+ .attachment___field-caption {
+ font-weight: 700;
+ }
+ .attachment___field p {
+ margin: 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/web/static/images/bing.mp3 b/web/static/images/bing.mp3
new file mode 100644
index 000000000..2827addd4
--- /dev/null
+++ b/web/static/images/bing.mp3
Binary files differ
diff --git a/web/static/js/perfect-scrollbar-0.6.7.jquery.js b/web/static/js/perfect-scrollbar-0.6.7.jquery.js
index 9e93de017..6c25fa91f 100644
--- a/web/static/js/perfect-scrollbar-0.6.7.jquery.js
+++ b/web/static/js/perfect-scrollbar-0.6.7.jquery.js
@@ -443,10 +443,11 @@ function bindClickRailHandler(element, i) {
function pageOffset(el) {
return el.getBoundingClientRect();
}
- var stopPropagation = window.Event.prototype.stopPropagation.bind;
if (i.settings.stopPropagationOnClick) {
- i.event.bind(i.scrollbarY, 'click', stopPropagation);
+ i.event.bind(i.scrollbarY, 'click', function (e) {
+ e.stopPropagation();
+ });
}
i.event.bind(i.scrollbarYRail, 'click', function (e) {
var halfOfScrollbarLength = h.toInt(i.scrollbarYHeight / 2);
@@ -467,7 +468,9 @@ function bindClickRailHandler(element, i) {
});
if (i.settings.stopPropagationOnClick) {
- i.event.bind(i.scrollbarX, 'click', stopPropagation);
+ i.event.bind(i.scrollbarY, 'click', function (e) {
+ e.stopPropagation();
+ });
}
i.event.bind(i.scrollbarXRail, 'click', function (e) {
var halfOfScrollbarLength = h.toInt(i.scrollbarXWidth / 2);
diff --git a/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js b/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js
index ade0c1836..8b0f0056c 100644
--- a/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js
+++ b/web/static/js/perfect-scrollbar-0.6.7.jquery.min.js
@@ -1,2 +1,2 @@
/* perfect-scrollbar v0.6.7 */
-!function t(e,n,r){function o(l,s){if(!n[l]){if(!e[l]){var a="function"==typeof require&&require;if(!s&&a)return a(l,!0);if(i)return i(l,!0);var c=new Error("Cannot find module '"+l+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[l]={exports:{}};e[l][0].call(u.exports,function(t){var n=e[l][1][t];return o(n?n:t)},u,u.exports,t,e,n,r)}return n[l].exports}for(var i="function"==typeof require&&require,l=0;l<r.length;l++)o(r[l]);return o}({1:[function(t,e,n){"use strict";function r(t){t.fn.perfectScrollbar=function(e){return this.each(function(){if("object"==typeof e||"undefined"==typeof e){var n=e;i.get(this)||o.initialize(this,n)}else{var r=e;"update"===r?o.update(this):"destroy"===r&&o.destroy(this)}return t(this)})}}var o=t("../main"),i=t("../plugin/instances");if("function"==typeof define&&define.amd)define(["jquery"],r);else{var l=window.jQuery?window.jQuery:window.$;"undefined"!=typeof l&&r(l)}e.exports=r},{"../main":7,"../plugin/instances":18}],2:[function(t,e,n){"use strict";function r(t,e){var n=t.className.split(" ");n.indexOf(e)<0&&n.push(e),t.className=n.join(" ")}function o(t,e){var n=t.className.split(" "),r=n.indexOf(e);r>=0&&n.splice(r,1),t.className=n.join(" ")}n.add=function(t,e){t.classList?t.classList.add(e):r(t,e)},n.remove=function(t,e){t.classList?t.classList.remove(e):o(t,e)},n.list=function(t){return t.classList?Array.prototype.slice.apply(t.classList):t.className.split(" ")}},{}],3:[function(t,e,n){"use strict";function r(t,e){return window.getComputedStyle(t)[e]}function o(t,e,n){return"number"==typeof n&&(n=n.toString()+"px"),t.style[e]=n,t}function i(t,e){for(var n in e){var r=e[n];"number"==typeof r&&(r=r.toString()+"px"),t.style[n]=r}return t}var l={};l.e=function(t,e){var n=document.createElement(t);return n.className=e,n},l.appendTo=function(t,e){return e.appendChild(t),t},l.css=function(t,e,n){return"object"==typeof e?i(t,e):"undefined"==typeof n?r(t,e):o(t,e,n)},l.matches=function(t,e){return"undefined"!=typeof t.matches?t.matches(e):"undefined"!=typeof t.matchesSelector?t.matchesSelector(e):"undefined"!=typeof t.webkitMatchesSelector?t.webkitMatchesSelector(e):"undefined"!=typeof t.mozMatchesSelector?t.mozMatchesSelector(e):"undefined"!=typeof t.msMatchesSelector?t.msMatchesSelector(e):void 0},l.remove=function(t){"undefined"!=typeof t.remove?t.remove():t.parentNode&&t.parentNode.removeChild(t)},l.queryChildren=function(t,e){return Array.prototype.filter.call(t.childNodes,function(t){return l.matches(t,e)})},e.exports=l},{}],4:[function(t,e,n){"use strict";var r=function(t){this.element=t,this.events={}};r.prototype.bind=function(t,e){"undefined"==typeof this.events[t]&&(this.events[t]=[]),this.events[t].push(e),this.element.addEventListener(t,e,!1)},r.prototype.unbind=function(t,e){var n="undefined"!=typeof e;this.events[t]=this.events[t].filter(function(r){return n&&r!==e?!0:(this.element.removeEventListener(t,r,!1),!1)},this)},r.prototype.unbindAll=function(){for(var t in this.events)this.unbind(t)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return"undefined"==typeof e&&(e=new r(t),this.eventElements.push(e)),e},o.prototype.bind=function(t,e,n){this.eventElement(t).bind(e,n)},o.prototype.unbind=function(t,e,n){this.eventElement(t).unbind(e,n)},o.prototype.unbindAll=function(){for(var t=0;t<this.eventElements.length;t++)this.eventElements[t].unbindAll()},o.prototype.once=function(t,e,n){var r=this.eventElement(t),o=function(t){r.unbind(e,o),n(t)};r.bind(e,o)},e.exports=o},{}],5:[function(t,e,n){"use strict";e.exports=function(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}}()},{}],6:[function(t,e,n){"use strict";var r=t("./class"),o=t("./dom");n.toInt=function(t){return parseInt(t,10)||0},n.clone=function(t){if(null===t)return null;if("object"==typeof t){var e={};for(var n in t)e[n]=this.clone(t[n]);return e}return t},n.extend=function(t,e){var n=this.clone(t);for(var r in e)n[r]=this.clone(e[r]);return n},n.isEditable=function(t){return o.matches(t,"input,[contenteditable]")||o.matches(t,"select,[contenteditable]")||o.matches(t,"textarea,[contenteditable]")||o.matches(t,"button,[contenteditable]")},n.removePsClasses=function(t){for(var e=r.list(t),n=0;n<e.length;n++){var o=e[n];0===o.indexOf("ps-")&&r.remove(t,o)}},n.outerWidth=function(t){return this.toInt(o.css(t,"width"))+this.toInt(o.css(t,"paddingLeft"))+this.toInt(o.css(t,"paddingRight"))+this.toInt(o.css(t,"borderLeftWidth"))+this.toInt(o.css(t,"borderRightWidth"))},n.startScrolling=function(t,e){r.add(t,"ps-in-scrolling"),"undefined"!=typeof e?r.add(t,"ps-"+e):(r.add(t,"ps-x"),r.add(t,"ps-y"))},n.stopScrolling=function(t,e){r.remove(t,"ps-in-scrolling"),"undefined"!=typeof e?r.remove(t,"ps-"+e):(r.remove(t,"ps-x"),r.remove(t,"ps-y"))},n.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(t,e,n){"use strict";var r=t("./plugin/destroy"),o=t("./plugin/initialize"),i=t("./plugin/update");e.exports={initialize:o,update:i,destroy:r}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(t,e,n){"use strict";e.exports={maxScrollbarLength:null,minScrollbarLength:null,scrollXMarginOffset:0,scrollYMarginOffset:0,stopPropagationOnClick:!0,suppressScrollX:!1,suppressScrollY:!1,swipePropagation:!0,useBothWheelAxes:!1,useKeyboard:!0,useSelectionScroll:!1,wheelPropagation:!1,wheelSpeed:1}},{}],9:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances");e.exports=function(t){var e=i.get(t);e&&(e.event.unbindAll(),r.remove(e.scrollbarX),r.remove(e.scrollbarY),r.remove(e.scrollbarXRail),r.remove(e.scrollbarYRail),o.removePsClasses(t),i.remove(t))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(t,e,n){"use strict";function r(t,e){function n(t){return t.getBoundingClientRect()}var r=window.Event.prototype.stopPropagation.bind;e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarY,"click",r),e.event.bind(e.scrollbarYRail,"click",function(r){var i=o.toInt(e.scrollbarYHeight/2),a=e.railYRatio*(r.pageY-window.scrollY-n(e.scrollbarYRail).top-i),c=e.railYRatio*(e.railYHeight-e.scrollbarYHeight),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"top",(e.contentHeight-e.containerHeight)*u),l(t),r.stopPropagation()}),e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarX,"click",r),e.event.bind(e.scrollbarXRail,"click",function(r){var i=o.toInt(e.scrollbarXWidth/2),a=e.railXRatio*(r.pageX-window.scrollX-n(e.scrollbarXRail).left-i),c=e.railXRatio*(e.railXWidth-e.scrollbarXWidth),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"left",(e.contentWidth-e.containerWidth)*u-e.negativeScrollAdjustment),l(t),r.stopPropagation()})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(t,e,n){"use strict";function r(t,e){function n(n){var o=r+n*e.railXRatio,i=e.scrollbarXRail.getBoundingClientRect().left+e.railXRatio*(e.railXWidth-e.scrollbarXWidth);0>o?e.scrollbarXLeft=0:o>i?e.scrollbarXLeft=i:e.scrollbarXLeft=o;var s=l.toInt(e.scrollbarXLeft*(e.contentWidth-e.containerWidth)/(e.containerWidth-e.railXRatio*e.scrollbarXWidth))-e.negativeScrollAdjustment;c(t,"left",s)}var r=null,o=null,s=function(e){n(e.pageX-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"x"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarX,"mousedown",function(n){o=n.pageX,r=l.toInt(i.css(e.scrollbarX,"left"))*e.railXRatio,l.startScrolling(t,"x"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}function o(t,e){function n(n){var o=r+n*e.railYRatio,i=e.scrollbarYRail.getBoundingClientRect().top+e.railYRatio*(e.railYHeight-e.scrollbarYHeight);0>o?e.scrollbarYTop=0:o>i?e.scrollbarYTop=i:e.scrollbarYTop=o;var s=l.toInt(e.scrollbarYTop*(e.contentHeight-e.containerHeight)/(e.containerHeight-e.railYRatio*e.scrollbarYHeight));c(t,"top",s)}var r=null,o=null,s=function(e){n(e.pageY-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"y"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarY,"mousedown",function(n){o=n.pageY,r=l.toInt(i.css(e.scrollbarY,"top"))*e.railYRatio,l.startScrolling(t,"y"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}var i=t("../../lib/dom"),l=t("../../lib/helper"),s=t("../instances"),a=t("../update-geometry"),c=t("../update-scroll");e.exports=function(t){var e=s.get(t);r(t,e),o(t,e)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}var r=!1;e.event.bind(t,"mouseenter",function(){r=!0}),e.event.bind(t,"mouseleave",function(){r=!1});var i=!1;e.event.bind(e.ownerDocument,"keydown",function(a){if((!a.isDefaultPrevented||!a.isDefaultPrevented())&&r){var c=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(c){for(;c.shadowRoot;)c=c.shadowRoot.activeElement;if(o.isEditable(c))return}var u=0,d=0;switch(a.which){case 37:u=-30;break;case 38:d=30;break;case 39:u=30;break;case 40:d=-30;break;case 33:d=90;break;case 32:d=a.shiftKey?90:-90;break;case 34:d=-90;break;case 35:d=a.ctrlKey?-e.contentHeight:-e.containerHeight;break;case 36:d=a.ctrlKey?t.scrollTop:e.containerHeight;break;default:return}s(t,"top",t.scrollTop-d),s(t,"left",t.scrollLeft+u),l(t),i=n(u,d),i&&a.preventDefault()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}function r(t){var e=t.deltaX,n=-1*t.deltaY;return("undefined"==typeof e||"undefined"==typeof n)&&(e=-1*t.wheelDeltaX/6,n=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,n*=10),e!==e&&n!==n&&(e=0,n=t.wheelDelta),[e,n]}function i(e,n){var r=t.querySelector("textarea:hover");if(r){var o=r.scrollHeight-r.clientHeight;if(o>0&&!(0===r.scrollTop&&n>0||r.scrollTop===o&&0>n))return!0;var i=r.scrollLeft-r.clientWidth;if(i>0&&!(0===r.scrollLeft&&0>e||r.scrollLeft===i&&e>0))return!0}return!1}function a(a){if(o.env.isWebKit||!t.querySelector("select:focus")){var u=r(a),d=u[0],p=u[1];i(d,p)||(c=!1,e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(p?s(t,"top",t.scrollTop-p*e.settings.wheelSpeed):s(t,"top",t.scrollTop+d*e.settings.wheelSpeed),c=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(d?s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed):s(t,"left",t.scrollLeft-p*e.settings.wheelSpeed),c=!0):(s(t,"top",t.scrollTop-p*e.settings.wheelSpeed),s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed)),l(t),c=c||n(d,p),c&&(a.stopPropagation(),a.preventDefault()))}}var c=!1;"undefined"!=typeof window.onwheel?e.event.bind(t,"wheel",a):"undefined"!=typeof window.onmousewheel&&e.event.bind(t,"mousewheel",a)}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(t,e,n){"use strict";function r(t,e){e.event.bind(t,"scroll",function(){i(t)})}var o=t("../instances"),i=t("../update-geometry");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19}],15:[function(t,e,n){"use strict";function r(t,e){function n(){var t=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===t.toString().length?null:t.getRangeAt(0).commonAncestorContainer}function r(){c||(c=setInterval(function(){return i.get(t)?(s(t,"top",t.scrollTop+u.top),s(t,"left",t.scrollLeft+u.left),void l(t)):void clearInterval(c)},50))}function a(){c&&(clearInterval(c),c=null),o.stopScrolling(t)}var c=null,u={top:0,left:0},d=!1;e.event.bind(e.ownerDocument,"selectionchange",function(){t.contains(n())?d=!0:(d=!1,a())}),e.event.bind(window,"mouseup",function(){d&&(d=!1,a())}),e.event.bind(window,"mousemove",function(e){if(d){var n={x:e.pageX,y:e.pageY},i={left:t.offsetLeft,right:t.offsetLeft+t.offsetWidth,top:t.offsetTop,bottom:t.offsetTop+t.offsetHeight};n.x<i.left+3?(u.left=-5,o.startScrolling(t,"x")):n.x>i.right-3?(u.left=5,o.startScrolling(t,"x")):u.left=0,n.y<i.top+3?(i.top+3-n.y<5?u.top=-5:u.top=-20,o.startScrolling(t,"y")):n.y>i.bottom-3?(n.y-i.bottom+3<5?u.top=5:u.top=20,o.startScrolling(t,"y")):u.top=0,0===u.top&&0===u.left?a():r()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(t,e,n){"use strict";function r(t,e,n,r){function s(n,r){var o=t.scrollTop,i=t.scrollLeft,l=Math.abs(n),s=Math.abs(r);if(s>l){if(0>r&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return!e.settings.swipePropagation}else if(l>s&&(0>n&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!e.settings.swipePropagation;return!0}function a(e,n){l(t,"top",t.scrollTop-n),l(t,"left",t.scrollLeft-e),i(t)}function c(){Y=!0}function u(){Y=!1}function d(t){return t.targetTouches?t.targetTouches[0]:t}function p(t){return t.targetTouches&&1===t.targetTouches.length?!0:t.pointerType&&"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE?!0:!1}function f(t){if(p(t)){w=!0;var e=d(t);b.pageX=e.pageX,b.pageY=e.pageY,g=(new Date).getTime(),null!==y&&clearInterval(y),t.stopPropagation()}}function h(t){if(!Y&&w&&p(t)){var e=d(t),n={pageX:e.pageX,pageY:e.pageY},r=n.pageX-b.pageX,o=n.pageY-b.pageY;a(r,o),b=n;var i=(new Date).getTime(),l=i-g;l>0&&(m.x=r/l,m.y=o/l,g=i),s(r,o)&&(t.stopPropagation(),t.preventDefault())}}function v(){!Y&&w&&(w=!1,clearInterval(y),y=setInterval(function(){return o.get(t)?Math.abs(m.x)<.01&&Math.abs(m.y)<.01?void clearInterval(y):(a(30*m.x,30*m.y),m.x*=.8,void(m.y*=.8)):void clearInterval(y)},10))}var b={},g=0,m={},y=null,Y=!1,w=!1;n&&(e.event.bind(window,"touchstart",c),e.event.bind(window,"touchend",u),e.event.bind(t,"touchstart",f),e.event.bind(t,"touchmove",h),e.event.bind(t,"touchend",v)),r&&(window.PointerEvent?(e.event.bind(window,"pointerdown",c),e.event.bind(window,"pointerup",u),e.event.bind(t,"pointerdown",f),e.event.bind(t,"pointermove",h),e.event.bind(t,"pointerup",v)):window.MSPointerEvent&&(e.event.bind(window,"MSPointerDown",c),e.event.bind(window,"MSPointerUp",u),e.event.bind(t,"MSPointerDown",f),e.event.bind(t,"MSPointerMove",h),e.event.bind(t,"MSPointerUp",v)))}var o=t("../instances"),i=t("../update-geometry"),l=t("../update-scroll");e.exports=function(t,e,n){var i=o.get(t);r(t,i,e,n)}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(t,e,n){"use strict";var r=t("../lib/class"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry"),s=t("./handler/click-rail"),a=t("./handler/drag-scrollbar"),c=t("./handler/keyboard"),u=t("./handler/mouse-wheel"),d=t("./handler/native-scroll"),p=t("./handler/selection"),f=t("./handler/touch");e.exports=function(t,e){e="object"==typeof e?e:{},r.add(t,"ps-container");var n=i.add(t);n.settings=o.extend(n.settings,e),s(t),a(t),u(t),d(t),n.settings.useSelectionScroll&&p(t),(o.env.supportsTouch||o.env.supportsIePointer)&&f(t,o.env.supportsTouch,o.env.supportsIePointer),n.settings.useKeyboard&&c(t),l(t)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(t,e,n){"use strict";function r(t){var e=this;e.settings=d.clone(a),e.containerWidth=null,e.containerHeight=null,e.contentWidth=null,e.contentHeight=null,e.isRtl="rtl"===s.css(t,"direction"),e.isNegativeScroll=function(){var e=t.scrollLeft,n=null;return t.scrollLeft=-1,n=t.scrollLeft<0,t.scrollLeft=e,n}(),e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,e.event=new c,e.ownerDocument=t.ownerDocument||document,e.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),t),e.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),e.scrollbarXRail),e.scrollbarXActive=null,e.scrollbarXWidth=null,e.scrollbarXLeft=null,e.scrollbarXBottom=d.toInt(s.css(e.scrollbarXRail,"bottom")),e.isScrollbarXUsingBottom=e.scrollbarXBottom===e.scrollbarXBottom,e.scrollbarXTop=e.isScrollbarXUsingBottom?null:d.toInt(s.css(e.scrollbarXRail,"top")),e.railBorderXWidth=d.toInt(s.css(e.scrollbarXRail,"borderLeftWidth"))+d.toInt(s.css(e.scrollbarXRail,"borderRightWidth")),s.css(e.scrollbarXRail,"display","block"),e.railXMarginWidth=d.toInt(s.css(e.scrollbarXRail,"marginLeft"))+d.toInt(s.css(e.scrollbarXRail,"marginRight")),s.css(e.scrollbarXRail,"display",""),e.railXWidth=null,e.railXRatio=null,e.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),t),e.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),e.scrollbarYRail),e.scrollbarYActive=null,e.scrollbarYHeight=null,e.scrollbarYTop=null,e.scrollbarYRight=d.toInt(s.css(e.scrollbarYRail,"right")),e.isScrollbarYUsingRight=e.scrollbarYRight===e.scrollbarYRight,e.scrollbarYLeft=e.isScrollbarYUsingRight?null:d.toInt(s.css(e.scrollbarYRail,"left")),e.scrollbarYOuterWidth=e.isRtl?d.outerWidth(e.scrollbarY):null,e.railBorderYWidth=d.toInt(s.css(e.scrollbarYRail,"borderTopWidth"))+d.toInt(s.css(e.scrollbarYRail,"borderBottomWidth")),s.css(e.scrollbarYRail,"display","block"),e.railYMarginHeight=d.toInt(s.css(e.scrollbarYRail,"marginTop"))+d.toInt(s.css(e.scrollbarYRail,"marginBottom")),s.css(e.scrollbarYRail,"display",""),e.railYHeight=null,e.railYRatio=null}function o(t){return"undefined"==typeof t.dataset?t.getAttribute("data-ps-id"):t.dataset.psId}function i(t,e){"undefined"==typeof t.dataset?t.setAttribute("data-ps-id",e):t.dataset.psId=e}function l(t){"undefined"==typeof t.dataset?t.removeAttribute("data-ps-id"):delete t.dataset.psId}var s=t("../lib/dom"),a=t("./default-setting"),c=t("../lib/event-manager"),u=t("../lib/guid"),d=t("../lib/helper"),p={};n.add=function(t){var e=u();return i(t,e),p[e]=new r(t),p[e]},n.remove=function(t){delete p[o(t)],l(t)},n.get=function(t){return p[o(t)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(t,e,n){"use strict";function r(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function o(t,e){var n={width:e.railXWidth};e.isRtl?n.left=e.negativeScrollAdjustment+t.scrollLeft+e.containerWidth-e.contentWidth:n.left=t.scrollLeft,e.isScrollbarXUsingBottom?n.bottom=e.scrollbarXBottom-t.scrollTop:n.top=e.scrollbarXTop+t.scrollTop,l.css(e.scrollbarXRail,n);var r={top:t.scrollTop,height:e.railYHeight};e.isScrollbarYUsingRight?e.isRtl?r.right=e.contentWidth-(e.negativeScrollAdjustment+t.scrollLeft)-e.scrollbarYRight-e.scrollbarYOuterWidth:r.right=e.scrollbarYRight-t.scrollLeft:e.isRtl?r.left=e.negativeScrollAdjustment+t.scrollLeft+2*e.containerWidth-e.contentWidth-e.scrollbarYLeft-e.scrollbarYOuterWidth:r.left=e.scrollbarYLeft+t.scrollLeft,l.css(e.scrollbarYRail,r),l.css(e.scrollbarX,{left:e.scrollbarXLeft,width:e.scrollbarXWidth-e.railBorderXWidth}),l.css(e.scrollbarY,{top:e.scrollbarYTop,height:e.scrollbarYHeight-e.railBorderYWidth})}var i=t("../lib/class"),l=t("../lib/dom"),s=t("../lib/helper"),a=t("./instances"),c=t("./update-scroll");e.exports=function(t){var e=a.get(t);e.containerWidth=t.clientWidth,e.containerHeight=t.clientHeight,e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight;var n;t.contains(e.scrollbarXRail)||(n=l.queryChildren(t,".ps-scrollbar-x-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarXRail,t)),t.contains(e.scrollbarYRail)||(n=l.queryChildren(t,".ps-scrollbar-y-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarYRail,t)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset<e.contentWidth?(e.scrollbarXActive=!0,e.railXWidth=e.containerWidth-e.railXMarginWidth,e.railXRatio=e.containerWidth/e.railXWidth,e.scrollbarXWidth=r(e,s.toInt(e.railXWidth*e.containerWidth/e.contentWidth)),e.scrollbarXLeft=s.toInt((e.negativeScrollAdjustment+t.scrollLeft)*(e.railXWidth-e.scrollbarXWidth)/(e.contentWidth-e.containerWidth))):(e.scrollbarXActive=!1,e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=0),!e.settings.suppressScrollY&&e.containerHeight+e.settings.scrollYMarginOffset<e.contentHeight?(e.scrollbarYActive=!0,e.railYHeight=e.containerHeight-e.railYMarginHeight,e.railYRatio=e.containerHeight/e.railYHeight,e.scrollbarYHeight=r(e,s.toInt(e.railYHeight*e.containerHeight/e.contentHeight)),e.scrollbarYTop=s.toInt(t.scrollTop*(e.railYHeight-e.scrollbarYHeight)/(e.contentHeight-e.containerHeight))):(e.scrollbarYActive=!1,e.scrollbarYHeight=0,e.scrollbarYTop=0,c(t,"top",0)),e.scrollbarXLeft>=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),o(t,e),i[e.scrollbarXActive?"add":"remove"](t,"ps-active-x"),i[e.scrollbarYActive?"add":"remove"](t,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(t,e,n){"use strict";var r,o,i=t("./instances"),l=document.createEvent("Event"),s=document.createEvent("Event"),a=document.createEvent("Event"),c=document.createEvent("Event"),u=document.createEvent("Event"),d=document.createEvent("Event"),p=document.createEvent("Event"),f=document.createEvent("Event"),h=document.createEvent("Event"),v=document.createEvent("Event");l.initEvent("ps-scroll-up",!0,!0),s.initEvent("ps-scroll-down",!0,!0),a.initEvent("ps-scroll-left",!0,!0),c.initEvent("ps-scroll-right",!0,!0),u.initEvent("ps-scroll-y",!0,!0),d.initEvent("ps-scroll-x",!0,!0),p.initEvent("ps-x-reach-start",!0,!0),f.initEvent("ps-x-reach-end",!0,!0),h.initEvent("ps-y-reach-start",!0,!0),v.initEvent("ps-y-reach-end",!0,!0),e.exports=function(t,e,n){if("undefined"==typeof t)throw"You must provide an element to the update-scroll function";if("undefined"==typeof e)throw"You must provide an axis to the update-scroll function";if("undefined"==typeof n)throw"You must provide a value to the update-scroll function";if("top"===e&&0>=n)return t.scrollTop=0,void t.dispatchEvent(h);if("left"===e&&0>=n)return t.scrollLeft=0,void t.dispatchEvent(p);var b=i.get(t);return"top"===e&&n>b.contentHeight-b.containerHeight?(t.scrollTop=b.contentHeight-b.containerHeight,void t.dispatchEvent(v)):"left"===e&&n>b.contentWidth-b.containerWidth?(t.scrollLeft=b.contentWidth-b.containerWidth,void t.dispatchEvent(f)):(r||(r=t.scrollTop),o||(o=t.scrollLeft),"top"===e&&r>n&&t.dispatchEvent(l),"top"===e&&n>r&&t.dispatchEvent(s),"left"===e&&o>n&&t.dispatchEvent(a),"left"===e&&n>o&&t.dispatchEvent(c),"top"===e&&(t.scrollTop=r=n,t.dispatchEvent(u)),void("left"===e&&(t.scrollLeft=o=n,t.dispatchEvent(d))))}},{"./instances":18}],21:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry");e.exports=function(t){var e=i.get(t);e&&(e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,r.css(e.scrollbarXRail,"display","block"),r.css(e.scrollbarYRail,"display","block"),e.railXMarginWidth=o.toInt(r.css(e.scrollbarXRail,"marginLeft"))+o.toInt(r.css(e.scrollbarXRail,"marginRight")),e.railYMarginHeight=o.toInt(r.css(e.scrollbarYRail,"marginTop"))+o.toInt(r.css(e.scrollbarYRail,"marginBottom")),r.css(e.scrollbarXRail,"display","none"),r.css(e.scrollbarYRail,"display","none"),l(t),r.css(e.scrollbarXRail,"display",""),r.css(e.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19}]},{},[1]); \ No newline at end of file
+!function t(e,n,r){function o(l,s){if(!n[l]){if(!e[l]){var a="function"==typeof require&&require;if(!s&&a)return a(l,!0);if(i)return i(l,!0);var c=new Error("Cannot find module '"+l+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[l]={exports:{}};e[l][0].call(u.exports,function(t){var n=e[l][1][t];return o(n?n:t)},u,u.exports,t,e,n,r)}return n[l].exports}for(var i="function"==typeof require&&require,l=0;l<r.length;l++)o(r[l]);return o}({1:[function(t,e,n){"use strict";function r(t){t.fn.perfectScrollbar=function(e){return this.each(function(){if("object"==typeof e||"undefined"==typeof e){var n=e;i.get(this)||o.initialize(this,n)}else{var r=e;"update"===r?o.update(this):"destroy"===r&&o.destroy(this)}return t(this)})}}var o=t("../main"),i=t("../plugin/instances");if("function"==typeof define&&define.amd)define(["jquery"],r);else{var l=window.jQuery?window.jQuery:window.$;"undefined"!=typeof l&&r(l)}e.exports=r},{"../main":7,"../plugin/instances":18}],2:[function(t,e,n){"use strict";function r(t,e){var n=t.className.split(" ");n.indexOf(e)<0&&n.push(e),t.className=n.join(" ")}function o(t,e){var n=t.className.split(" "),r=n.indexOf(e);r>=0&&n.splice(r,1),t.className=n.join(" ")}n.add=function(t,e){t.classList?t.classList.add(e):r(t,e)},n.remove=function(t,e){t.classList?t.classList.remove(e):o(t,e)},n.list=function(t){return t.classList?Array.prototype.slice.apply(t.classList):t.className.split(" ")}},{}],3:[function(t,e,n){"use strict";function r(t,e){return window.getComputedStyle(t)[e]}function o(t,e,n){return"number"==typeof n&&(n=n.toString()+"px"),t.style[e]=n,t}function i(t,e){for(var n in e){var r=e[n];"number"==typeof r&&(r=r.toString()+"px"),t.style[n]=r}return t}var l={};l.e=function(t,e){var n=document.createElement(t);return n.className=e,n},l.appendTo=function(t,e){return e.appendChild(t),t},l.css=function(t,e,n){return"object"==typeof e?i(t,e):"undefined"==typeof n?r(t,e):o(t,e,n)},l.matches=function(t,e){return"undefined"!=typeof t.matches?t.matches(e):"undefined"!=typeof t.matchesSelector?t.matchesSelector(e):"undefined"!=typeof t.webkitMatchesSelector?t.webkitMatchesSelector(e):"undefined"!=typeof t.mozMatchesSelector?t.mozMatchesSelector(e):"undefined"!=typeof t.msMatchesSelector?t.msMatchesSelector(e):void 0},l.remove=function(t){"undefined"!=typeof t.remove?t.remove():t.parentNode&&t.parentNode.removeChild(t)},l.queryChildren=function(t,e){return Array.prototype.filter.call(t.childNodes,function(t){return l.matches(t,e)})},e.exports=l},{}],4:[function(t,e,n){"use strict";var r=function(t){this.element=t,this.events={}};r.prototype.bind=function(t,e){"undefined"==typeof this.events[t]&&(this.events[t]=[]),this.events[t].push(e),this.element.addEventListener(t,e,!1)},r.prototype.unbind=function(t,e){var n="undefined"!=typeof e;this.events[t]=this.events[t].filter(function(r){return n&&r!==e?!0:(this.element.removeEventListener(t,r,!1),!1)},this)},r.prototype.unbindAll=function(){for(var t in this.events)this.unbind(t)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return"undefined"==typeof e&&(e=new r(t),this.eventElements.push(e)),e},o.prototype.bind=function(t,e,n){this.eventElement(t).bind(e,n)},o.prototype.unbind=function(t,e,n){this.eventElement(t).unbind(e,n)},o.prototype.unbindAll=function(){for(var t=0;t<this.eventElements.length;t++)this.eventElements[t].unbindAll()},o.prototype.once=function(t,e,n){var r=this.eventElement(t),o=function(t){r.unbind(e,o),n(t)};r.bind(e,o)},e.exports=o},{}],5:[function(t,e,n){"use strict";e.exports=function(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return function(){return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}}()},{}],6:[function(t,e,n){"use strict";var r=t("./class"),o=t("./dom");n.toInt=function(t){return parseInt(t,10)||0},n.clone=function(t){if(null===t)return null;if("object"==typeof t){var e={};for(var n in t)e[n]=this.clone(t[n]);return e}return t},n.extend=function(t,e){var n=this.clone(t);for(var r in e)n[r]=this.clone(e[r]);return n},n.isEditable=function(t){return o.matches(t,"input,[contenteditable]")||o.matches(t,"select,[contenteditable]")||o.matches(t,"textarea,[contenteditable]")||o.matches(t,"button,[contenteditable]")},n.removePsClasses=function(t){for(var e=r.list(t),n=0;n<e.length;n++){var o=e[n];0===o.indexOf("ps-")&&r.remove(t,o)}},n.outerWidth=function(t){return this.toInt(o.css(t,"width"))+this.toInt(o.css(t,"paddingLeft"))+this.toInt(o.css(t,"paddingRight"))+this.toInt(o.css(t,"borderLeftWidth"))+this.toInt(o.css(t,"borderRightWidth"))},n.startScrolling=function(t,e){r.add(t,"ps-in-scrolling"),"undefined"!=typeof e?r.add(t,"ps-"+e):(r.add(t,"ps-x"),r.add(t,"ps-y"))},n.stopScrolling=function(t,e){r.remove(t,"ps-in-scrolling"),"undefined"!=typeof e?r.remove(t,"ps-"+e):(r.remove(t,"ps-x"),r.remove(t,"ps-y"))},n.env={isWebKit:"WebkitAppearance"in document.documentElement.style,supportsTouch:"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,supportsIePointer:null!==window.navigator.msMaxTouchPoints}},{"./class":2,"./dom":3}],7:[function(t,e,n){"use strict";var r=t("./plugin/destroy"),o=t("./plugin/initialize"),i=t("./plugin/update");e.exports={initialize:o,update:i,destroy:r}},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(t,e,n){"use strict";e.exports={maxScrollbarLength:null,minScrollbarLength:null,scrollXMarginOffset:0,scrollYMarginOffset:0,stopPropagationOnClick:!0,suppressScrollX:!1,suppressScrollY:!1,swipePropagation:!0,useBothWheelAxes:!1,useKeyboard:!0,useSelectionScroll:!1,wheelPropagation:!1,wheelSpeed:1}},{}],9:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances");e.exports=function(t){var e=i.get(t);e&&(e.event.unbindAll(),r.remove(e.scrollbarX),r.remove(e.scrollbarY),r.remove(e.scrollbarXRail),r.remove(e.scrollbarYRail),o.removePsClasses(t),i.remove(t))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(t,e,n){"use strict";function r(t,e){function n(t){return t.getBoundingClientRect()}e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarY,"click",function(t){t.stopPropagation()}),e.event.bind(e.scrollbarYRail,"click",function(r){var i=o.toInt(e.scrollbarYHeight/2),a=e.railYRatio*(r.pageY-window.scrollY-n(e.scrollbarYRail).top-i),c=e.railYRatio*(e.railYHeight-e.scrollbarYHeight),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"top",(e.contentHeight-e.containerHeight)*u),l(t),r.stopPropagation()}),e.settings.stopPropagationOnClick&&e.event.bind(e.scrollbarY,"click",function(t){t.stopPropagation()}),e.event.bind(e.scrollbarXRail,"click",function(r){var i=o.toInt(e.scrollbarXWidth/2),a=e.railXRatio*(r.pageX-window.scrollX-n(e.scrollbarXRail).left-i),c=e.railXRatio*(e.railXWidth-e.scrollbarXWidth),u=a/c;0>u?u=0:u>1&&(u=1),s(t,"left",(e.contentWidth-e.containerWidth)*u-e.negativeScrollAdjustment),l(t),r.stopPropagation()})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(t,e,n){"use strict";function r(t,e){function n(n){var o=r+n*e.railXRatio,i=e.scrollbarXRail.getBoundingClientRect().left+e.railXRatio*(e.railXWidth-e.scrollbarXWidth);0>o?e.scrollbarXLeft=0:o>i?e.scrollbarXLeft=i:e.scrollbarXLeft=o;var s=l.toInt(e.scrollbarXLeft*(e.contentWidth-e.containerWidth)/(e.containerWidth-e.railXRatio*e.scrollbarXWidth))-e.negativeScrollAdjustment;c(t,"left",s)}var r=null,o=null,s=function(e){n(e.pageX-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"x"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarX,"mousedown",function(n){o=n.pageX,r=l.toInt(i.css(e.scrollbarX,"left"))*e.railXRatio,l.startScrolling(t,"x"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}function o(t,e){function n(n){var o=r+n*e.railYRatio,i=e.scrollbarYRail.getBoundingClientRect().top+e.railYRatio*(e.railYHeight-e.scrollbarYHeight);0>o?e.scrollbarYTop=0:o>i?e.scrollbarYTop=i:e.scrollbarYTop=o;var s=l.toInt(e.scrollbarYTop*(e.contentHeight-e.containerHeight)/(e.containerHeight-e.railYRatio*e.scrollbarYHeight));c(t,"top",s)}var r=null,o=null,s=function(e){n(e.pageY-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"y"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarY,"mousedown",function(n){o=n.pageY,r=l.toInt(i.css(e.scrollbarY,"top"))*e.railYRatio,l.startScrolling(t,"y"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}var i=t("../../lib/dom"),l=t("../../lib/helper"),s=t("../instances"),a=t("../update-geometry"),c=t("../update-scroll");e.exports=function(t){var e=s.get(t);r(t,e),o(t,e)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}var r=!1;e.event.bind(t,"mouseenter",function(){r=!0}),e.event.bind(t,"mouseleave",function(){r=!1});var i=!1;e.event.bind(e.ownerDocument,"keydown",function(a){if((!a.isDefaultPrevented||!a.isDefaultPrevented())&&r){var c=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(c){for(;c.shadowRoot;)c=c.shadowRoot.activeElement;if(o.isEditable(c))return}var u=0,d=0;switch(a.which){case 37:u=-30;break;case 38:d=30;break;case 39:u=30;break;case 40:d=-30;break;case 33:d=90;break;case 32:d=a.shiftKey?90:-90;break;case 34:d=-90;break;case 35:d=a.ctrlKey?-e.contentHeight:-e.containerHeight;break;case 36:d=a.ctrlKey?t.scrollTop:e.containerHeight;break;default:return}s(t,"top",t.scrollTop-d),s(t,"left",t.scrollLeft+u),l(t),i=n(u,d),i&&a.preventDefault()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&0>n||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}function r(t){var e=t.deltaX,n=-1*t.deltaY;return("undefined"==typeof e||"undefined"==typeof n)&&(e=-1*t.wheelDeltaX/6,n=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,n*=10),e!==e&&n!==n&&(e=0,n=t.wheelDelta),[e,n]}function i(e,n){var r=t.querySelector("textarea:hover");if(r){var o=r.scrollHeight-r.clientHeight;if(o>0&&!(0===r.scrollTop&&n>0||r.scrollTop===o&&0>n))return!0;var i=r.scrollLeft-r.clientWidth;if(i>0&&!(0===r.scrollLeft&&0>e||r.scrollLeft===i&&e>0))return!0}return!1}function a(a){if(o.env.isWebKit||!t.querySelector("select:focus")){var u=r(a),d=u[0],p=u[1];i(d,p)||(c=!1,e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(p?s(t,"top",t.scrollTop-p*e.settings.wheelSpeed):s(t,"top",t.scrollTop+d*e.settings.wheelSpeed),c=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(d?s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed):s(t,"left",t.scrollLeft-p*e.settings.wheelSpeed),c=!0):(s(t,"top",t.scrollTop-p*e.settings.wheelSpeed),s(t,"left",t.scrollLeft+d*e.settings.wheelSpeed)),l(t),c=c||n(d,p),c&&(a.stopPropagation(),a.preventDefault()))}}var c=!1;"undefined"!=typeof window.onwheel?e.event.bind(t,"wheel",a):"undefined"!=typeof window.onmousewheel&&e.event.bind(t,"mousewheel",a)}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(t,e,n){"use strict";function r(t,e){e.event.bind(t,"scroll",function(){i(t)})}var o=t("../instances"),i=t("../update-geometry");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19}],15:[function(t,e,n){"use strict";function r(t,e){function n(){var t=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===t.toString().length?null:t.getRangeAt(0).commonAncestorContainer}function r(){c||(c=setInterval(function(){return i.get(t)?(s(t,"top",t.scrollTop+u.top),s(t,"left",t.scrollLeft+u.left),void l(t)):void clearInterval(c)},50))}function a(){c&&(clearInterval(c),c=null),o.stopScrolling(t)}var c=null,u={top:0,left:0},d=!1;e.event.bind(e.ownerDocument,"selectionchange",function(){t.contains(n())?d=!0:(d=!1,a())}),e.event.bind(window,"mouseup",function(){d&&(d=!1,a())}),e.event.bind(window,"mousemove",function(e){if(d){var n={x:e.pageX,y:e.pageY},i={left:t.offsetLeft,right:t.offsetLeft+t.offsetWidth,top:t.offsetTop,bottom:t.offsetTop+t.offsetHeight};n.x<i.left+3?(u.left=-5,o.startScrolling(t,"x")):n.x>i.right-3?(u.left=5,o.startScrolling(t,"x")):u.left=0,n.y<i.top+3?(i.top+3-n.y<5?u.top=-5:u.top=-20,o.startScrolling(t,"y")):n.y>i.bottom-3?(n.y-i.bottom+3<5?u.top=5:u.top=20,o.startScrolling(t,"y")):u.top=0,0===u.top&&0===u.left?a():r()}})}var o=t("../../lib/helper"),i=t("../instances"),l=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(t,e,n){"use strict";function r(t,e,n,r){function s(n,r){var o=t.scrollTop,i=t.scrollLeft,l=Math.abs(n),s=Math.abs(r);if(s>l){if(0>r&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return!e.settings.swipePropagation}else if(l>s&&(0>n&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!e.settings.swipePropagation;return!0}function a(e,n){l(t,"top",t.scrollTop-n),l(t,"left",t.scrollLeft-e),i(t)}function c(){Y=!0}function u(){Y=!1}function d(t){return t.targetTouches?t.targetTouches[0]:t}function p(t){return t.targetTouches&&1===t.targetTouches.length?!0:t.pointerType&&"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE?!0:!1}function f(t){if(p(t)){w=!0;var e=d(t);b.pageX=e.pageX,b.pageY=e.pageY,g=(new Date).getTime(),null!==y&&clearInterval(y),t.stopPropagation()}}function h(t){if(!Y&&w&&p(t)){var e=d(t),n={pageX:e.pageX,pageY:e.pageY},r=n.pageX-b.pageX,o=n.pageY-b.pageY;a(r,o),b=n;var i=(new Date).getTime(),l=i-g;l>0&&(m.x=r/l,m.y=o/l,g=i),s(r,o)&&(t.stopPropagation(),t.preventDefault())}}function v(){!Y&&w&&(w=!1,clearInterval(y),y=setInterval(function(){return o.get(t)?Math.abs(m.x)<.01&&Math.abs(m.y)<.01?void clearInterval(y):(a(30*m.x,30*m.y),m.x*=.8,void(m.y*=.8)):void clearInterval(y)},10))}var b={},g=0,m={},y=null,Y=!1,w=!1;n&&(e.event.bind(window,"touchstart",c),e.event.bind(window,"touchend",u),e.event.bind(t,"touchstart",f),e.event.bind(t,"touchmove",h),e.event.bind(t,"touchend",v)),r&&(window.PointerEvent?(e.event.bind(window,"pointerdown",c),e.event.bind(window,"pointerup",u),e.event.bind(t,"pointerdown",f),e.event.bind(t,"pointermove",h),e.event.bind(t,"pointerup",v)):window.MSPointerEvent&&(e.event.bind(window,"MSPointerDown",c),e.event.bind(window,"MSPointerUp",u),e.event.bind(t,"MSPointerDown",f),e.event.bind(t,"MSPointerMove",h),e.event.bind(t,"MSPointerUp",v)))}var o=t("../instances"),i=t("../update-geometry"),l=t("../update-scroll");e.exports=function(t,e,n){var i=o.get(t);r(t,i,e,n)}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(t,e,n){"use strict";var r=t("../lib/class"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry"),s=t("./handler/click-rail"),a=t("./handler/drag-scrollbar"),c=t("./handler/keyboard"),u=t("./handler/mouse-wheel"),d=t("./handler/native-scroll"),p=t("./handler/selection"),f=t("./handler/touch");e.exports=function(t,e){e="object"==typeof e?e:{},r.add(t,"ps-container");var n=i.add(t);n.settings=o.extend(n.settings,e),s(t),a(t),u(t),d(t),n.settings.useSelectionScroll&&p(t),(o.env.supportsTouch||o.env.supportsIePointer)&&f(t,o.env.supportsTouch,o.env.supportsIePointer),n.settings.useKeyboard&&c(t),l(t)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(t,e,n){"use strict";function r(t){var e=this;e.settings=d.clone(a),e.containerWidth=null,e.containerHeight=null,e.contentWidth=null,e.contentHeight=null,e.isRtl="rtl"===s.css(t,"direction"),e.isNegativeScroll=function(){var e=t.scrollLeft,n=null;return t.scrollLeft=-1,n=t.scrollLeft<0,t.scrollLeft=e,n}(),e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,e.event=new c,e.ownerDocument=t.ownerDocument||document,e.scrollbarXRail=s.appendTo(s.e("div","ps-scrollbar-x-rail"),t),e.scrollbarX=s.appendTo(s.e("div","ps-scrollbar-x"),e.scrollbarXRail),e.scrollbarXActive=null,e.scrollbarXWidth=null,e.scrollbarXLeft=null,e.scrollbarXBottom=d.toInt(s.css(e.scrollbarXRail,"bottom")),e.isScrollbarXUsingBottom=e.scrollbarXBottom===e.scrollbarXBottom,e.scrollbarXTop=e.isScrollbarXUsingBottom?null:d.toInt(s.css(e.scrollbarXRail,"top")),e.railBorderXWidth=d.toInt(s.css(e.scrollbarXRail,"borderLeftWidth"))+d.toInt(s.css(e.scrollbarXRail,"borderRightWidth")),s.css(e.scrollbarXRail,"display","block"),e.railXMarginWidth=d.toInt(s.css(e.scrollbarXRail,"marginLeft"))+d.toInt(s.css(e.scrollbarXRail,"marginRight")),s.css(e.scrollbarXRail,"display",""),e.railXWidth=null,e.railXRatio=null,e.scrollbarYRail=s.appendTo(s.e("div","ps-scrollbar-y-rail"),t),e.scrollbarY=s.appendTo(s.e("div","ps-scrollbar-y"),e.scrollbarYRail),e.scrollbarYActive=null,e.scrollbarYHeight=null,e.scrollbarYTop=null,e.scrollbarYRight=d.toInt(s.css(e.scrollbarYRail,"right")),e.isScrollbarYUsingRight=e.scrollbarYRight===e.scrollbarYRight,e.scrollbarYLeft=e.isScrollbarYUsingRight?null:d.toInt(s.css(e.scrollbarYRail,"left")),e.scrollbarYOuterWidth=e.isRtl?d.outerWidth(e.scrollbarY):null,e.railBorderYWidth=d.toInt(s.css(e.scrollbarYRail,"borderTopWidth"))+d.toInt(s.css(e.scrollbarYRail,"borderBottomWidth")),s.css(e.scrollbarYRail,"display","block"),e.railYMarginHeight=d.toInt(s.css(e.scrollbarYRail,"marginTop"))+d.toInt(s.css(e.scrollbarYRail,"marginBottom")),s.css(e.scrollbarYRail,"display",""),e.railYHeight=null,e.railYRatio=null}function o(t){return"undefined"==typeof t.dataset?t.getAttribute("data-ps-id"):t.dataset.psId}function i(t,e){"undefined"==typeof t.dataset?t.setAttribute("data-ps-id",e):t.dataset.psId=e}function l(t){"undefined"==typeof t.dataset?t.removeAttribute("data-ps-id"):delete t.dataset.psId}var s=t("../lib/dom"),a=t("./default-setting"),c=t("../lib/event-manager"),u=t("../lib/guid"),d=t("../lib/helper"),p={};n.add=function(t){var e=u();return i(t,e),p[e]=new r(t),p[e]},n.remove=function(t){delete p[o(t)],l(t)},n.get=function(t){return p[o(t)]}},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(t,e,n){"use strict";function r(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function o(t,e){var n={width:e.railXWidth};e.isRtl?n.left=e.negativeScrollAdjustment+t.scrollLeft+e.containerWidth-e.contentWidth:n.left=t.scrollLeft,e.isScrollbarXUsingBottom?n.bottom=e.scrollbarXBottom-t.scrollTop:n.top=e.scrollbarXTop+t.scrollTop,l.css(e.scrollbarXRail,n);var r={top:t.scrollTop,height:e.railYHeight};e.isScrollbarYUsingRight?e.isRtl?r.right=e.contentWidth-(e.negativeScrollAdjustment+t.scrollLeft)-e.scrollbarYRight-e.scrollbarYOuterWidth:r.right=e.scrollbarYRight-t.scrollLeft:e.isRtl?r.left=e.negativeScrollAdjustment+t.scrollLeft+2*e.containerWidth-e.contentWidth-e.scrollbarYLeft-e.scrollbarYOuterWidth:r.left=e.scrollbarYLeft+t.scrollLeft,l.css(e.scrollbarYRail,r),l.css(e.scrollbarX,{left:e.scrollbarXLeft,width:e.scrollbarXWidth-e.railBorderXWidth}),l.css(e.scrollbarY,{top:e.scrollbarYTop,height:e.scrollbarYHeight-e.railBorderYWidth})}var i=t("../lib/class"),l=t("../lib/dom"),s=t("../lib/helper"),a=t("./instances"),c=t("./update-scroll");e.exports=function(t){var e=a.get(t);e.containerWidth=t.clientWidth,e.containerHeight=t.clientHeight,e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight;var n;t.contains(e.scrollbarXRail)||(n=l.queryChildren(t,".ps-scrollbar-x-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarXRail,t)),t.contains(e.scrollbarYRail)||(n=l.queryChildren(t,".ps-scrollbar-y-rail"),n.length>0&&n.forEach(function(t){l.remove(t)}),l.appendTo(e.scrollbarYRail,t)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset<e.contentWidth?(e.scrollbarXActive=!0,e.railXWidth=e.containerWidth-e.railXMarginWidth,e.railXRatio=e.containerWidth/e.railXWidth,e.scrollbarXWidth=r(e,s.toInt(e.railXWidth*e.containerWidth/e.contentWidth)),e.scrollbarXLeft=s.toInt((e.negativeScrollAdjustment+t.scrollLeft)*(e.railXWidth-e.scrollbarXWidth)/(e.contentWidth-e.containerWidth))):(e.scrollbarXActive=!1,e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=0),!e.settings.suppressScrollY&&e.containerHeight+e.settings.scrollYMarginOffset<e.contentHeight?(e.scrollbarYActive=!0,e.railYHeight=e.containerHeight-e.railYMarginHeight,e.railYRatio=e.containerHeight/e.railYHeight,e.scrollbarYHeight=r(e,s.toInt(e.railYHeight*e.containerHeight/e.contentHeight)),e.scrollbarYTop=s.toInt(t.scrollTop*(e.railYHeight-e.scrollbarYHeight)/(e.contentHeight-e.containerHeight))):(e.scrollbarYActive=!1,e.scrollbarYHeight=0,e.scrollbarYTop=0,c(t,"top",0)),e.scrollbarXLeft>=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),o(t,e),i[e.scrollbarXActive?"add":"remove"](t,"ps-active-x"),i[e.scrollbarYActive?"add":"remove"](t,"ps-active-y")}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(t,e,n){"use strict";var r,o,i=t("./instances"),l=document.createEvent("Event"),s=document.createEvent("Event"),a=document.createEvent("Event"),c=document.createEvent("Event"),u=document.createEvent("Event"),d=document.createEvent("Event"),p=document.createEvent("Event"),f=document.createEvent("Event"),h=document.createEvent("Event"),v=document.createEvent("Event");l.initEvent("ps-scroll-up",!0,!0),s.initEvent("ps-scroll-down",!0,!0),a.initEvent("ps-scroll-left",!0,!0),c.initEvent("ps-scroll-right",!0,!0),u.initEvent("ps-scroll-y",!0,!0),d.initEvent("ps-scroll-x",!0,!0),p.initEvent("ps-x-reach-start",!0,!0),f.initEvent("ps-x-reach-end",!0,!0),h.initEvent("ps-y-reach-start",!0,!0),v.initEvent("ps-y-reach-end",!0,!0),e.exports=function(t,e,n){if("undefined"==typeof t)throw"You must provide an element to the update-scroll function";if("undefined"==typeof e)throw"You must provide an axis to the update-scroll function";if("undefined"==typeof n)throw"You must provide a value to the update-scroll function";if("top"===e&&0>=n)return t.scrollTop=0,void t.dispatchEvent(h);if("left"===e&&0>=n)return t.scrollLeft=0,void t.dispatchEvent(p);var b=i.get(t);return"top"===e&&n>b.contentHeight-b.containerHeight?(t.scrollTop=b.contentHeight-b.containerHeight,void t.dispatchEvent(v)):"left"===e&&n>b.contentWidth-b.containerWidth?(t.scrollLeft=b.contentWidth-b.containerWidth,void t.dispatchEvent(f)):(r||(r=t.scrollTop),o||(o=t.scrollLeft),"top"===e&&r>n&&t.dispatchEvent(l),"top"===e&&n>r&&t.dispatchEvent(s),"left"===e&&o>n&&t.dispatchEvent(a),"left"===e&&n>o&&t.dispatchEvent(c),"top"===e&&(t.scrollTop=r=n,t.dispatchEvent(u)),void("left"===e&&(t.scrollLeft=o=n,t.dispatchEvent(d))))}},{"./instances":18}],21:[function(t,e,n){"use strict";var r=t("../lib/dom"),o=t("../lib/helper"),i=t("./instances"),l=t("./update-geometry");e.exports=function(t){var e=i.get(t);e&&(e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,r.css(e.scrollbarXRail,"display","block"),r.css(e.scrollbarYRail,"display","block"),e.railXMarginWidth=o.toInt(r.css(e.scrollbarXRail,"marginLeft"))+o.toInt(r.css(e.scrollbarXRail,"marginRight")),e.railYMarginHeight=o.toInt(r.css(e.scrollbarYRail,"marginTop"))+o.toInt(r.css(e.scrollbarYRail,"marginBottom")),r.css(e.scrollbarXRail,"display","none"),r.css(e.scrollbarYRail,"display","none"),l(t),r.css(e.scrollbarXRail,"display",""),r.css(e.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19}]},{},[1]); \ No newline at end of file
diff --git a/web/templates/head.html b/web/templates/head.html
index a73e809a7..2bbf921ee 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -52,6 +52,15 @@
headers: { 'X-MM-TokenIndex': mm_session_token_index }
});
}
+
+ $(function () {
+ $(window).bind('storage', function (e) {
+ if (e.originalEvent.key === '__logout__') {
+ console.log('detected logout from a different tab');
+ window.location.href = '/' + window.mm_team.name;
+ }
+ });
+ });
</script>
<script>
diff --git a/web/web.go b/web/web.go
index 51f6664b6..1cae604ae 100644
--- a/web/web.go
+++ b/web/web.go
@@ -983,6 +983,11 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
parsedRequest = model.IncomingWebhookRequestFromJson(strings.NewReader(r.FormValue("payload")))
}
+ if parsedRequest == nil {
+ c.Err = model.NewAppError("incomingWebhook", "Unable to parse incoming data", "")
+ return
+ }
+
text := parsedRequest.Text
if len(text) == 0 {
c.Err = model.NewAppError("incomingWebhook", "No text specified", "")
@@ -990,6 +995,16 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
channelName := parsedRequest.ChannelName
+ webhookType := parsedRequest.Type
+
+ //attachments is in here for slack compatibility
+ if parsedRequest.Attachments != nil {
+ if len(parsedRequest.Props) == 0 {
+ parsedRequest.Props = make(model.StringInterface)
+ }
+ parsedRequest.Props["attachments"] = parsedRequest.Attachments
+ webhookType = model.POST_SLACK_ATTACHMENT
+ }
var hook *model.IncomingWebhook
if result := <-hchan; result.Err != nil {
@@ -1039,7 +1054,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
return
}
- if _, err := api.CreateWebhookPost(c, channel.Id, text, overrideUsername, overrideIconUrl); err != nil {
+ if _, err := api.CreateWebhookPost(c, channel.Id, text, overrideUsername, overrideIconUrl, parsedRequest.Props, webhookType); err != nil {
c.Err = err
return
}