From bc8a4d9bfa3dd80c80caf75f678067475ffe0430 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 26 Oct 2022 12:29:04 +0000 Subject: [PATCH] Run prettier formatter as part of ci (ref #4) (#28) * Run prettier formatter as part of ci (ref #4) * fix docker image * fix prettier path * format files * clean rust code * fix clippy --- .drone.yml | 8 +- assets/css/font-awesome.min.css | 2335 +++++++++- assets/images/avatars/gallery/index.htm | 12 +- assets/images/avatars/index.htm | 12 +- assets/images/avatars/upload/index.htm | 12 +- assets/images/icons/index.htm | 12 +- assets/images/icons/misc/index.htm | 12 +- assets/images/icons/smile/index.htm | 12 +- assets/images/index.htm | 12 +- assets/images/ranks/index.htm | 12 +- assets/javascript/ajax.js | 860 ++-- assets/javascript/core.js | 3781 +++++++-------- assets/javascript/editor.js | 714 +-- assets/javascript/forum_fn.js | 1843 ++++---- assets/javascript/installer.js | 1256 ++--- assets/javascript/jquery-3.6.0.min.js | 5574 ++++++++++++++++++++++- assets/javascript/plupload.js | 1447 +++--- assets/styles/prosilver/bidi.css | 15 +- assets/styles/prosilver/colours.css | 64 +- assets/styles/prosilver/common.css | 6 +- assets/styles/prosilver/content.css | 6 +- assets/styles/prosilver/cp.css | 3 +- assets/styles/prosilver/print.css | 3 +- 23 files changed, 13156 insertions(+), 4855 deletions(-) diff --git a/.drone.yml b/.drone.yml index f743e7d..243ef23 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,7 +14,13 @@ steps: - git submodule init - git submodule update - - name: check formatting + - name: check css/js formatting + image: node:alpine + commands: + - npm install --save-dev --save-exact prettier + - ./node_modules/prettier/bin-prettier.js --check assets + + - name: check rust formatting image: rustdocker/rust:nightly commands: - /root/.cargo/bin/cargo fmt -- --check diff --git a/assets/css/font-awesome.min.css b/assets/css/font-awesome.min.css index 540440c..180f4fc 100644 --- a/assets/css/font-awesome.min.css +++ b/assets/css/font-awesome.min.css @@ -1,4 +1,2337 @@ /*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} + */ +@font-face { + font-family: "FontAwesome"; + src: url("../fonts/fontawesome-webfont.eot?v=4.7.0"); + src: url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") + format("embedded-opentype"), + url("../fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"), + url("../fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"), + url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"), + url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") + format("svg"); + font-weight: normal; + font-style: normal; +} +.fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} +.fa-fw { + width: 1.28571429em; + text-align: center; +} +.fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.fa-ul > li { + position: relative; +} +.fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.fa-li.fa-lg { + left: -1.85714286em; +} +.fa-border { + padding: 0.2em 0.25em 0.15em; + border: solid 0.08em #eee; + border-radius: 0.1em; +} +.fa-pull-left { + float: left; +} +.fa-pull-right { + float: right; +} +.fa.fa-pull-left { + margin-right: 0.3em; +} +.fa.fa-pull-right { + margin-left: 0.3em; +} +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.fa.pull-left { + margin-right: 0.3em; +} +.fa.pull-right { + margin-left: 0.3em; +} +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} +.fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.fa-stack-1x, +.fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.fa-stack-1x { + line-height: inherit; +} +.fa-stack-2x { + font-size: 2em; +} +.fa-inverse { + color: #fff; +} +.fa-glass:before { + content: "\f000"; +} +.fa-music:before { + content: "\f001"; +} +.fa-search:before { + content: "\f002"; +} +.fa-envelope-o:before { + content: "\f003"; +} +.fa-heart:before { + content: "\f004"; +} +.fa-star:before { + content: "\f005"; +} +.fa-star-o:before { + content: "\f006"; +} +.fa-user:before { + content: "\f007"; +} +.fa-film:before { + content: "\f008"; +} +.fa-th-large:before { + content: "\f009"; +} +.fa-th:before { + content: "\f00a"; +} +.fa-th-list:before { + content: "\f00b"; +} +.fa-check:before { + content: "\f00c"; +} +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: "\f00d"; +} +.fa-search-plus:before { + content: "\f00e"; +} +.fa-search-minus:before { + content: "\f010"; +} +.fa-power-off:before { + content: "\f011"; +} +.fa-signal:before { + content: "\f012"; +} +.fa-gear:before, +.fa-cog:before { + content: "\f013"; +} +.fa-trash-o:before { + content: "\f014"; +} +.fa-home:before { + content: "\f015"; +} +.fa-file-o:before { + content: "\f016"; +} +.fa-clock-o:before { + content: "\f017"; +} +.fa-road:before { + content: "\f018"; +} +.fa-download:before { + content: "\f019"; +} +.fa-arrow-circle-o-down:before { + content: "\f01a"; +} +.fa-arrow-circle-o-up:before { + content: "\f01b"; +} +.fa-inbox:before { + content: "\f01c"; +} +.fa-play-circle-o:before { + content: "\f01d"; +} +.fa-rotate-right:before, +.fa-repeat:before { + content: "\f01e"; +} +.fa-refresh:before { + content: "\f021"; +} +.fa-list-alt:before { + content: "\f022"; +} +.fa-lock:before { + content: "\f023"; +} +.fa-flag:before { + content: "\f024"; +} +.fa-headphones:before { + content: "\f025"; +} +.fa-volume-off:before { + content: "\f026"; +} +.fa-volume-down:before { + content: "\f027"; +} +.fa-volume-up:before { + content: "\f028"; +} +.fa-qrcode:before { + content: "\f029"; +} +.fa-barcode:before { + content: "\f02a"; +} +.fa-tag:before { + content: "\f02b"; +} +.fa-tags:before { + content: "\f02c"; +} +.fa-book:before { + content: "\f02d"; +} +.fa-bookmark:before { + content: "\f02e"; +} +.fa-print:before { + content: "\f02f"; +} +.fa-camera:before { + content: "\f030"; +} +.fa-font:before { + content: "\f031"; +} +.fa-bold:before { + content: "\f032"; +} +.fa-italic:before { + content: "\f033"; +} +.fa-text-height:before { + content: "\f034"; +} +.fa-text-width:before { + content: "\f035"; +} +.fa-align-left:before { + content: "\f036"; +} +.fa-align-center:before { + content: "\f037"; +} +.fa-align-right:before { + content: "\f038"; +} +.fa-align-justify:before { + content: "\f039"; +} +.fa-list:before { + content: "\f03a"; +} +.fa-dedent:before, +.fa-outdent:before { + content: "\f03b"; +} +.fa-indent:before { + content: "\f03c"; +} +.fa-video-camera:before { + content: "\f03d"; +} +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: "\f03e"; +} +.fa-pencil:before { + content: "\f040"; +} +.fa-map-marker:before { + content: "\f041"; +} +.fa-adjust:before { + content: "\f042"; +} +.fa-tint:before { + content: "\f043"; +} +.fa-edit:before, +.fa-pencil-square-o:before { + content: "\f044"; +} +.fa-share-square-o:before { + content: "\f045"; +} +.fa-check-square-o:before { + content: "\f046"; +} +.fa-arrows:before { + content: "\f047"; +} +.fa-step-backward:before { + content: "\f048"; +} +.fa-fast-backward:before { + content: "\f049"; +} +.fa-backward:before { + content: "\f04a"; +} +.fa-play:before { + content: "\f04b"; +} +.fa-pause:before { + content: "\f04c"; +} +.fa-stop:before { + content: "\f04d"; +} +.fa-forward:before { + content: "\f04e"; +} +.fa-fast-forward:before { + content: "\f050"; +} +.fa-step-forward:before { + content: "\f051"; +} +.fa-eject:before { + content: "\f052"; +} +.fa-chevron-left:before { + content: "\f053"; +} +.fa-chevron-right:before { + content: "\f054"; +} +.fa-plus-circle:before { + content: "\f055"; +} +.fa-minus-circle:before { + content: "\f056"; +} +.fa-times-circle:before { + content: "\f057"; +} +.fa-check-circle:before { + content: "\f058"; +} +.fa-question-circle:before { + content: "\f059"; +} +.fa-info-circle:before { + content: "\f05a"; +} +.fa-crosshairs:before { + content: "\f05b"; +} +.fa-times-circle-o:before { + content: "\f05c"; +} +.fa-check-circle-o:before { + content: "\f05d"; +} +.fa-ban:before { + content: "\f05e"; +} +.fa-arrow-left:before { + content: "\f060"; +} +.fa-arrow-right:before { + content: "\f061"; +} +.fa-arrow-up:before { + content: "\f062"; +} +.fa-arrow-down:before { + content: "\f063"; +} +.fa-mail-forward:before, +.fa-share:before { + content: "\f064"; +} +.fa-expand:before { + content: "\f065"; +} +.fa-compress:before { + content: "\f066"; +} +.fa-plus:before { + content: "\f067"; +} +.fa-minus:before { + content: "\f068"; +} +.fa-asterisk:before { + content: "\f069"; +} +.fa-exclamation-circle:before { + content: "\f06a"; +} +.fa-gift:before { + content: "\f06b"; +} +.fa-leaf:before { + content: "\f06c"; +} +.fa-fire:before { + content: "\f06d"; +} +.fa-eye:before { + content: "\f06e"; +} +.fa-eye-slash:before { + content: "\f070"; +} +.fa-warning:before, +.fa-exclamation-triangle:before { + content: "\f071"; +} +.fa-plane:before { + content: "\f072"; +} +.fa-calendar:before { + content: "\f073"; +} +.fa-random:before { + content: "\f074"; +} +.fa-comment:before { + content: "\f075"; +} +.fa-magnet:before { + content: "\f076"; +} +.fa-chevron-up:before { + content: "\f077"; +} +.fa-chevron-down:before { + content: "\f078"; +} +.fa-retweet:before { + content: "\f079"; +} +.fa-shopping-cart:before { + content: "\f07a"; +} +.fa-folder:before { + content: "\f07b"; +} +.fa-folder-open:before { + content: "\f07c"; +} +.fa-arrows-v:before { + content: "\f07d"; +} +.fa-arrows-h:before { + content: "\f07e"; +} +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: "\f080"; +} +.fa-twitter-square:before { + content: "\f081"; +} +.fa-facebook-square:before { + content: "\f082"; +} +.fa-camera-retro:before { + content: "\f083"; +} +.fa-key:before { + content: "\f084"; +} +.fa-gears:before, +.fa-cogs:before { + content: "\f085"; +} +.fa-comments:before { + content: "\f086"; +} +.fa-thumbs-o-up:before { + content: "\f087"; +} +.fa-thumbs-o-down:before { + content: "\f088"; +} +.fa-star-half:before { + content: "\f089"; +} +.fa-heart-o:before { + content: "\f08a"; +} +.fa-sign-out:before { + content: "\f08b"; +} +.fa-linkedin-square:before { + content: "\f08c"; +} +.fa-thumb-tack:before { + content: "\f08d"; +} +.fa-external-link:before { + content: "\f08e"; +} +.fa-sign-in:before { + content: "\f090"; +} +.fa-trophy:before { + content: "\f091"; +} +.fa-github-square:before { + content: "\f092"; +} +.fa-upload:before { + content: "\f093"; +} +.fa-lemon-o:before { + content: "\f094"; +} +.fa-phone:before { + content: "\f095"; +} +.fa-square-o:before { + content: "\f096"; +} +.fa-bookmark-o:before { + content: "\f097"; +} +.fa-phone-square:before { + content: "\f098"; +} +.fa-twitter:before { + content: "\f099"; +} +.fa-facebook-f:before, +.fa-facebook:before { + content: "\f09a"; +} +.fa-github:before { + content: "\f09b"; +} +.fa-unlock:before { + content: "\f09c"; +} +.fa-credit-card:before { + content: "\f09d"; +} +.fa-feed:before, +.fa-rss:before { + content: "\f09e"; +} +.fa-hdd-o:before { + content: "\f0a0"; +} +.fa-bullhorn:before { + content: "\f0a1"; +} +.fa-bell:before { + content: "\f0f3"; +} +.fa-certificate:before { + content: "\f0a3"; +} +.fa-hand-o-right:before { + content: "\f0a4"; +} +.fa-hand-o-left:before { + content: "\f0a5"; +} +.fa-hand-o-up:before { + content: "\f0a6"; +} +.fa-hand-o-down:before { + content: "\f0a7"; +} +.fa-arrow-circle-left:before { + content: "\f0a8"; +} +.fa-arrow-circle-right:before { + content: "\f0a9"; +} +.fa-arrow-circle-up:before { + content: "\f0aa"; +} +.fa-arrow-circle-down:before { + content: "\f0ab"; +} +.fa-globe:before { + content: "\f0ac"; +} +.fa-wrench:before { + content: "\f0ad"; +} +.fa-tasks:before { + content: "\f0ae"; +} +.fa-filter:before { + content: "\f0b0"; +} +.fa-briefcase:before { + content: "\f0b1"; +} +.fa-arrows-alt:before { + content: "\f0b2"; +} +.fa-group:before, +.fa-users:before { + content: "\f0c0"; +} +.fa-chain:before, +.fa-link:before { + content: "\f0c1"; +} +.fa-cloud:before { + content: "\f0c2"; +} +.fa-flask:before { + content: "\f0c3"; +} +.fa-cut:before, +.fa-scissors:before { + content: "\f0c4"; +} +.fa-copy:before, +.fa-files-o:before { + content: "\f0c5"; +} +.fa-paperclip:before { + content: "\f0c6"; +} +.fa-save:before, +.fa-floppy-o:before { + content: "\f0c7"; +} +.fa-square:before { + content: "\f0c8"; +} +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: "\f0c9"; +} +.fa-list-ul:before { + content: "\f0ca"; +} +.fa-list-ol:before { + content: "\f0cb"; +} +.fa-strikethrough:before { + content: "\f0cc"; +} +.fa-underline:before { + content: "\f0cd"; +} +.fa-table:before { + content: "\f0ce"; +} +.fa-magic:before { + content: "\f0d0"; +} +.fa-truck:before { + content: "\f0d1"; +} +.fa-pinterest:before { + content: "\f0d2"; +} +.fa-pinterest-square:before { + content: "\f0d3"; +} +.fa-google-plus-square:before { + content: "\f0d4"; +} +.fa-google-plus:before { + content: "\f0d5"; +} +.fa-money:before { + content: "\f0d6"; +} +.fa-caret-down:before { + content: "\f0d7"; +} +.fa-caret-up:before { + content: "\f0d8"; +} +.fa-caret-left:before { + content: "\f0d9"; +} +.fa-caret-right:before { + content: "\f0da"; +} +.fa-columns:before { + content: "\f0db"; +} +.fa-unsorted:before, +.fa-sort:before { + content: "\f0dc"; +} +.fa-sort-down:before, +.fa-sort-desc:before { + content: "\f0dd"; +} +.fa-sort-up:before, +.fa-sort-asc:before { + content: "\f0de"; +} +.fa-envelope:before { + content: "\f0e0"; +} +.fa-linkedin:before { + content: "\f0e1"; +} +.fa-rotate-left:before, +.fa-undo:before { + content: "\f0e2"; +} +.fa-legal:before, +.fa-gavel:before { + content: "\f0e3"; +} +.fa-dashboard:before, +.fa-tachometer:before { + content: "\f0e4"; +} +.fa-comment-o:before { + content: "\f0e5"; +} +.fa-comments-o:before { + content: "\f0e6"; +} +.fa-flash:before, +.fa-bolt:before { + content: "\f0e7"; +} +.fa-sitemap:before { + content: "\f0e8"; +} +.fa-umbrella:before { + content: "\f0e9"; +} +.fa-paste:before, +.fa-clipboard:before { + content: "\f0ea"; +} +.fa-lightbulb-o:before { + content: "\f0eb"; +} +.fa-exchange:before { + content: "\f0ec"; +} +.fa-cloud-download:before { + content: "\f0ed"; +} +.fa-cloud-upload:before { + content: "\f0ee"; +} +.fa-user-md:before { + content: "\f0f0"; +} +.fa-stethoscope:before { + content: "\f0f1"; +} +.fa-suitcase:before { + content: "\f0f2"; +} +.fa-bell-o:before { + content: "\f0a2"; +} +.fa-coffee:before { + content: "\f0f4"; +} +.fa-cutlery:before { + content: "\f0f5"; +} +.fa-file-text-o:before { + content: "\f0f6"; +} +.fa-building-o:before { + content: "\f0f7"; +} +.fa-hospital-o:before { + content: "\f0f8"; +} +.fa-ambulance:before { + content: "\f0f9"; +} +.fa-medkit:before { + content: "\f0fa"; +} +.fa-fighter-jet:before { + content: "\f0fb"; +} +.fa-beer:before { + content: "\f0fc"; +} +.fa-h-square:before { + content: "\f0fd"; +} +.fa-plus-square:before { + content: "\f0fe"; +} +.fa-angle-double-left:before { + content: "\f100"; +} +.fa-angle-double-right:before { + content: "\f101"; +} +.fa-angle-double-up:before { + content: "\f102"; +} +.fa-angle-double-down:before { + content: "\f103"; +} +.fa-angle-left:before { + content: "\f104"; +} +.fa-angle-right:before { + content: "\f105"; +} +.fa-angle-up:before { + content: "\f106"; +} +.fa-angle-down:before { + content: "\f107"; +} +.fa-desktop:before { + content: "\f108"; +} +.fa-laptop:before { + content: "\f109"; +} +.fa-tablet:before { + content: "\f10a"; +} +.fa-mobile-phone:before, +.fa-mobile:before { + content: "\f10b"; +} +.fa-circle-o:before { + content: "\f10c"; +} +.fa-quote-left:before { + content: "\f10d"; +} +.fa-quote-right:before { + content: "\f10e"; +} +.fa-spinner:before { + content: "\f110"; +} +.fa-circle:before { + content: "\f111"; +} +.fa-mail-reply:before, +.fa-reply:before { + content: "\f112"; +} +.fa-github-alt:before { + content: "\f113"; +} +.fa-folder-o:before { + content: "\f114"; +} +.fa-folder-open-o:before { + content: "\f115"; +} +.fa-smile-o:before { + content: "\f118"; +} +.fa-frown-o:before { + content: "\f119"; +} +.fa-meh-o:before { + content: "\f11a"; +} +.fa-gamepad:before { + content: "\f11b"; +} +.fa-keyboard-o:before { + content: "\f11c"; +} +.fa-flag-o:before { + content: "\f11d"; +} +.fa-flag-checkered:before { + content: "\f11e"; +} +.fa-terminal:before { + content: "\f120"; +} +.fa-code:before { + content: "\f121"; +} +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: "\f122"; +} +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: "\f123"; +} +.fa-location-arrow:before { + content: "\f124"; +} +.fa-crop:before { + content: "\f125"; +} +.fa-code-fork:before { + content: "\f126"; +} +.fa-unlink:before, +.fa-chain-broken:before { + content: "\f127"; +} +.fa-question:before { + content: "\f128"; +} +.fa-info:before { + content: "\f129"; +} +.fa-exclamation:before { + content: "\f12a"; +} +.fa-superscript:before { + content: "\f12b"; +} +.fa-subscript:before { + content: "\f12c"; +} +.fa-eraser:before { + content: "\f12d"; +} +.fa-puzzle-piece:before { + content: "\f12e"; +} +.fa-microphone:before { + content: "\f130"; +} +.fa-microphone-slash:before { + content: "\f131"; +} +.fa-shield:before { + content: "\f132"; +} +.fa-calendar-o:before { + content: "\f133"; +} +.fa-fire-extinguisher:before { + content: "\f134"; +} +.fa-rocket:before { + content: "\f135"; +} +.fa-maxcdn:before { + content: "\f136"; +} +.fa-chevron-circle-left:before { + content: "\f137"; +} +.fa-chevron-circle-right:before { + content: "\f138"; +} +.fa-chevron-circle-up:before { + content: "\f139"; +} +.fa-chevron-circle-down:before { + content: "\f13a"; +} +.fa-html5:before { + content: "\f13b"; +} +.fa-css3:before { + content: "\f13c"; +} +.fa-anchor:before { + content: "\f13d"; +} +.fa-unlock-alt:before { + content: "\f13e"; +} +.fa-bullseye:before { + content: "\f140"; +} +.fa-ellipsis-h:before { + content: "\f141"; +} +.fa-ellipsis-v:before { + content: "\f142"; +} +.fa-rss-square:before { + content: "\f143"; +} +.fa-play-circle:before { + content: "\f144"; +} +.fa-ticket:before { + content: "\f145"; +} +.fa-minus-square:before { + content: "\f146"; +} +.fa-minus-square-o:before { + content: "\f147"; +} +.fa-level-up:before { + content: "\f148"; +} +.fa-level-down:before { + content: "\f149"; +} +.fa-check-square:before { + content: "\f14a"; +} +.fa-pencil-square:before { + content: "\f14b"; +} +.fa-external-link-square:before { + content: "\f14c"; +} +.fa-share-square:before { + content: "\f14d"; +} +.fa-compass:before { + content: "\f14e"; +} +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: "\f150"; +} +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: "\f151"; +} +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: "\f152"; +} +.fa-euro:before, +.fa-eur:before { + content: "\f153"; +} +.fa-gbp:before { + content: "\f154"; +} +.fa-dollar:before, +.fa-usd:before { + content: "\f155"; +} +.fa-rupee:before, +.fa-inr:before { + content: "\f156"; +} +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: "\f157"; +} +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: "\f158"; +} +.fa-won:before, +.fa-krw:before { + content: "\f159"; +} +.fa-bitcoin:before, +.fa-btc:before { + content: "\f15a"; +} +.fa-file:before { + content: "\f15b"; +} +.fa-file-text:before { + content: "\f15c"; +} +.fa-sort-alpha-asc:before { + content: "\f15d"; +} +.fa-sort-alpha-desc:before { + content: "\f15e"; +} +.fa-sort-amount-asc:before { + content: "\f160"; +} +.fa-sort-amount-desc:before { + content: "\f161"; +} +.fa-sort-numeric-asc:before { + content: "\f162"; +} +.fa-sort-numeric-desc:before { + content: "\f163"; +} +.fa-thumbs-up:before { + content: "\f164"; +} +.fa-thumbs-down:before { + content: "\f165"; +} +.fa-youtube-square:before { + content: "\f166"; +} +.fa-youtube:before { + content: "\f167"; +} +.fa-xing:before { + content: "\f168"; +} +.fa-xing-square:before { + content: "\f169"; +} +.fa-youtube-play:before { + content: "\f16a"; +} +.fa-dropbox:before { + content: "\f16b"; +} +.fa-stack-overflow:before { + content: "\f16c"; +} +.fa-instagram:before { + content: "\f16d"; +} +.fa-flickr:before { + content: "\f16e"; +} +.fa-adn:before { + content: "\f170"; +} +.fa-bitbucket:before { + content: "\f171"; +} +.fa-bitbucket-square:before { + content: "\f172"; +} +.fa-tumblr:before { + content: "\f173"; +} +.fa-tumblr-square:before { + content: "\f174"; +} +.fa-long-arrow-down:before { + content: "\f175"; +} +.fa-long-arrow-up:before { + content: "\f176"; +} +.fa-long-arrow-left:before { + content: "\f177"; +} +.fa-long-arrow-right:before { + content: "\f178"; +} +.fa-apple:before { + content: "\f179"; +} +.fa-windows:before { + content: "\f17a"; +} +.fa-android:before { + content: "\f17b"; +} +.fa-linux:before { + content: "\f17c"; +} +.fa-dribbble:before { + content: "\f17d"; +} +.fa-skype:before { + content: "\f17e"; +} +.fa-foursquare:before { + content: "\f180"; +} +.fa-trello:before { + content: "\f181"; +} +.fa-female:before { + content: "\f182"; +} +.fa-male:before { + content: "\f183"; +} +.fa-gittip:before, +.fa-gratipay:before { + content: "\f184"; +} +.fa-sun-o:before { + content: "\f185"; +} +.fa-moon-o:before { + content: "\f186"; +} +.fa-archive:before { + content: "\f187"; +} +.fa-bug:before { + content: "\f188"; +} +.fa-vk:before { + content: "\f189"; +} +.fa-weibo:before { + content: "\f18a"; +} +.fa-renren:before { + content: "\f18b"; +} +.fa-pagelines:before { + content: "\f18c"; +} +.fa-stack-exchange:before { + content: "\f18d"; +} +.fa-arrow-circle-o-right:before { + content: "\f18e"; +} +.fa-arrow-circle-o-left:before { + content: "\f190"; +} +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: "\f191"; +} +.fa-dot-circle-o:before { + content: "\f192"; +} +.fa-wheelchair:before { + content: "\f193"; +} +.fa-vimeo-square:before { + content: "\f194"; +} +.fa-turkish-lira:before, +.fa-try:before { + content: "\f195"; +} +.fa-plus-square-o:before { + content: "\f196"; +} +.fa-space-shuttle:before { + content: "\f197"; +} +.fa-slack:before { + content: "\f198"; +} +.fa-envelope-square:before { + content: "\f199"; +} +.fa-wordpress:before { + content: "\f19a"; +} +.fa-openid:before { + content: "\f19b"; +} +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: "\f19c"; +} +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: "\f19d"; +} +.fa-yahoo:before { + content: "\f19e"; +} +.fa-google:before { + content: "\f1a0"; +} +.fa-reddit:before { + content: "\f1a1"; +} +.fa-reddit-square:before { + content: "\f1a2"; +} +.fa-stumbleupon-circle:before { + content: "\f1a3"; +} +.fa-stumbleupon:before { + content: "\f1a4"; +} +.fa-delicious:before { + content: "\f1a5"; +} +.fa-digg:before { + content: "\f1a6"; +} +.fa-pied-piper-pp:before { + content: "\f1a7"; +} +.fa-pied-piper-alt:before { + content: "\f1a8"; +} +.fa-drupal:before { + content: "\f1a9"; +} +.fa-joomla:before { + content: "\f1aa"; +} +.fa-language:before { + content: "\f1ab"; +} +.fa-fax:before { + content: "\f1ac"; +} +.fa-building:before { + content: "\f1ad"; +} +.fa-child:before { + content: "\f1ae"; +} +.fa-paw:before { + content: "\f1b0"; +} +.fa-spoon:before { + content: "\f1b1"; +} +.fa-cube:before { + content: "\f1b2"; +} +.fa-cubes:before { + content: "\f1b3"; +} +.fa-behance:before { + content: "\f1b4"; +} +.fa-behance-square:before { + content: "\f1b5"; +} +.fa-steam:before { + content: "\f1b6"; +} +.fa-steam-square:before { + content: "\f1b7"; +} +.fa-recycle:before { + content: "\f1b8"; +} +.fa-automobile:before, +.fa-car:before { + content: "\f1b9"; +} +.fa-cab:before, +.fa-taxi:before { + content: "\f1ba"; +} +.fa-tree:before { + content: "\f1bb"; +} +.fa-spotify:before { + content: "\f1bc"; +} +.fa-deviantart:before { + content: "\f1bd"; +} +.fa-soundcloud:before { + content: "\f1be"; +} +.fa-database:before { + content: "\f1c0"; +} +.fa-file-pdf-o:before { + content: "\f1c1"; +} +.fa-file-word-o:before { + content: "\f1c2"; +} +.fa-file-excel-o:before { + content: "\f1c3"; +} +.fa-file-powerpoint-o:before { + content: "\f1c4"; +} +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: "\f1c5"; +} +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: "\f1c6"; +} +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: "\f1c7"; +} +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: "\f1c8"; +} +.fa-file-code-o:before { + content: "\f1c9"; +} +.fa-vine:before { + content: "\f1ca"; +} +.fa-codepen:before { + content: "\f1cb"; +} +.fa-jsfiddle:before { + content: "\f1cc"; +} +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: "\f1cd"; +} +.fa-circle-o-notch:before { + content: "\f1ce"; +} +.fa-ra:before, +.fa-resistance:before, +.fa-rebel:before { + content: "\f1d0"; +} +.fa-ge:before, +.fa-empire:before { + content: "\f1d1"; +} +.fa-git-square:before { + content: "\f1d2"; +} +.fa-git:before { + content: "\f1d3"; +} +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: "\f1d4"; +} +.fa-tencent-weibo:before { + content: "\f1d5"; +} +.fa-qq:before { + content: "\f1d6"; +} +.fa-wechat:before, +.fa-weixin:before { + content: "\f1d7"; +} +.fa-send:before, +.fa-paper-plane:before { + content: "\f1d8"; +} +.fa-send-o:before, +.fa-paper-plane-o:before { + content: "\f1d9"; +} +.fa-history:before { + content: "\f1da"; +} +.fa-circle-thin:before { + content: "\f1db"; +} +.fa-header:before { + content: "\f1dc"; +} +.fa-paragraph:before { + content: "\f1dd"; +} +.fa-sliders:before { + content: "\f1de"; +} +.fa-share-alt:before { + content: "\f1e0"; +} +.fa-share-alt-square:before { + content: "\f1e1"; +} +.fa-bomb:before { + content: "\f1e2"; +} +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} +.fa-buysellads:before { + content: "\f20d"; +} +.fa-connectdevelop:before { + content: "\f20e"; +} +.fa-dashcube:before { + content: "\f210"; +} +.fa-forumbee:before { + content: "\f211"; +} +.fa-leanpub:before { + content: "\f212"; +} +.fa-sellsy:before { + content: "\f213"; +} +.fa-shirtsinbulk:before { + content: "\f214"; +} +.fa-simplybuilt:before { + content: "\f215"; +} +.fa-skyatlas:before { + content: "\f216"; +} +.fa-cart-plus:before { + content: "\f217"; +} +.fa-cart-arrow-down:before { + content: "\f218"; +} +.fa-diamond:before { + content: "\f219"; +} +.fa-ship:before { + content: "\f21a"; +} +.fa-user-secret:before { + content: "\f21b"; +} +.fa-motorcycle:before { + content: "\f21c"; +} +.fa-street-view:before { + content: "\f21d"; +} +.fa-heartbeat:before { + content: "\f21e"; +} +.fa-venus:before { + content: "\f221"; +} +.fa-mars:before { + content: "\f222"; +} +.fa-mercury:before { + content: "\f223"; +} +.fa-intersex:before, +.fa-transgender:before { + content: "\f224"; +} +.fa-transgender-alt:before { + content: "\f225"; +} +.fa-venus-double:before { + content: "\f226"; +} +.fa-mars-double:before { + content: "\f227"; +} +.fa-venus-mars:before { + content: "\f228"; +} +.fa-mars-stroke:before { + content: "\f229"; +} +.fa-mars-stroke-v:before { + content: "\f22a"; +} +.fa-mars-stroke-h:before { + content: "\f22b"; +} +.fa-neuter:before { + content: "\f22c"; +} +.fa-genderless:before { + content: "\f22d"; +} +.fa-facebook-official:before { + content: "\f230"; +} +.fa-pinterest-p:before { + content: "\f231"; +} +.fa-whatsapp:before { + content: "\f232"; +} +.fa-server:before { + content: "\f233"; +} +.fa-user-plus:before { + content: "\f234"; +} +.fa-user-times:before { + content: "\f235"; +} +.fa-hotel:before, +.fa-bed:before { + content: "\f236"; +} +.fa-viacoin:before { + content: "\f237"; +} +.fa-train:before { + content: "\f238"; +} +.fa-subway:before { + content: "\f239"; +} +.fa-medium:before { + content: "\f23a"; +} +.fa-yc:before, +.fa-y-combinator:before { + content: "\f23b"; +} +.fa-optin-monster:before { + content: "\f23c"; +} +.fa-opencart:before { + content: "\f23d"; +} +.fa-expeditedssl:before { + content: "\f23e"; +} +.fa-battery-4:before, +.fa-battery:before, +.fa-battery-full:before { + content: "\f240"; +} +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: "\f241"; +} +.fa-battery-2:before, +.fa-battery-half:before { + content: "\f242"; +} +.fa-battery-1:before, +.fa-battery-quarter:before { + content: "\f243"; +} +.fa-battery-0:before, +.fa-battery-empty:before { + content: "\f244"; +} +.fa-mouse-pointer:before { + content: "\f245"; +} +.fa-i-cursor:before { + content: "\f246"; +} +.fa-object-group:before { + content: "\f247"; +} +.fa-object-ungroup:before { + content: "\f248"; +} +.fa-sticky-note:before { + content: "\f249"; +} +.fa-sticky-note-o:before { + content: "\f24a"; +} +.fa-cc-jcb:before { + content: "\f24b"; +} +.fa-cc-diners-club:before { + content: "\f24c"; +} +.fa-clone:before { + content: "\f24d"; +} +.fa-balance-scale:before { + content: "\f24e"; +} +.fa-hourglass-o:before { + content: "\f250"; +} +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: "\f251"; +} +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: "\f252"; +} +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: "\f253"; +} +.fa-hourglass:before { + content: "\f254"; +} +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: "\f255"; +} +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: "\f256"; +} +.fa-hand-scissors-o:before { + content: "\f257"; +} +.fa-hand-lizard-o:before { + content: "\f258"; +} +.fa-hand-spock-o:before { + content: "\f259"; +} +.fa-hand-pointer-o:before { + content: "\f25a"; +} +.fa-hand-peace-o:before { + content: "\f25b"; +} +.fa-trademark:before { + content: "\f25c"; +} +.fa-registered:before { + content: "\f25d"; +} +.fa-creative-commons:before { + content: "\f25e"; +} +.fa-gg:before { + content: "\f260"; +} +.fa-gg-circle:before { + content: "\f261"; +} +.fa-tripadvisor:before { + content: "\f262"; +} +.fa-odnoklassniki:before { + content: "\f263"; +} +.fa-odnoklassniki-square:before { + content: "\f264"; +} +.fa-get-pocket:before { + content: "\f265"; +} +.fa-wikipedia-w:before { + content: "\f266"; +} +.fa-safari:before { + content: "\f267"; +} +.fa-chrome:before { + content: "\f268"; +} +.fa-firefox:before { + content: "\f269"; +} +.fa-opera:before { + content: "\f26a"; +} +.fa-internet-explorer:before { + content: "\f26b"; +} +.fa-tv:before, +.fa-television:before { + content: "\f26c"; +} +.fa-contao:before { + content: "\f26d"; +} +.fa-500px:before { + content: "\f26e"; +} +.fa-amazon:before { + content: "\f270"; +} +.fa-calendar-plus-o:before { + content: "\f271"; +} +.fa-calendar-minus-o:before { + content: "\f272"; +} +.fa-calendar-times-o:before { + content: "\f273"; +} +.fa-calendar-check-o:before { + content: "\f274"; +} +.fa-industry:before { + content: "\f275"; +} +.fa-map-pin:before { + content: "\f276"; +} +.fa-map-signs:before { + content: "\f277"; +} +.fa-map-o:before { + content: "\f278"; +} +.fa-map:before { + content: "\f279"; +} +.fa-commenting:before { + content: "\f27a"; +} +.fa-commenting-o:before { + content: "\f27b"; +} +.fa-houzz:before { + content: "\f27c"; +} +.fa-vimeo:before { + content: "\f27d"; +} +.fa-black-tie:before { + content: "\f27e"; +} +.fa-fonticons:before { + content: "\f280"; +} +.fa-reddit-alien:before { + content: "\f281"; +} +.fa-edge:before { + content: "\f282"; +} +.fa-credit-card-alt:before { + content: "\f283"; +} +.fa-codiepie:before { + content: "\f284"; +} +.fa-modx:before { + content: "\f285"; +} +.fa-fort-awesome:before { + content: "\f286"; +} +.fa-usb:before { + content: "\f287"; +} +.fa-product-hunt:before { + content: "\f288"; +} +.fa-mixcloud:before { + content: "\f289"; +} +.fa-scribd:before { + content: "\f28a"; +} +.fa-pause-circle:before { + content: "\f28b"; +} +.fa-pause-circle-o:before { + content: "\f28c"; +} +.fa-stop-circle:before { + content: "\f28d"; +} +.fa-stop-circle-o:before { + content: "\f28e"; +} +.fa-shopping-bag:before { + content: "\f290"; +} +.fa-shopping-basket:before { + content: "\f291"; +} +.fa-hashtag:before { + content: "\f292"; +} +.fa-bluetooth:before { + content: "\f293"; +} +.fa-bluetooth-b:before { + content: "\f294"; +} +.fa-percent:before { + content: "\f295"; +} +.fa-gitlab:before { + content: "\f296"; +} +.fa-wpbeginner:before { + content: "\f297"; +} +.fa-wpforms:before { + content: "\f298"; +} +.fa-envira:before { + content: "\f299"; +} +.fa-universal-access:before { + content: "\f29a"; +} +.fa-wheelchair-alt:before { + content: "\f29b"; +} +.fa-question-circle-o:before { + content: "\f29c"; +} +.fa-blind:before { + content: "\f29d"; +} +.fa-audio-description:before { + content: "\f29e"; +} +.fa-volume-control-phone:before { + content: "\f2a0"; +} +.fa-braille:before { + content: "\f2a1"; +} +.fa-assistive-listening-systems:before { + content: "\f2a2"; +} +.fa-asl-interpreting:before, +.fa-american-sign-language-interpreting:before { + content: "\f2a3"; +} +.fa-deafness:before, +.fa-hard-of-hearing:before, +.fa-deaf:before { + content: "\f2a4"; +} +.fa-glide:before { + content: "\f2a5"; +} +.fa-glide-g:before { + content: "\f2a6"; +} +.fa-signing:before, +.fa-sign-language:before { + content: "\f2a7"; +} +.fa-low-vision:before { + content: "\f2a8"; +} +.fa-viadeo:before { + content: "\f2a9"; +} +.fa-viadeo-square:before { + content: "\f2aa"; +} +.fa-snapchat:before { + content: "\f2ab"; +} +.fa-snapchat-ghost:before { + content: "\f2ac"; +} +.fa-snapchat-square:before { + content: "\f2ad"; +} +.fa-pied-piper:before { + content: "\f2ae"; +} +.fa-first-order:before { + content: "\f2b0"; +} +.fa-yoast:before { + content: "\f2b1"; +} +.fa-themeisle:before { + content: "\f2b2"; +} +.fa-google-plus-circle:before, +.fa-google-plus-official:before { + content: "\f2b3"; +} +.fa-fa:before, +.fa-font-awesome:before { + content: "\f2b4"; +} +.fa-handshake-o:before { + content: "\f2b5"; +} +.fa-envelope-open:before { + content: "\f2b6"; +} +.fa-envelope-open-o:before { + content: "\f2b7"; +} +.fa-linode:before { + content: "\f2b8"; +} +.fa-address-book:before { + content: "\f2b9"; +} +.fa-address-book-o:before { + content: "\f2ba"; +} +.fa-vcard:before, +.fa-address-card:before { + content: "\f2bb"; +} +.fa-vcard-o:before, +.fa-address-card-o:before { + content: "\f2bc"; +} +.fa-user-circle:before { + content: "\f2bd"; +} +.fa-user-circle-o:before { + content: "\f2be"; +} +.fa-user-o:before { + content: "\f2c0"; +} +.fa-id-badge:before { + content: "\f2c1"; +} +.fa-drivers-license:before, +.fa-id-card:before { + content: "\f2c2"; +} +.fa-drivers-license-o:before, +.fa-id-card-o:before { + content: "\f2c3"; +} +.fa-quora:before { + content: "\f2c4"; +} +.fa-free-code-camp:before { + content: "\f2c5"; +} +.fa-telegram:before { + content: "\f2c6"; +} +.fa-thermometer-4:before, +.fa-thermometer:before, +.fa-thermometer-full:before { + content: "\f2c7"; +} +.fa-thermometer-3:before, +.fa-thermometer-three-quarters:before { + content: "\f2c8"; +} +.fa-thermometer-2:before, +.fa-thermometer-half:before { + content: "\f2c9"; +} +.fa-thermometer-1:before, +.fa-thermometer-quarter:before { + content: "\f2ca"; +} +.fa-thermometer-0:before, +.fa-thermometer-empty:before { + content: "\f2cb"; +} +.fa-shower:before { + content: "\f2cc"; +} +.fa-bathtub:before, +.fa-s15:before, +.fa-bath:before { + content: "\f2cd"; +} +.fa-podcast:before { + content: "\f2ce"; +} +.fa-window-maximize:before { + content: "\f2d0"; +} +.fa-window-minimize:before { + content: "\f2d1"; +} +.fa-window-restore:before { + content: "\f2d2"; +} +.fa-times-rectangle:before, +.fa-window-close:before { + content: "\f2d3"; +} +.fa-times-rectangle-o:before, +.fa-window-close-o:before { + content: "\f2d4"; +} +.fa-bandcamp:before { + content: "\f2d5"; +} +.fa-grav:before { + content: "\f2d6"; +} +.fa-etsy:before { + content: "\f2d7"; +} +.fa-imdb:before { + content: "\f2d8"; +} +.fa-ravelry:before { + content: "\f2d9"; +} +.fa-eercast:before { + content: "\f2da"; +} +.fa-microchip:before { + content: "\f2db"; +} +.fa-snowflake-o:before { + content: "\f2dc"; +} +.fa-superpowers:before { + content: "\f2dd"; +} +.fa-wpexplorer:before { + content: "\f2de"; +} +.fa-meetup:before { + content: "\f2e0"; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} diff --git a/assets/images/avatars/gallery/index.htm b/assets/images/avatars/gallery/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/avatars/gallery/index.htm +++ b/assets/images/avatars/gallery/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/avatars/index.htm b/assets/images/avatars/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/avatars/index.htm +++ b/assets/images/avatars/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/avatars/upload/index.htm b/assets/images/avatars/upload/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/avatars/upload/index.htm +++ b/assets/images/avatars/upload/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/icons/index.htm b/assets/images/icons/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/icons/index.htm +++ b/assets/images/icons/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/icons/misc/index.htm b/assets/images/icons/misc/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/icons/misc/index.htm +++ b/assets/images/icons/misc/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/icons/smile/index.htm b/assets/images/icons/smile/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/icons/smile/index.htm +++ b/assets/images/icons/smile/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/index.htm b/assets/images/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/index.htm +++ b/assets/images/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/images/ranks/index.htm b/assets/images/ranks/index.htm index ee1f723..87bdb24 100644 --- a/assets/images/ranks/index.htm +++ b/assets/images/ranks/index.htm @@ -1,10 +1,8 @@ - - - - + + + + - - - + diff --git a/assets/javascript/ajax.js b/assets/javascript/ajax.js index db2a573..15c52f4 100644 --- a/assets/javascript/ajax.js +++ b/assets/javascript/ajax.js @@ -1,405 +1,461 @@ /* global phpbb */ -(function($) { // Avoid conflicts with other libraries - -'use strict'; - -// This callback will mark all forum icons read -phpbb.addAjaxCallback('mark_forums_read', function(res) { - var readTitle = res.NO_UNREAD_POSTS; - var unreadTitle = res.UNREAD_POSTS; - var iconsArray = { - forum_unread: 'forum_read', - forum_unread_subforum: 'forum_read_subforum', - forum_unread_locked: 'forum_read_locked' - }; - - $('li.row').find('dl[class*="forum_unread"]').each(function() { - var $this = $(this); - - $.each(iconsArray, function(unreadClass, readClass) { - if ($this.hasClass(unreadClass)) { - $this.removeClass(unreadClass).addClass(readClass); - } - }); - $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); - }); - - // Mark subforums read - $('a.subforum[class*="unread"]').removeClass('unread').addClass('read').children('.icon.icon-red').removeClass('icon-red').addClass('icon-blue'); - - // Mark topics read if we are watching a category and showing active topics - if ($('#active_topics').length) { - phpbb.ajaxCallbacks.mark_topics_read.call(this, res, false); - } - - // Update mark forums read links - $('[data-ajax="mark_forums_read"]').attr('href', res.U_MARK_FORUMS); - - phpbb.closeDarkenWrapper(3000); -}); - -/** -* This callback will mark all topic icons read -* -* @param {bool} [update_topic_links=true] Whether "Mark topics read" links -* should be updated. Defaults to true. -*/ -phpbb.addAjaxCallback('mark_topics_read', function(res, updateTopicLinks) { - var readTitle = res.NO_UNREAD_POSTS; - var unreadTitle = res.UNREAD_POSTS; - var iconsArray = { - global_unread: 'global_read', - announce_unread: 'announce_read', - sticky_unread: 'sticky_read', - topic_unread: 'topic_read' - }; - var iconsState = ['', '_hot', '_hot_mine', '_locked', '_locked_mine', '_mine']; - var unreadClassSelectors; - var classMap = {}; - var classNames = []; - - if (typeof updateTopicLinks === 'undefined') { - updateTopicLinks = true; - } - - $.each(iconsArray, function(unreadClass, readClass) { - $.each(iconsState, function(key, value) { - // Only topics can be hot - if ((value === '_hot' || value === '_hot_mine') && unreadClass !== 'topic_unread') { - return true; - } - classMap[unreadClass + value] = readClass + value; - classNames.push(unreadClass + value); - }); - }); - - unreadClassSelectors = '.' + classNames.join(',.'); - - $('li.row').find(unreadClassSelectors).each(function() { - var $this = $(this); - $.each(classMap, function(unreadClass, readClass) { - if ($this.hasClass(unreadClass)) { - $this.removeClass(unreadClass).addClass(readClass); - } - }); - $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); - }); - - // Remove link to first unread post - $('a.unread').has('.icon-red').remove(); - - // Update mark topics read links - if (updateTopicLinks) { - $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS); - } - - phpbb.closeDarkenWrapper(3000); -}); - -// This callback will mark all notifications read -phpbb.addAjaxCallback('notification.mark_all_read', function(res) { - if (typeof res.success !== 'undefined') { - phpbb.markNotifications($('#notification_list li.bg2'), 0); - phpbb.closeDarkenWrapper(3000); - } -}); - -// This callback will mark a notification read -phpbb.addAjaxCallback('notification.mark_read', function(res) { - if (typeof res.success !== 'undefined') { - var unreadCount = Number($('#notification_list_button strong').html()) - 1; - phpbb.markNotifications($(this).parent('li.bg2'), unreadCount); - } -}); - -/** - * Mark notification popup rows as read. - * - * @param {jQuery} $popup jQuery object(s) to mark read. - * @param {int} unreadCount The new unread notifications count. - */ -phpbb.markNotifications = function($popup, unreadCount) { - // Remove the unread status. - $popup.removeClass('bg2'); - $popup.find('a.mark_read').remove(); - - // Update the notification link to the real URL. - $popup.each(function() { - var link = $(this).find('a'); - link.attr('href', link.attr('data-real-url')); - }); - - // Update the unread count. - $('strong', '#notification_list_button').html(unreadCount); - // Remove the Mark all read link and hide notification count if there are no unread notifications. - if (!unreadCount) { - $('#mark_all_notifications').remove(); - $('#notification_list_button > strong').addClass('hidden'); - } - - // Update page title - var $title = $('title'); - var originalTitle = $title.text().replace(/(\((\d+)\))/, ''); - $title.text((unreadCount ? '(' + unreadCount + ')' : '') + originalTitle); -}; - -// This callback finds the post from the delete link, and removes it. -phpbb.addAjaxCallback('post_delete', function() { - var $this = $(this), - postId; - - if ($this.attr('data-refresh') === undefined) { - postId = $this[0].href.split('&p=')[1]; - var post = $this.parents('#p' + postId).css('pointer-events', 'none'); - if (post.hasClass('bg1') || post.hasClass('bg2')) { - var posts1 = post.nextAll('.bg1'); - post.nextAll('.bg2').removeClass('bg2').addClass('bg1'); - posts1.removeClass('bg1').addClass('bg2'); - } - post.fadeOut(function() { - $(this).remove(); - }); - } -}); - -// This callback removes the approve / disapprove div or link. -phpbb.addAjaxCallback('post_visibility', function(res) { - var remove = (res.visible) ? $(this) : $(this).parents('.post'); - $(remove).css('pointer-events', 'none').fadeOut(function() { - $(this).remove(); - }); - - if (res.visible) { - // Remove the "Deleted by" message from the post on restoring. - remove.parents('.post').find('.post_deleted_msg').css('pointer-events', 'none').fadeOut(function() { - $(this).remove(); - }); - } -}); - -// This removes the parent row of the link or form that fired the callback. -phpbb.addAjaxCallback('row_delete', function() { - $(this).parents('tr').remove(); -}); - -// This handles friend / foe additions removals. -phpbb.addAjaxCallback('zebra', function(res) { - var zebra; - - if (res.success) { - zebra = $('.zebra'); - zebra.first().html(res.MESSAGE_TEXT); - zebra.not(':first').html(' ').prev().html(' '); - } -}); - -/** - * This callback updates the poll results after voting. - */ -phpbb.addAjaxCallback('vote_poll', function(res) { - if (typeof res.success !== 'undefined') { - var poll = $(this).closest('.topic_poll'); - var panel = poll.find('.panel'); - var resultsVisible = poll.find('dl:first-child .resultbar').is(':visible'); - var mostVotes = 0; - - // Set min-height to prevent the page from jumping when the content changes - var updatePanelHeight = function (height) { - height = (typeof height === 'undefined') ? panel.find('.inner').outerHeight() : height; - panel.css('min-height', height); - }; - updatePanelHeight(); - - // Remove the View results link - if (!resultsVisible) { - poll.find('.poll_view_results').hide(500); - } - - if (!res.can_vote) { - poll.find('.polls, .poll_max_votes, .poll_vote, .poll_option_select').fadeOut(500, function () { - poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(); - }); - } else { - // If the user can still vote, simply slide down the results - poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); - } - - // Get the votes count of the highest poll option - poll.find('[data-poll-option-id]').each(function() { - var option = $(this); - var optionId = option.attr('data-poll-option-id'); - mostVotes = (res.vote_counts[optionId] >= mostVotes) ? res.vote_counts[optionId] : mostVotes; - }); - - // Update the total votes count - poll.find('.poll_total_vote_cnt').html(res.total_votes); - - // Update each option - poll.find('[data-poll-option-id]').each(function() { - var $this = $(this); - var optionId = $this.attr('data-poll-option-id'); - var voted = (typeof res.user_votes[optionId] !== 'undefined'); - var mostVoted = (res.vote_counts[optionId] === mostVotes); - var percent = (!res.total_votes) ? 0 : Math.round((res.vote_counts[optionId] / res.total_votes) * 100); - var percentRel = (mostVotes === 0) ? 0 : Math.round((res.vote_counts[optionId] / mostVotes) * 100); - var altText; - - altText = $this.attr('data-alt-text'); - if (voted) { - $this.attr('title', $.trim(altText)); - } else { - $this.attr('title', ''); - }; - $this.toggleClass('voted', voted); - $this.toggleClass('most-votes', mostVoted); - - // Update the bars - var bar = $this.find('.resultbar div'); - var barTimeLapse = (res.can_vote) ? 500 : 1500; - var newBarClass = (percent === 100) ? 'pollbar5' : 'pollbar' + (Math.floor(percent / 20) + 1); - - setTimeout(function () { - bar.animate({ width: percentRel + '%' }, 500) - .removeClass('pollbar1 pollbar2 pollbar3 pollbar4 pollbar5') - .addClass(newBarClass) - .html(res.vote_counts[optionId]); - - var percentText = percent ? percent + '%' : res.NO_VOTES; - $this.find('.poll_option_percent').html(percentText); - }, barTimeLapse); - }); - - if (!res.can_vote) { - poll.find('.polls').delay(400).fadeIn(500); - } - - // Display "Your vote has been cast." message. Disappears after 5 seconds. - var confirmationDelay = (res.can_vote) ? 300 : 900; - poll.find('.vote-submitted').delay(confirmationDelay).slideDown(200, function() { - if (resultsVisible) { - updatePanelHeight(); - } - - $(this).delay(5000).fadeOut(500, function() { - resizePanel(300); - }); - }); - - // Remove the gap resulting from removing options - setTimeout(function() { - resizePanel(500); - }, 1500); - - var resizePanel = function (time) { - var panelHeight = panel.height(); - var innerHeight = panel.find('.inner').outerHeight(); - - if (panelHeight !== innerHeight) { - panel.css({ minHeight: '', height: panelHeight }) - .animate({ height: innerHeight }, time, function () { - panel.css({ minHeight: innerHeight, height: '' }); - }); - } - }; - } -}); - -/** - * Show poll results when clicking View results link. - */ -$('.poll_view_results a').click(function(e) { - // Do not follow the link - e.preventDefault(); - - var $poll = $(this).parents('.topic_poll'); - - $poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); - $poll.find('.poll_view_results').hide(500); -}); - -$('[data-ajax]').each(function() { - var $this = $(this); - var ajax = $this.attr('data-ajax'); - var filter = $this.attr('data-filter'); - - if (ajax !== 'false') { - var fn = (ajax !== 'true') ? ajax : null; - filter = (filter !== undefined) ? phpbb.getFunctionByName(filter) : null; - - phpbb.ajaxify({ - selector: this, - refresh: $this.attr('data-refresh') !== undefined, - filter: filter, - callback: fn - }); - } -}); - - -/** - * This simply appends #preview to the action of the - * QR action when you click the Full Editor & Preview button - */ -$('#qr_full_editor').click(function() { - $('#qr_postform').attr('action', function(i, val) { - return val + '#preview'; - }); -}); - - -/** - * Make the display post links to use JS - */ -$('.display_post').click(function(e) { - // Do not follow the link - e.preventDefault(); - - var postId = $(this).attr('data-post-id'); - $('#post_content' + postId).show(); - $('#profile' + postId).show(); - $('#post_hidden' + postId).hide(); -}); - -/** - * Display hidden post on post review page - */ -$('.display_post_review').on('click', function(e) { - e.preventDefault(); - - let $displayPostLink = $(this); - $displayPostLink.closest('.post-ignore').removeClass('post-ignore'); - $displayPostLink.hide(); -}); - -/** -* Toggle the member search panel in memberlist.php. -* -* If user returns to search page after viewing results the search panel is automatically displayed. -* In any case the link will toggle the display status of the search panel and link text will be -* appropriately changed based on the status of the search panel. -*/ -$('#member_search').click(function () { - var $memberlistSearch = $('#memberlist_search'); - - $memberlistSearch.slideToggle('fast'); - phpbb.ajaxCallbacks.alt_text.call(this); - - // Focus on the username textbox if it's available and displayed - if ($memberlistSearch.is(':visible')) { - $('#username').focus(); - } - return false; -}); - -/** -* Automatically resize textarea -*/ -$(function() { - var $textarea = $('textarea:not(#message-box textarea, .no-auto-resize)'); - phpbb.resizeTextArea($textarea, { minHeight: 75, maxHeight: 250 }); - phpbb.resizeTextArea($('textarea', '#message-box')); -}); - - +(function ($) { + // Avoid conflicts with other libraries + + "use strict"; + + // This callback will mark all forum icons read + phpbb.addAjaxCallback("mark_forums_read", function (res) { + var readTitle = res.NO_UNREAD_POSTS; + var unreadTitle = res.UNREAD_POSTS; + var iconsArray = { + forum_unread: "forum_read", + forum_unread_subforum: "forum_read_subforum", + forum_unread_locked: "forum_read_locked", + }; + + $("li.row") + .find('dl[class*="forum_unread"]') + .each(function () { + var $this = $(this); + + $.each(iconsArray, function (unreadClass, readClass) { + if ($this.hasClass(unreadClass)) { + $this.removeClass(unreadClass).addClass(readClass); + } + }); + $this + .children('dt[title="' + unreadTitle + '"]') + .attr("title", readTitle); + }); + + // Mark subforums read + $('a.subforum[class*="unread"]') + .removeClass("unread") + .addClass("read") + .children(".icon.icon-red") + .removeClass("icon-red") + .addClass("icon-blue"); + + // Mark topics read if we are watching a category and showing active topics + if ($("#active_topics").length) { + phpbb.ajaxCallbacks.mark_topics_read.call(this, res, false); + } + + // Update mark forums read links + $('[data-ajax="mark_forums_read"]').attr("href", res.U_MARK_FORUMS); + + phpbb.closeDarkenWrapper(3000); + }); + + /** + * This callback will mark all topic icons read + * + * @param {bool} [update_topic_links=true] Whether "Mark topics read" links + * should be updated. Defaults to true. + */ + phpbb.addAjaxCallback("mark_topics_read", function (res, updateTopicLinks) { + var readTitle = res.NO_UNREAD_POSTS; + var unreadTitle = res.UNREAD_POSTS; + var iconsArray = { + global_unread: "global_read", + announce_unread: "announce_read", + sticky_unread: "sticky_read", + topic_unread: "topic_read", + }; + var iconsState = [ + "", + "_hot", + "_hot_mine", + "_locked", + "_locked_mine", + "_mine", + ]; + var unreadClassSelectors; + var classMap = {}; + var classNames = []; + + if (typeof updateTopicLinks === "undefined") { + updateTopicLinks = true; + } + + $.each(iconsArray, function (unreadClass, readClass) { + $.each(iconsState, function (key, value) { + // Only topics can be hot + if ( + (value === "_hot" || value === "_hot_mine") && + unreadClass !== "topic_unread" + ) { + return true; + } + classMap[unreadClass + value] = readClass + value; + classNames.push(unreadClass + value); + }); + }); + + unreadClassSelectors = "." + classNames.join(",."); + + $("li.row") + .find(unreadClassSelectors) + .each(function () { + var $this = $(this); + $.each(classMap, function (unreadClass, readClass) { + if ($this.hasClass(unreadClass)) { + $this.removeClass(unreadClass).addClass(readClass); + } + }); + $this + .children('dt[title="' + unreadTitle + '"]') + .attr("title", readTitle); + }); + + // Remove link to first unread post + $("a.unread").has(".icon-red").remove(); + + // Update mark topics read links + if (updateTopicLinks) { + $('[data-ajax="mark_topics_read"]').attr("href", res.U_MARK_TOPICS); + } + + phpbb.closeDarkenWrapper(3000); + }); + + // This callback will mark all notifications read + phpbb.addAjaxCallback("notification.mark_all_read", function (res) { + if (typeof res.success !== "undefined") { + phpbb.markNotifications($("#notification_list li.bg2"), 0); + phpbb.closeDarkenWrapper(3000); + } + }); + + // This callback will mark a notification read + phpbb.addAjaxCallback("notification.mark_read", function (res) { + if (typeof res.success !== "undefined") { + var unreadCount = + Number($("#notification_list_button strong").html()) - 1; + phpbb.markNotifications($(this).parent("li.bg2"), unreadCount); + } + }); + + /** + * Mark notification popup rows as read. + * + * @param {jQuery} $popup jQuery object(s) to mark read. + * @param {int} unreadCount The new unread notifications count. + */ + phpbb.markNotifications = function ($popup, unreadCount) { + // Remove the unread status. + $popup.removeClass("bg2"); + $popup.find("a.mark_read").remove(); + + // Update the notification link to the real URL. + $popup.each(function () { + var link = $(this).find("a"); + link.attr("href", link.attr("data-real-url")); + }); + + // Update the unread count. + $("strong", "#notification_list_button").html(unreadCount); + // Remove the Mark all read link and hide notification count if there are no unread notifications. + if (!unreadCount) { + $("#mark_all_notifications").remove(); + $("#notification_list_button > strong").addClass("hidden"); + } + + // Update page title + var $title = $("title"); + var originalTitle = $title.text().replace(/(\((\d+)\))/, ""); + $title.text((unreadCount ? "(" + unreadCount + ")" : "") + originalTitle); + }; + + // This callback finds the post from the delete link, and removes it. + phpbb.addAjaxCallback("post_delete", function () { + var $this = $(this), + postId; + + if ($this.attr("data-refresh") === undefined) { + postId = $this[0].href.split("&p=")[1]; + var post = $this.parents("#p" + postId).css("pointer-events", "none"); + if (post.hasClass("bg1") || post.hasClass("bg2")) { + var posts1 = post.nextAll(".bg1"); + post.nextAll(".bg2").removeClass("bg2").addClass("bg1"); + posts1.removeClass("bg1").addClass("bg2"); + } + post.fadeOut(function () { + $(this).remove(); + }); + } + }); + + // This callback removes the approve / disapprove div or link. + phpbb.addAjaxCallback("post_visibility", function (res) { + var remove = res.visible ? $(this) : $(this).parents(".post"); + $(remove) + .css("pointer-events", "none") + .fadeOut(function () { + $(this).remove(); + }); + + if (res.visible) { + // Remove the "Deleted by" message from the post on restoring. + remove + .parents(".post") + .find(".post_deleted_msg") + .css("pointer-events", "none") + .fadeOut(function () { + $(this).remove(); + }); + } + }); + + // This removes the parent row of the link or form that fired the callback. + phpbb.addAjaxCallback("row_delete", function () { + $(this).parents("tr").remove(); + }); + + // This handles friend / foe additions removals. + phpbb.addAjaxCallback("zebra", function (res) { + var zebra; + + if (res.success) { + zebra = $(".zebra"); + zebra.first().html(res.MESSAGE_TEXT); + zebra.not(":first").html(" ").prev().html(" "); + } + }); + + /** + * This callback updates the poll results after voting. + */ + phpbb.addAjaxCallback("vote_poll", function (res) { + if (typeof res.success !== "undefined") { + var poll = $(this).closest(".topic_poll"); + var panel = poll.find(".panel"); + var resultsVisible = poll + .find("dl:first-child .resultbar") + .is(":visible"); + var mostVotes = 0; + + // Set min-height to prevent the page from jumping when the content changes + var updatePanelHeight = function (height) { + height = + typeof height === "undefined" + ? panel.find(".inner").outerHeight() + : height; + panel.css("min-height", height); + }; + updatePanelHeight(); + + // Remove the View results link + if (!resultsVisible) { + poll.find(".poll_view_results").hide(500); + } + + if (!res.can_vote) { + poll + .find(".polls, .poll_max_votes, .poll_vote, .poll_option_select") + .fadeOut(500, function () { + poll + .find(".resultbar, .poll_option_percent, .poll_total_votes") + .show(); + }); + } else { + // If the user can still vote, simply slide down the results + poll + .find(".resultbar, .poll_option_percent, .poll_total_votes") + .show(500); + } + + // Get the votes count of the highest poll option + poll.find("[data-poll-option-id]").each(function () { + var option = $(this); + var optionId = option.attr("data-poll-option-id"); + mostVotes = + res.vote_counts[optionId] >= mostVotes + ? res.vote_counts[optionId] + : mostVotes; + }); + + // Update the total votes count + poll.find(".poll_total_vote_cnt").html(res.total_votes); + + // Update each option + poll.find("[data-poll-option-id]").each(function () { + var $this = $(this); + var optionId = $this.attr("data-poll-option-id"); + var voted = typeof res.user_votes[optionId] !== "undefined"; + var mostVoted = res.vote_counts[optionId] === mostVotes; + var percent = !res.total_votes + ? 0 + : Math.round((res.vote_counts[optionId] / res.total_votes) * 100); + var percentRel = + mostVotes === 0 + ? 0 + : Math.round((res.vote_counts[optionId] / mostVotes) * 100); + var altText; + + altText = $this.attr("data-alt-text"); + if (voted) { + $this.attr("title", $.trim(altText)); + } else { + $this.attr("title", ""); + } + $this.toggleClass("voted", voted); + $this.toggleClass("most-votes", mostVoted); + + // Update the bars + var bar = $this.find(".resultbar div"); + var barTimeLapse = res.can_vote ? 500 : 1500; + var newBarClass = + percent === 100 + ? "pollbar5" + : "pollbar" + (Math.floor(percent / 20) + 1); + + setTimeout(function () { + bar + .animate({ width: percentRel + "%" }, 500) + .removeClass("pollbar1 pollbar2 pollbar3 pollbar4 pollbar5") + .addClass(newBarClass) + .html(res.vote_counts[optionId]); + + var percentText = percent ? percent + "%" : res.NO_VOTES; + $this.find(".poll_option_percent").html(percentText); + }, barTimeLapse); + }); + + if (!res.can_vote) { + poll.find(".polls").delay(400).fadeIn(500); + } + + // Display "Your vote has been cast." message. Disappears after 5 seconds. + var confirmationDelay = res.can_vote ? 300 : 900; + poll + .find(".vote-submitted") + .delay(confirmationDelay) + .slideDown(200, function () { + if (resultsVisible) { + updatePanelHeight(); + } + + $(this) + .delay(5000) + .fadeOut(500, function () { + resizePanel(300); + }); + }); + + // Remove the gap resulting from removing options + setTimeout(function () { + resizePanel(500); + }, 1500); + + var resizePanel = function (time) { + var panelHeight = panel.height(); + var innerHeight = panel.find(".inner").outerHeight(); + + if (panelHeight !== innerHeight) { + panel + .css({ minHeight: "", height: panelHeight }) + .animate({ height: innerHeight }, time, function () { + panel.css({ minHeight: innerHeight, height: "" }); + }); + } + }; + } + }); + + /** + * Show poll results when clicking View results link. + */ + $(".poll_view_results a").click(function (e) { + // Do not follow the link + e.preventDefault(); + + var $poll = $(this).parents(".topic_poll"); + + $poll.find(".resultbar, .poll_option_percent, .poll_total_votes").show(500); + $poll.find(".poll_view_results").hide(500); + }); + + $("[data-ajax]").each(function () { + var $this = $(this); + var ajax = $this.attr("data-ajax"); + var filter = $this.attr("data-filter"); + + if (ajax !== "false") { + var fn = ajax !== "true" ? ajax : null; + filter = filter !== undefined ? phpbb.getFunctionByName(filter) : null; + + phpbb.ajaxify({ + selector: this, + refresh: $this.attr("data-refresh") !== undefined, + filter: filter, + callback: fn, + }); + } + }); + + /** + * This simply appends #preview to the action of the + * QR action when you click the Full Editor & Preview button + */ + $("#qr_full_editor").click(function () { + $("#qr_postform").attr("action", function (i, val) { + return val + "#preview"; + }); + }); + + /** + * Make the display post links to use JS + */ + $(".display_post").click(function (e) { + // Do not follow the link + e.preventDefault(); + + var postId = $(this).attr("data-post-id"); + $("#post_content" + postId).show(); + $("#profile" + postId).show(); + $("#post_hidden" + postId).hide(); + }); + + /** + * Display hidden post on post review page + */ + $(".display_post_review").on("click", function (e) { + e.preventDefault(); + + let $displayPostLink = $(this); + $displayPostLink.closest(".post-ignore").removeClass("post-ignore"); + $displayPostLink.hide(); + }); + + /** + * Toggle the member search panel in memberlist.php. + * + * If user returns to search page after viewing results the search panel is automatically displayed. + * In any case the link will toggle the display status of the search panel and link text will be + * appropriately changed based on the status of the search panel. + */ + $("#member_search").click(function () { + var $memberlistSearch = $("#memberlist_search"); + + $memberlistSearch.slideToggle("fast"); + phpbb.ajaxCallbacks.alt_text.call(this); + + // Focus on the username textbox if it's available and displayed + if ($memberlistSearch.is(":visible")) { + $("#username").focus(); + } + return false; + }); + + /** + * Automatically resize textarea + */ + $(function () { + var $textarea = $("textarea:not(#message-box textarea, .no-auto-resize)"); + phpbb.resizeTextArea($textarea, { minHeight: 75, maxHeight: 250 }); + phpbb.resizeTextArea($("textarea", "#message-box")); + }); })(jQuery); // Avoid conflicts with other libraries diff --git a/assets/javascript/core.js b/assets/javascript/core.js index f9dcc2b..45f983b 100644 --- a/assets/javascript/core.js +++ b/assets/javascript/core.js @@ -3,1093 +3,1160 @@ var phpbb = {}; phpbb.alertTime = 100; -(function($) { // Avoid conflicts with other libraries - -'use strict'; - -// define a couple constants for keydown functions. -var keymap = { - TAB: 9, - ENTER: 13, - ESC: 27, - ARROW_UP: 38, - ARROW_DOWN: 40 -}; - -var $dark = $('#darkenwrapper'); -var $loadingIndicator; -var phpbbAlertTimer = null; - -phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined'); - -// Add ajax pre-filter to prevent cross-domain script execution -$.ajaxPrefilter(function(s) { - if (s.crossDomain) { - s.contents.script = false; - } -}); - -/** - * Display a loading screen - * - * @returns {object} Returns loadingIndicator. - */ -phpbb.loadingIndicator = function() { - if (!$loadingIndicator) { - $loadingIndicator = $('
', { - 'id': 'loading_indicator', - 'class': 'loading_indicator' - }); - $loadingIndicator.appendTo('#page-footer'); - } - - if (!$loadingIndicator.is(':visible')) { - $loadingIndicator.fadeIn(phpbb.alertTime); - // Wait 60 seconds and display an error if nothing has been returned by then. - phpbb.clearLoadingTimeout(); - phpbbAlertTimer = setTimeout(function() { - phpbb.showTimeoutMessage(); - }, 60000); - } - - return $loadingIndicator; -}; - -/** - * Show timeout message - */ -phpbb.showTimeoutMessage = function () { - var $alert = $('#phpbb_alert'); - - if ($loadingIndicator.is(':visible')) { - phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req')); - } -}; - -/** - * Clear loading alert timeout -*/ -phpbb.clearLoadingTimeout = function() { - if (phpbbAlertTimer !== null) { - clearTimeout(phpbbAlertTimer); - phpbbAlertTimer = null; - } -}; - - -/** -* Close popup alert after a specified delay -* -* @param {int} delay Delay in ms until darkenwrapper's click event is triggered -*/ -phpbb.closeDarkenWrapper = function(delay) { - phpbbAlertTimer = setTimeout(function() { - $('#darkenwrapper').trigger('click'); - }, delay); -}; - -/** - * Display a simple alert similar to JSs native alert(). - * - * You can only call one alert or confirm box at any one time. - * - * @param {string} title Title of the message, eg "Information" (HTML). - * @param {string} msg Message to display (HTML). - * - * @returns {object} Returns the div created. - */ -phpbb.alert = function(title, msg) { - var $alert = $('#phpbb_alert'); - $alert.find('.alert_title').html(title); - $alert.find('.alert_text').html(msg); - - $(document).on('keydown.phpbb.alert', function(e) { - if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { - phpbb.alert.close($alert, true); - e.preventDefault(); - e.stopPropagation(); - } - }); - phpbb.alert.open($alert); - - return $alert; -}; - -/** -* Handler for opening an alert box. -* -* @param {jQuery} $alert jQuery object. -*/ -phpbb.alert.open = function($alert) { - if (!$dark.is(':visible')) { - $dark.fadeIn(phpbb.alertTime); - } - - if ($loadingIndicator && $loadingIndicator.is(':visible')) { - $loadingIndicator.fadeOut(phpbb.alertTime, function() { - $dark.append($alert); - $alert.fadeIn(phpbb.alertTime); - }); - } else if ($dark.is(':visible')) { - $dark.append($alert); - $alert.fadeIn(phpbb.alertTime); - } else { - $dark.append($alert); - $alert.show(); - $dark.fadeIn(phpbb.alertTime); - } - - $alert.on('click', function(e) { - e.stopPropagation(); - }); - - $dark.one('click', function(e) { - phpbb.alert.close($alert, true); - e.preventDefault(); - e.stopPropagation(); - }); - - $alert.find('.alert_close').one('click', function(e) { - phpbb.alert.close($alert, true); - e.preventDefault(); - }); -}; - -/** -* Handler for closing an alert box. -* -* @param {jQuery} $alert jQuery object. -* @param {bool} fadedark Whether to remove dark background. -*/ -phpbb.alert.close = function($alert, fadedark) { - var $fade = (fadedark) ? $dark : $alert; - - $fade.fadeOut(phpbb.alertTime, function() { - $alert.hide(); - }); - - $alert.find('.alert_close').off('click'); - $(document).off('keydown.phpbb.alert'); -}; - -/** - * Display a simple yes / no box to the user. - * - * You can only call one alert or confirm box at any one time. - * - * @param {string} msg Message to display (HTML). - * @param {function} callback Callback. Bool param, whether the user pressed - * yes or no (or whatever their language is). - * @param {bool} fadedark Remove the dark background when done? Defaults - * to yes. - * - * @returns {object} Returns the div created. - */ -phpbb.confirm = function(msg, callback, fadedark) { - var $confirmDiv = $('#phpbb_confirm'); - $confirmDiv.find('.alert_text').html(msg); - fadedark = typeof fadedark !== 'undefined' ? fadedark : true; - - $(document).on('keydown.phpbb.alert', function(e) { - if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { - var name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel'; - - $('input[name="' + name + '"]').trigger('click'); - e.preventDefault(); - e.stopPropagation(); - } - }); - - $confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) { - var confirmed = this.name === 'confirm'; - - callback(confirmed); - $confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox'); - phpbb.alert.close($confirmDiv, fadedark || !confirmed); - - e.preventDefault(); - e.stopPropagation(); - }); - - phpbb.alert.open($confirmDiv); - - return $confirmDiv; -}; - -/** - * Turn a querystring into an array. - * - * @argument {string} string The querystring to parse. - * @returns {object} The object created. - */ -phpbb.parseQuerystring = function(string) { - var params = {}, i, split; - - string = string.split('&'); - for (i = 0; i < string.length; i++) { - split = string[i].split('='); - params[split[0]] = decodeURIComponent(split[1]); - } - return params; -}; - - -/** - * Makes a link use AJAX instead of loading an entire page. - * - * This function will work for links (both standard links and links which - * invoke confirm_box) and forms. It will be called automatically for links - * and forms with the data-ajax attribute set, and will call the necessary - * callback. - * - * For more info, view the following page on the phpBB wiki: - * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify - * - * @param {object} options Options. - */ -phpbb.ajaxify = function(options) { - var $elements = $(options.selector), - refresh = options.refresh, - callback = options.callback, - overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true, - isForm = $elements.is('form'), - isText = $elements.is('input[type="text"], textarea'), - eventName; - - if (isForm) { - eventName = 'submit'; - } else if (isText) { - eventName = 'keyup'; - } else { - eventName = 'click'; - } - - $elements.on(eventName, function(event) { - var action, method, data, submit, that = this, $this = $(this); - - if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') { - return; - } - - /** - * Handler for AJAX errors - */ - function errorHandler(jqXHR, textStatus, errorThrown) { - if (typeof console !== 'undefined' && console.log) { - console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); - } - phpbb.clearLoadingTimeout(); - var responseText, errorText = false; - try { - responseText = JSON.parse(jqXHR.responseText); - responseText = responseText.message; - } catch (e) {} - if (typeof responseText === 'string' && responseText.length > 0) { - errorText = responseText; - } else if (typeof errorThrown === 'string' && errorThrown.length > 0) { - errorText = errorThrown; - } else { - errorText = $dark.attr('data-ajax-error-text-' + textStatus); - if (typeof errorText !== 'string' || !errorText.length) { - errorText = $dark.attr('data-ajax-error-text'); - } - } - phpbb.alert($dark.attr('data-ajax-error-title'), errorText); - } - - /** - * This is a private function used to handle the callbacks, refreshes - * and alert. It calls the callback, refreshes the page if necessary, and - * displays an alert to the user and removes it after an amount of time. - * - * It cannot be called from outside this function, and is purely here to - * avoid repetition of code. - * - * @param {object} res The object sent back by the server. - */ - function returnHandler(res) { - var alert; - - phpbb.clearLoadingTimeout(); - - // Is a confirmation required? - if (typeof res.S_CONFIRM_ACTION === 'undefined') { - // If a confirmation is not required, display an alert and call the - // callbacks. - if (typeof res.MESSAGE_TITLE !== 'undefined') { - alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); - } else { - $dark.fadeOut(phpbb.alertTime); - - if ($loadingIndicator) { - $loadingIndicator.fadeOut(phpbb.alertTime); - } - } - - if (typeof phpbb.ajaxCallbacks[callback] === 'function') { - phpbb.ajaxCallbacks[callback].call(that, res); - } - - // If the server says to refresh the page, check whether the page should - // be refreshed and refresh page after specified time if required. - if (res.REFRESH_DATA) { - if (typeof refresh === 'function') { - refresh = refresh(res.REFRESH_DATA.url); - } else if (typeof refresh !== 'boolean') { - refresh = false; - } - - phpbbAlertTimer = setTimeout(function() { - if (refresh) { - window.location = res.REFRESH_DATA.url; - } - - // Hide the alert even if we refresh the page, in case the user - // presses the back button. - $dark.fadeOut(phpbb.alertTime, function() { - if (typeof alert !== 'undefined') { - alert.hide(); - } - }); - }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds - } - } else { - // If confirmation is required, display a dialog to the user. - phpbb.confirm(res.MESSAGE_BODY, function(del) { - if (!del) { - return; - } - - phpbb.loadingIndicator(); - data = $('
' + res.S_HIDDEN_FIELDS + '
').serialize(); - $.ajax({ - url: res.S_CONFIRM_ACTION, - type: 'POST', - data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(), - success: returnHandler, - error: errorHandler - }); - }, false); - } - } - - // If the element is a form, POST must be used and some extra data must - // be taken from the form. - var runFilter = (typeof options.filter === 'function'); - data = {}; - - if (isForm) { - action = $this.attr('action').replace('&', '&'); - data = $this.serializeArray(); - method = $this.attr('method') || 'GET'; - - if ($this.find('input[type="submit"][data-clicked]')) { - submit = $this.find('input[type="submit"][data-clicked]'); - data.push({ - name: submit.attr('name'), - value: submit.val() - }); - } - } else if (isText) { - var name = $this.attr('data-name') || this.name; - action = $this.attr('data-url').replace('&', '&'); - data[name] = this.value; - method = 'POST'; - } else { - action = this.href; - data = null; - method = 'GET'; - } - - var sendRequest = function() { - var dataOverlay = $this.attr('data-overlay'); - if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) { - phpbb.loadingIndicator(); - } - - var request = $.ajax({ - url: action, - type: method, - data: data, - success: returnHandler, - error: errorHandler, - cache: false - }); - - request.always(function() { - if ($loadingIndicator && $loadingIndicator.is(':visible')) { - $loadingIndicator.fadeOut(phpbb.alertTime); - } - }); - }; - - // If filter function returns false, cancel the AJAX functionality, - // and return true (meaning that the HTTP request will be sent normally). - if (runFilter && !options.filter.call(this, data, event, sendRequest)) { - return; - } - - sendRequest(); - event.preventDefault(); - }); - - if (isForm) { - $elements.find('input:submit').click(function () { - var $this = $(this); - - // Remove data-clicked attribute from any submit button of form - $this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked'); - - $this.attr('data-clicked', 'true'); - }); - } - - return this; -}; - -phpbb.search = { - cache: { - data: [] - }, - tpl: [], - container: [] -}; - -/** - * Get cached search data. - * - * @param {string} id Search ID. - * @returns {bool|object} Cached data object. Returns false if no data exists. - */ -phpbb.search.cache.get = function(id) { - if (this.data[id]) { - return this.data[id]; - } - return false; -}; - -/** - * Set search cache data value. - * - * @param {string} id Search ID. - * @param {string} key Data key. - * @param {string} value Data value. - */ -phpbb.search.cache.set = function(id, key, value) { - if (!this.data[id]) { - this.data[id] = { results: [] }; - } - this.data[id][key] = value; -}; - -/** - * Cache search result. - * - * @param {string} id Search ID. - * @param {string} keyword Keyword. - * @param {Array} results Search results. - */ -phpbb.search.cache.setResults = function(id, keyword, results) { - this.data[id].results[keyword] = results; -}; - -/** - * Trim spaces from keyword and lower its case. - * - * @param {string} keyword Search keyword to clean. - * @returns {string} Cleaned string. - */ -phpbb.search.cleanKeyword = function(keyword) { - return $.trim(keyword).toLowerCase(); -}; - -/** - * Get clean version of search keyword. If textarea supports several keywords - * (one per line), it fetches the current keyword based on the caret position. - * - * @param {jQuery} $input Search input|textarea. - * @param {string} keyword Input|textarea value. - * @param {bool} multiline Whether textarea supports multiple search keywords. - * - * @returns string Clean string. - */ -phpbb.search.getKeyword = function($input, keyword, multiline) { - if (multiline) { - var line = phpbb.search.getKeywordLine($input); - keyword = keyword.split('\n').splice(line, 1); - } - return phpbb.search.cleanKeyword(keyword); -}; - -/** - * Get the textarea line number on which the keyword resides - for textareas - * that support multiple keywords (one per line). - * - * @param {jQuery} $textarea Search textarea. - * @returns {int} The line number. - */ -phpbb.search.getKeywordLine = function ($textarea) { - var selectionStart = $textarea.get(0).selectionStart; - return $textarea.val().substr(0, selectionStart).split('\n').length - 1; -}; - -/** - * Set the value on the input|textarea. If textarea supports multiple - * keywords, only the active keyword is replaced. - * - * @param {jQuery} $input Search input|textarea. - * @param {string} value Value to set. - * @param {bool} multiline Whether textarea supports multiple search keywords. - */ -phpbb.search.setValue = function($input, value, multiline) { - if (multiline) { - var line = phpbb.search.getKeywordLine($input), - lines = $input.val().split('\n'); - lines[line] = value; - value = lines.join('\n'); - } - $input.val(value); -}; - -/** - * Sets the onclick event to set the value on the input|textarea to the - * selected search result. - * - * @param {jQuery} $input Search input|textarea. - * @param {object} value Result object. - * @param {jQuery} $row Result element. - * @param {jQuery} $container jQuery object for the search container. - */ -phpbb.search.setValueOnClick = function($input, value, $row, $container) { - $row.click(function() { - phpbb.search.setValue($input, value.result, $input.attr('data-multiline')); - phpbb.search.closeResults($input, $container); - }); -}; - -/** - * Runs before the AJAX search request is sent and determines whether - * there is a need to contact the server. If there are cached results - * already, those are displayed instead. Executes the AJAX request function - * itself due to the need to use a timeout to limit the number of requests. - * - * @param {Array} data Data to be sent to the server. - * @param {object} event Onkeyup event object. - * @param {function} sendRequest Function to execute AJAX request. - * - * @returns {boolean} Returns false. - */ -phpbb.search.filter = function(data, event, sendRequest) { - var $this = $(this), - dataName = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : $this.attr('name'), - minLength = parseInt($this.attr('data-min-length'), 10), - searchID = $this.attr('data-results'), - keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')), - cache = phpbb.search.cache.get(searchID), - key = event.keyCode || event.which, - proceed = true; - data[dataName] = keyword; - - // No need to search if enter was pressed - // for selecting a value from the results. - if (key === keymap.ENTER) { - return false; - } - - if (cache.timeout) { - clearTimeout(cache.timeout); - } - - var timeout = setTimeout(function() { - // Check min length and existence of cache. - if (minLength > keyword.length) { - proceed = false; - } else if (cache.lastSearch) { - // Has the keyword actually changed? - if (cache.lastSearch === keyword) { - proceed = false; - } else { - // Do we already have results for this? - if (cache.results[keyword]) { - var response = { - keyword: keyword, - results: cache.results[keyword] - }; - phpbb.search.handleResponse(response, $this, true); - proceed = false; - } - - // If the previous search didn't yield results and the string only had characters added to it, - // then we won't bother sending a request. - if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) { - phpbb.search.cache.set(searchID, 'lastSearch', keyword); - phpbb.search.cache.setResults(searchID, keyword, []); - proceed = false; - } - } - } - - if (proceed) { - sendRequest.call(this); - } - }, 350); - phpbb.search.cache.set(searchID, 'timeout', timeout); - - return false; -}; - -/** - * Handle search result response. - * - * @param {object} res Data received from server. - * @param {jQuery} $input Search input|textarea. - * @param {bool} fromCache Whether the results are from the cache. - * @param {function} callback Optional callback to run when assigning each search result. - */ -phpbb.search.handleResponse = function(res, $input, fromCache, callback) { - if (typeof res !== 'object') { - return; - } - - var searchID = $input.attr('data-results'), - $container = $(searchID); - - if (this.cache.get(searchID).callback) { - callback = this.cache.get(searchID).callback; - } else if (typeof callback === 'function') { - this.cache.set(searchID, 'callback', callback); - } - - if (!fromCache) { - this.cache.setResults(searchID, res.keyword, res.results); - } - - this.cache.set(searchID, 'lastSearch', res.keyword); - this.showResults(res.results, $input, $container, callback); -}; - -/** - * Show search results. - * - * @param {Array} results Search results. - * @param {jQuery} $input Search input|textarea. - * @param {jQuery} $container Search results container element. - * @param {function} callback Optional callback to run when assigning each search result. - */ -phpbb.search.showResults = function(results, $input, $container, callback) { - var $resultContainer = $('.search-results', $container); - this.clearResults($resultContainer); - - if (!results.length) { - $container.hide(); - return; - } - - var searchID = $container.attr('id'), - tpl, - row; - - if (!this.tpl[searchID]) { - tpl = $('.search-result-tpl', $container); - this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl'); - tpl.remove(); - } - tpl = this.tpl[searchID]; - - $.each(results, function(i, item) { - row = tpl.clone(); - row.find('.search-result').html(item.display); - - if (typeof callback === 'function') { - callback.call(this, $input, item, row, $container); - } - row.appendTo($resultContainer).show(); - }); - $container.show(); - - phpbb.search.navigateResults($input, $container, $resultContainer); -}; - -/** - * Clear search results. - * - * @param {jQuery} $container Search results container. - */ -phpbb.search.clearResults = function($container) { - $container.children(':not(.search-result-tpl)').remove(); -}; - -/** - * Close search results. - * - * @param {jQuery} $input Search input|textarea. - * @param {jQuery} $container Search results container. - */ -phpbb.search.closeResults = function($input, $container) { - $input.off('.phpbb.search'); - $container.hide(); -}; - -/** - * Navigate search results. - * - * @param {jQuery} $input Search input|textarea. - * @param {jQuery} $container Search results container. - * @param {jQuery} $resultContainer Search results list container. - */ -phpbb.search.navigateResults = function($input, $container, $resultContainer) { - // Add a namespace to the event (.phpbb.search), - // so it can be unbound specifically later on. - // Rebind it, to ensure the event is 'dynamic'. - $input.off('.phpbb.search'); - $input.on('keydown.phpbb.search', function(event) { - var key = event.keyCode || event.which, - $active = $resultContainer.children('.active'); - - switch (key) { - // Close the results - case keymap.ESC: - phpbb.search.closeResults($input, $container); - break; - - // Set the value for the selected result - case keymap.ENTER: - if ($active.length) { - var value = $active.find('.search-result > span').text(); - - phpbb.search.setValue($input, value, $input.attr('data-multiline')); - } - - phpbb.search.closeResults($input, $container); - - // Do not submit the form - event.preventDefault(); - break; - - // Navigate the results - case keymap.ARROW_DOWN: - case keymap.ARROW_UP: - var up = key === keymap.ARROW_UP, - $children = $resultContainer.children(); - - if (!$active.length) { - if (up) { - $children.last().addClass('active'); - } else { - $children.first().addClass('active'); - } - } else if ($children.length > 1) { - if (up) { - if ($active.is(':first-child')) { - $children.last().addClass('active'); - } else { - $active.prev().addClass('active'); - } - } else { - if ($active.is(':last-child')) { - $children.first().addClass('active'); - } else { - $active.next().addClass('active'); - } - } - - $active.removeClass('active'); - } - - // Do not change cursor position in the input element - event.preventDefault(); - break; - } - }); -}; - -$('#phpbb').click(function() { - var $this = $(this); - - if (!$this.is('.live-search') && !$this.parents().is('.live-search')) { - phpbb.search.closeResults($('input, textarea'), $('.live-search')); - } -}); - -phpbb.history = {}; - -/** -* Check whether a method in the native history object is supported. -* -* @param {string} fn Method name. -* @returns {bool} Returns true if the method is supported. -*/ -phpbb.history.isSupported = function(fn) { - return !(typeof history === 'undefined' || typeof history[fn] === 'undefined'); -}; - -/** -* Wrapper for the pushState and replaceState methods of the -* native history object. -* -* @param {string} mode Mode. Either push or replace. -* @param {string} url New URL. -* @param {string} [title] Optional page title. -* @param {object} [obj] Optional state object. -*/ -phpbb.history.alterUrl = function(mode, url, title, obj) { - var fn = mode + 'State'; - - if (!url || !phpbb.history.isSupported(fn)) { - return; - } - if (!title) { - title = document.title; - } - if (!obj) { - obj = null; - } - - history[fn](obj, title, url); -}; - -/** -* Wrapper for the native history.replaceState method. -* -* @param {string} url New URL. -* @param {string} [title] Optional page title. -* @param {object} [obj] Optional state object. -*/ -phpbb.history.replaceUrl = function(url, title, obj) { - phpbb.history.alterUrl('replace', url, title, obj); -}; - -/** -* Wrapper for the native history.pushState method. -* -* @param {string} url New URL. -* @param {string} [title] Optional page title. -* @param {object} [obj] Optional state object. -*/ -phpbb.history.pushUrl = function(url, title, obj) { - phpbb.history.alterUrl('push', url, title, obj); -}; - -/** -* Hide the optgroups that are not the selected timezone -* -* @param {bool} keepSelection Shall we keep the value selected, or shall the -* user be forced to repick one. -*/ -phpbb.timezoneSwitchDate = function(keepSelection) { - var $timezoneCopy = $('#timezone_copy'); - var $timezone = $('#timezone'); - var $tzDate = $('#tz_date'); - var $tzSelectDateSuggest = $('#tz_select_date_suggest'); - - if ($timezoneCopy.length === 0) { - // We make a backup of the original dropdown, so we can remove optgroups - // instead of setting display to none, because IE and chrome will not - // hide options inside of optgroups and selects via css - $timezone.clone() - .attr('id', 'timezone_copy') - .css('display', 'none') - .attr('name', 'tz_copy') - .insertAfter('#timezone'); - } else { - // Copy the content of our backup, so we can remove all unneeded options - $timezone.html($timezoneCopy.html()); - } - - if ($tzDate.val() !== '') { - $timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])'); - } - - if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) { - $tzSelectDateSuggest.css('display', 'none'); - } else { - $tzSelectDateSuggest.css('display', 'inline'); - } - - var $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option'); - - if ($tzOptions.length === 1) { - // If there is only one timezone for the selected date, we just select that automatically. - $tzOptions.prop('selected', true); - keepSelection = true; - } - - if (typeof keepSelection !== 'undefined' && !keepSelection) { - var $timezoneOptions = $timezone.find('optgroup option'); - if ($timezoneOptions.filter(':selected').length <= 0) { - $timezoneOptions.filter(':first').prop('selected', true); - } - } -}; - -/** -* Display the date/time select -*/ -phpbb.timezoneEnableDateSelection = function() { - $('#tz_select_date').css('display', 'block'); -}; - -/** -* Preselect a date/time or suggest one, if it is not picked. -* -* @param {bool} forceSelector Shall we select the suggestion? -*/ -phpbb.timezonePreselectSelect = function(forceSelector) { - - // The offset returned here is in minutes and negated. - var offset = (new Date()).getTimezoneOffset(); - var sign = '-'; - - if (offset < 0) { - sign = '+'; - offset = -offset; - } - - var minutes = offset % 60; - var hours = (offset - minutes) / 60; - - if (hours === 0) { - hours = '00'; - sign = '+'; - } else if (hours < 10) { - hours = '0' + hours.toString(); - } else { - hours = hours.toString(); - } - - if (minutes < 10) { - minutes = '0' + minutes.toString(); - } else { - minutes = minutes.toString(); - } - - var prefix = 'UTC' + sign + hours + ':' + minutes; - var prefixLength = prefix.length; - var selectorOptions = $('option', '#tz_date'); - var i; - - var $tzSelectDateSuggest = $('#tz_select_date_suggest'); - - for (i = 0; i < selectorOptions.length; ++i) { - var option = selectorOptions[i]; - - if (option.value.substring(0, prefixLength) === prefix) { - if ($('#tz_date').val() !== option.value && !forceSelector) { - // We do not select the option for the user, but notify him, - // that we would suggest a different setting. - phpbb.timezoneSwitchDate(true); - $tzSelectDateSuggest.css('display', 'inline'); - } else { - option.selected = true; - phpbb.timezoneSwitchDate(!forceSelector); - $tzSelectDateSuggest.css('display', 'none'); - } - - var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion'); - - $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML)); - $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9))); - $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML); - - // Found the suggestion, there cannot be more, so return from here. - return; - } - } -}; - -phpbb.ajaxCallbacks = {}; - -/** - * Adds an AJAX callback to be used by phpbb.ajaxify. - * - * See the phpbb.ajaxify comments for information on stuff like parameters. - * - * @param {string} id The name of the callback. - * @param {function} callback The callback to be called. - */ -phpbb.addAjaxCallback = function(id, callback) { - if (typeof callback === 'function') { - phpbb.ajaxCallbacks[id] = callback; - } - return this; -}; - -/** - * This callback handles live member searches. - */ -phpbb.addAjaxCallback('member_search', function(res) { - phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick')); -}); - -/** - * This callback alternates text - it replaces the current text with the text in - * the alt-text data attribute, and replaces the text in the attribute with the - * current text so that the process can be repeated. - */ -phpbb.addAjaxCallback('alt_text', function() { - var $anchor, - updateAll = $(this).data('update-all'), - altText; - - if (updateAll !== undefined && updateAll.length) { - $anchor = $(updateAll); - } else { - $anchor = $(this); - } - - $anchor.each(function() { - var $this = $(this); - altText = $this.attr('data-alt-text'); - $this.attr('data-alt-text', $.trim($this.text())); - $this.attr('title', altText); - $this.children('span').text(altText); - }); -}); - -/** - * This callback is based on the alt_text callback. - * - * It replaces the current text with the text in the alt-text data attribute, - * and replaces the text in the attribute with the current text so that the - * process can be repeated. - * Additionally it replaces the class of the link's parent - * and changes the link itself. - */ -phpbb.addAjaxCallback('toggle_link', function() { - var $anchor, - updateAll = $(this).data('update-all') , - toggleText, - toggleUrl, - toggleClass; - - if (updateAll !== undefined && updateAll.length) { - $anchor = $(updateAll); - } else { - $anchor = $(this); - } - - $anchor.each(function() { - var $this = $(this); - - // Toggle link url - toggleUrl = $this.attr('data-toggle-url'); - $this.attr('data-toggle-url', $this.attr('href')); - $this.attr('href', toggleUrl); - - // Toggle class of link parent - toggleClass = $this.attr('data-toggle-class'); - $this.attr('data-toggle-class', $this.children().attr('class')); - $this.children('.icon').attr('class', toggleClass); - - // Toggle link text - toggleText = $this.attr('data-toggle-text'); - $this.attr('data-toggle-text', $this.children('span').text()); - $this.attr('title', $.trim(toggleText)); - $this.children('span').text(toggleText); - }); -}); - -/** +(function ($) { + // Avoid conflicts with other libraries + + "use strict"; + + // define a couple constants for keydown functions. + var keymap = { + TAB: 9, + ENTER: 13, + ESC: 27, + ARROW_UP: 38, + ARROW_DOWN: 40, + }; + + var $dark = $("#darkenwrapper"); + var $loadingIndicator; + var phpbbAlertTimer = null; + + phpbb.isTouch = window && typeof window.ontouchstart !== "undefined"; + + // Add ajax pre-filter to prevent cross-domain script execution + $.ajaxPrefilter(function (s) { + if (s.crossDomain) { + s.contents.script = false; + } + }); + + /** + * Display a loading screen + * + * @returns {object} Returns loadingIndicator. + */ + phpbb.loadingIndicator = function () { + if (!$loadingIndicator) { + $loadingIndicator = $("
", { + id: "loading_indicator", + class: "loading_indicator", + }); + $loadingIndicator.appendTo("#page-footer"); + } + + if (!$loadingIndicator.is(":visible")) { + $loadingIndicator.fadeIn(phpbb.alertTime); + // Wait 60 seconds and display an error if nothing has been returned by then. + phpbb.clearLoadingTimeout(); + phpbbAlertTimer = setTimeout(function () { + phpbb.showTimeoutMessage(); + }, 60000); + } + + return $loadingIndicator; + }; + + /** + * Show timeout message + */ + phpbb.showTimeoutMessage = function () { + var $alert = $("#phpbb_alert"); + + if ($loadingIndicator.is(":visible")) { + phpbb.alert( + $alert.attr("data-l-err"), + $alert.attr("data-l-timeout-processing-req") + ); + } + }; + + /** + * Clear loading alert timeout + */ + phpbb.clearLoadingTimeout = function () { + if (phpbbAlertTimer !== null) { + clearTimeout(phpbbAlertTimer); + phpbbAlertTimer = null; + } + }; + + /** + * Close popup alert after a specified delay + * + * @param {int} delay Delay in ms until darkenwrapper's click event is triggered + */ + phpbb.closeDarkenWrapper = function (delay) { + phpbbAlertTimer = setTimeout(function () { + $("#darkenwrapper").trigger("click"); + }, delay); + }; + + /** + * Display a simple alert similar to JSs native alert(). + * + * You can only call one alert or confirm box at any one time. + * + * @param {string} title Title of the message, eg "Information" (HTML). + * @param {string} msg Message to display (HTML). + * + * @returns {object} Returns the div created. + */ + phpbb.alert = function (title, msg) { + var $alert = $("#phpbb_alert"); + $alert.find(".alert_title").html(title); + $alert.find(".alert_text").html(msg); + + $(document).on("keydown.phpbb.alert", function (e) { + if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + phpbb.alert.close($alert, true); + e.preventDefault(); + e.stopPropagation(); + } + }); + phpbb.alert.open($alert); + + return $alert; + }; + + /** + * Handler for opening an alert box. + * + * @param {jQuery} $alert jQuery object. + */ + phpbb.alert.open = function ($alert) { + if (!$dark.is(":visible")) { + $dark.fadeIn(phpbb.alertTime); + } + + if ($loadingIndicator && $loadingIndicator.is(":visible")) { + $loadingIndicator.fadeOut(phpbb.alertTime, function () { + $dark.append($alert); + $alert.fadeIn(phpbb.alertTime); + }); + } else if ($dark.is(":visible")) { + $dark.append($alert); + $alert.fadeIn(phpbb.alertTime); + } else { + $dark.append($alert); + $alert.show(); + $dark.fadeIn(phpbb.alertTime); + } + + $alert.on("click", function (e) { + e.stopPropagation(); + }); + + $dark.one("click", function (e) { + phpbb.alert.close($alert, true); + e.preventDefault(); + e.stopPropagation(); + }); + + $alert.find(".alert_close").one("click", function (e) { + phpbb.alert.close($alert, true); + e.preventDefault(); + }); + }; + + /** + * Handler for closing an alert box. + * + * @param {jQuery} $alert jQuery object. + * @param {bool} fadedark Whether to remove dark background. + */ + phpbb.alert.close = function ($alert, fadedark) { + var $fade = fadedark ? $dark : $alert; + + $fade.fadeOut(phpbb.alertTime, function () { + $alert.hide(); + }); + + $alert.find(".alert_close").off("click"); + $(document).off("keydown.phpbb.alert"); + }; + + /** + * Display a simple yes / no box to the user. + * + * You can only call one alert or confirm box at any one time. + * + * @param {string} msg Message to display (HTML). + * @param {function} callback Callback. Bool param, whether the user pressed + * yes or no (or whatever their language is). + * @param {bool} fadedark Remove the dark background when done? Defaults + * to yes. + * + * @returns {object} Returns the div created. + */ + phpbb.confirm = function (msg, callback, fadedark) { + var $confirmDiv = $("#phpbb_confirm"); + $confirmDiv.find(".alert_text").html(msg); + fadedark = typeof fadedark !== "undefined" ? fadedark : true; + + $(document).on("keydown.phpbb.alert", function (e) { + if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + var name = e.keyCode === keymap.ENTER ? "confirm" : "cancel"; + + $('input[name="' + name + '"]').trigger("click"); + e.preventDefault(); + e.stopPropagation(); + } + }); + + $confirmDiv + .find('input[type="button"]') + .one("click.phpbb.confirmbox", function (e) { + var confirmed = this.name === "confirm"; + + callback(confirmed); + $confirmDiv.find('input[type="button"]').off("click.phpbb.confirmbox"); + phpbb.alert.close($confirmDiv, fadedark || !confirmed); + + e.preventDefault(); + e.stopPropagation(); + }); + + phpbb.alert.open($confirmDiv); + + return $confirmDiv; + }; + + /** + * Turn a querystring into an array. + * + * @argument {string} string The querystring to parse. + * @returns {object} The object created. + */ + phpbb.parseQuerystring = function (string) { + var params = {}, + i, + split; + + string = string.split("&"); + for (i = 0; i < string.length; i++) { + split = string[i].split("="); + params[split[0]] = decodeURIComponent(split[1]); + } + return params; + }; + + /** + * Makes a link use AJAX instead of loading an entire page. + * + * This function will work for links (both standard links and links which + * invoke confirm_box) and forms. It will be called automatically for links + * and forms with the data-ajax attribute set, and will call the necessary + * callback. + * + * For more info, view the following page on the phpBB wiki: + * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify + * + * @param {object} options Options. + */ + phpbb.ajaxify = function (options) { + var $elements = $(options.selector), + refresh = options.refresh, + callback = options.callback, + overlay = typeof options.overlay !== "undefined" ? options.overlay : true, + isForm = $elements.is("form"), + isText = $elements.is('input[type="text"], textarea'), + eventName; + + if (isForm) { + eventName = "submit"; + } else if (isText) { + eventName = "keyup"; + } else { + eventName = "click"; + } + + $elements.on(eventName, function (event) { + var action, + method, + data, + submit, + that = this, + $this = $(this); + + if ( + $this.find('input[type="submit"][data-clicked]').attr("data-ajax") === + "false" + ) { + return; + } + + /** + * Handler for AJAX errors + */ + function errorHandler(jqXHR, textStatus, errorThrown) { + if (typeof console !== "undefined" && console.log) { + console.log( + "AJAX error. status: " + textStatus + ", message: " + errorThrown + ); + } + phpbb.clearLoadingTimeout(); + var responseText, + errorText = false; + try { + responseText = JSON.parse(jqXHR.responseText); + responseText = responseText.message; + } catch (e) {} + if (typeof responseText === "string" && responseText.length > 0) { + errorText = responseText; + } else if (typeof errorThrown === "string" && errorThrown.length > 0) { + errorText = errorThrown; + } else { + errorText = $dark.attr("data-ajax-error-text-" + textStatus); + if (typeof errorText !== "string" || !errorText.length) { + errorText = $dark.attr("data-ajax-error-text"); + } + } + phpbb.alert($dark.attr("data-ajax-error-title"), errorText); + } + + /** + * This is a private function used to handle the callbacks, refreshes + * and alert. It calls the callback, refreshes the page if necessary, and + * displays an alert to the user and removes it after an amount of time. + * + * It cannot be called from outside this function, and is purely here to + * avoid repetition of code. + * + * @param {object} res The object sent back by the server. + */ + function returnHandler(res) { + var alert; + + phpbb.clearLoadingTimeout(); + + // Is a confirmation required? + if (typeof res.S_CONFIRM_ACTION === "undefined") { + // If a confirmation is not required, display an alert and call the + // callbacks. + if (typeof res.MESSAGE_TITLE !== "undefined") { + alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + } else { + $dark.fadeOut(phpbb.alertTime); + + if ($loadingIndicator) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + } + + if (typeof phpbb.ajaxCallbacks[callback] === "function") { + phpbb.ajaxCallbacks[callback].call(that, res); + } + + // If the server says to refresh the page, check whether the page should + // be refreshed and refresh page after specified time if required. + if (res.REFRESH_DATA) { + if (typeof refresh === "function") { + refresh = refresh(res.REFRESH_DATA.url); + } else if (typeof refresh !== "boolean") { + refresh = false; + } + + phpbbAlertTimer = setTimeout(function () { + if (refresh) { + window.location = res.REFRESH_DATA.url; + } + + // Hide the alert even if we refresh the page, in case the user + // presses the back button. + $dark.fadeOut(phpbb.alertTime, function () { + if (typeof alert !== "undefined") { + alert.hide(); + } + }); + }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds + } + } else { + // If confirmation is required, display a dialog to the user. + phpbb.confirm( + res.MESSAGE_BODY, + function (del) { + if (!del) { + return; + } + + phpbb.loadingIndicator(); + data = $("
" + res.S_HIDDEN_FIELDS + "
").serialize(); + $.ajax({ + url: res.S_CONFIRM_ACTION, + type: "POST", + data: + data + + "&confirm=" + + res.YES_VALUE + + "&" + + $("form", "#phpbb_confirm").serialize(), + success: returnHandler, + error: errorHandler, + }); + }, + false + ); + } + } + + // If the element is a form, POST must be used and some extra data must + // be taken from the form. + var runFilter = typeof options.filter === "function"; + data = {}; + + if (isForm) { + action = $this.attr("action").replace("&", "&"); + data = $this.serializeArray(); + method = $this.attr("method") || "GET"; + + if ($this.find('input[type="submit"][data-clicked]')) { + submit = $this.find('input[type="submit"][data-clicked]'); + data.push({ + name: submit.attr("name"), + value: submit.val(), + }); + } + } else if (isText) { + var name = $this.attr("data-name") || this.name; + action = $this.attr("data-url").replace("&", "&"); + data[name] = this.value; + method = "POST"; + } else { + action = this.href; + data = null; + method = "GET"; + } + + var sendRequest = function () { + var dataOverlay = $this.attr("data-overlay"); + if ( + overlay && + (typeof dataOverlay === "undefined" || dataOverlay === "true") + ) { + phpbb.loadingIndicator(); + } + + var request = $.ajax({ + url: action, + type: method, + data: data, + success: returnHandler, + error: errorHandler, + cache: false, + }); + + request.always(function () { + if ($loadingIndicator && $loadingIndicator.is(":visible")) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + }); + }; + + // If filter function returns false, cancel the AJAX functionality, + // and return true (meaning that the HTTP request will be sent normally). + if (runFilter && !options.filter.call(this, data, event, sendRequest)) { + return; + } + + sendRequest(); + event.preventDefault(); + }); + + if (isForm) { + $elements.find("input:submit").click(function () { + var $this = $(this); + + // Remove data-clicked attribute from any submit button of form + $this + .parents("form:first") + .find("input:submit[data-clicked]") + .removeAttr("data-clicked"); + + $this.attr("data-clicked", "true"); + }); + } + + return this; + }; + + phpbb.search = { + cache: { + data: [], + }, + tpl: [], + container: [], + }; + + /** + * Get cached search data. + * + * @param {string} id Search ID. + * @returns {bool|object} Cached data object. Returns false if no data exists. + */ + phpbb.search.cache.get = function (id) { + if (this.data[id]) { + return this.data[id]; + } + return false; + }; + + /** + * Set search cache data value. + * + * @param {string} id Search ID. + * @param {string} key Data key. + * @param {string} value Data value. + */ + phpbb.search.cache.set = function (id, key, value) { + if (!this.data[id]) { + this.data[id] = { results: [] }; + } + this.data[id][key] = value; + }; + + /** + * Cache search result. + * + * @param {string} id Search ID. + * @param {string} keyword Keyword. + * @param {Array} results Search results. + */ + phpbb.search.cache.setResults = function (id, keyword, results) { + this.data[id].results[keyword] = results; + }; + + /** + * Trim spaces from keyword and lower its case. + * + * @param {string} keyword Search keyword to clean. + * @returns {string} Cleaned string. + */ + phpbb.search.cleanKeyword = function (keyword) { + return $.trim(keyword).toLowerCase(); + }; + + /** + * Get clean version of search keyword. If textarea supports several keywords + * (one per line), it fetches the current keyword based on the caret position. + * + * @param {jQuery} $input Search input|textarea. + * @param {string} keyword Input|textarea value. + * @param {bool} multiline Whether textarea supports multiple search keywords. + * + * @returns string Clean string. + */ + phpbb.search.getKeyword = function ($input, keyword, multiline) { + if (multiline) { + var line = phpbb.search.getKeywordLine($input); + keyword = keyword.split("\n").splice(line, 1); + } + return phpbb.search.cleanKeyword(keyword); + }; + + /** + * Get the textarea line number on which the keyword resides - for textareas + * that support multiple keywords (one per line). + * + * @param {jQuery} $textarea Search textarea. + * @returns {int} The line number. + */ + phpbb.search.getKeywordLine = function ($textarea) { + var selectionStart = $textarea.get(0).selectionStart; + return $textarea.val().substr(0, selectionStart).split("\n").length - 1; + }; + + /** + * Set the value on the input|textarea. If textarea supports multiple + * keywords, only the active keyword is replaced. + * + * @param {jQuery} $input Search input|textarea. + * @param {string} value Value to set. + * @param {bool} multiline Whether textarea supports multiple search keywords. + */ + phpbb.search.setValue = function ($input, value, multiline) { + if (multiline) { + var line = phpbb.search.getKeywordLine($input), + lines = $input.val().split("\n"); + lines[line] = value; + value = lines.join("\n"); + } + $input.val(value); + }; + + /** + * Sets the onclick event to set the value on the input|textarea to the + * selected search result. + * + * @param {jQuery} $input Search input|textarea. + * @param {object} value Result object. + * @param {jQuery} $row Result element. + * @param {jQuery} $container jQuery object for the search container. + */ + phpbb.search.setValueOnClick = function ($input, value, $row, $container) { + $row.click(function () { + phpbb.search.setValue( + $input, + value.result, + $input.attr("data-multiline") + ); + phpbb.search.closeResults($input, $container); + }); + }; + + /** + * Runs before the AJAX search request is sent and determines whether + * there is a need to contact the server. If there are cached results + * already, those are displayed instead. Executes the AJAX request function + * itself due to the need to use a timeout to limit the number of requests. + * + * @param {Array} data Data to be sent to the server. + * @param {object} event Onkeyup event object. + * @param {function} sendRequest Function to execute AJAX request. + * + * @returns {boolean} Returns false. + */ + phpbb.search.filter = function (data, event, sendRequest) { + var $this = $(this), + dataName = + $this.attr("data-name") !== undefined + ? $this.attr("data-name") + : $this.attr("name"), + minLength = parseInt($this.attr("data-min-length"), 10), + searchID = $this.attr("data-results"), + keyword = phpbb.search.getKeyword( + $this, + data[dataName], + $this.attr("data-multiline") + ), + cache = phpbb.search.cache.get(searchID), + key = event.keyCode || event.which, + proceed = true; + data[dataName] = keyword; + + // No need to search if enter was pressed + // for selecting a value from the results. + if (key === keymap.ENTER) { + return false; + } + + if (cache.timeout) { + clearTimeout(cache.timeout); + } + + var timeout = setTimeout(function () { + // Check min length and existence of cache. + if (minLength > keyword.length) { + proceed = false; + } else if (cache.lastSearch) { + // Has the keyword actually changed? + if (cache.lastSearch === keyword) { + proceed = false; + } else { + // Do we already have results for this? + if (cache.results[keyword]) { + var response = { + keyword: keyword, + results: cache.results[keyword], + }; + phpbb.search.handleResponse(response, $this, true); + proceed = false; + } + + // If the previous search didn't yield results and the string only had characters added to it, + // then we won't bother sending a request. + if ( + keyword.indexOf(cache.lastSearch) === 0 && + cache.results[cache.lastSearch].length === 0 + ) { + phpbb.search.cache.set(searchID, "lastSearch", keyword); + phpbb.search.cache.setResults(searchID, keyword, []); + proceed = false; + } + } + } + + if (proceed) { + sendRequest.call(this); + } + }, 350); + phpbb.search.cache.set(searchID, "timeout", timeout); + + return false; + }; + + /** + * Handle search result response. + * + * @param {object} res Data received from server. + * @param {jQuery} $input Search input|textarea. + * @param {bool} fromCache Whether the results are from the cache. + * @param {function} callback Optional callback to run when assigning each search result. + */ + phpbb.search.handleResponse = function (res, $input, fromCache, callback) { + if (typeof res !== "object") { + return; + } + + var searchID = $input.attr("data-results"), + $container = $(searchID); + + if (this.cache.get(searchID).callback) { + callback = this.cache.get(searchID).callback; + } else if (typeof callback === "function") { + this.cache.set(searchID, "callback", callback); + } + + if (!fromCache) { + this.cache.setResults(searchID, res.keyword, res.results); + } + + this.cache.set(searchID, "lastSearch", res.keyword); + this.showResults(res.results, $input, $container, callback); + }; + + /** + * Show search results. + * + * @param {Array} results Search results. + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container element. + * @param {function} callback Optional callback to run when assigning each search result. + */ + phpbb.search.showResults = function (results, $input, $container, callback) { + var $resultContainer = $(".search-results", $container); + this.clearResults($resultContainer); + + if (!results.length) { + $container.hide(); + return; + } + + var searchID = $container.attr("id"), + tpl, + row; + + if (!this.tpl[searchID]) { + tpl = $(".search-result-tpl", $container); + this.tpl[searchID] = tpl.clone().removeClass("search-result-tpl"); + tpl.remove(); + } + tpl = this.tpl[searchID]; + + $.each(results, function (i, item) { + row = tpl.clone(); + row.find(".search-result").html(item.display); + + if (typeof callback === "function") { + callback.call(this, $input, item, row, $container); + } + row.appendTo($resultContainer).show(); + }); + $container.show(); + + phpbb.search.navigateResults($input, $container, $resultContainer); + }; + + /** + * Clear search results. + * + * @param {jQuery} $container Search results container. + */ + phpbb.search.clearResults = function ($container) { + $container.children(":not(.search-result-tpl)").remove(); + }; + + /** + * Close search results. + * + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container. + */ + phpbb.search.closeResults = function ($input, $container) { + $input.off(".phpbb.search"); + $container.hide(); + }; + + /** + * Navigate search results. + * + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container. + * @param {jQuery} $resultContainer Search results list container. + */ + phpbb.search.navigateResults = function ( + $input, + $container, + $resultContainer + ) { + // Add a namespace to the event (.phpbb.search), + // so it can be unbound specifically later on. + // Rebind it, to ensure the event is 'dynamic'. + $input.off(".phpbb.search"); + $input.on("keydown.phpbb.search", function (event) { + var key = event.keyCode || event.which, + $active = $resultContainer.children(".active"); + + switch (key) { + // Close the results + case keymap.ESC: + phpbb.search.closeResults($input, $container); + break; + + // Set the value for the selected result + case keymap.ENTER: + if ($active.length) { + var value = $active.find(".search-result > span").text(); + + phpbb.search.setValue($input, value, $input.attr("data-multiline")); + } + + phpbb.search.closeResults($input, $container); + + // Do not submit the form + event.preventDefault(); + break; + + // Navigate the results + case keymap.ARROW_DOWN: + case keymap.ARROW_UP: + var up = key === keymap.ARROW_UP, + $children = $resultContainer.children(); + + if (!$active.length) { + if (up) { + $children.last().addClass("active"); + } else { + $children.first().addClass("active"); + } + } else if ($children.length > 1) { + if (up) { + if ($active.is(":first-child")) { + $children.last().addClass("active"); + } else { + $active.prev().addClass("active"); + } + } else { + if ($active.is(":last-child")) { + $children.first().addClass("active"); + } else { + $active.next().addClass("active"); + } + } + + $active.removeClass("active"); + } + + // Do not change cursor position in the input element + event.preventDefault(); + break; + } + }); + }; + + $("#phpbb").click(function () { + var $this = $(this); + + if (!$this.is(".live-search") && !$this.parents().is(".live-search")) { + phpbb.search.closeResults($("input, textarea"), $(".live-search")); + } + }); + + phpbb.history = {}; + + /** + * Check whether a method in the native history object is supported. + * + * @param {string} fn Method name. + * @returns {bool} Returns true if the method is supported. + */ + phpbb.history.isSupported = function (fn) { + return !( + typeof history === "undefined" || typeof history[fn] === "undefined" + ); + }; + + /** + * Wrapper for the pushState and replaceState methods of the + * native history object. + * + * @param {string} mode Mode. Either push or replace. + * @param {string} url New URL. + * @param {string} [title] Optional page title. + * @param {object} [obj] Optional state object. + */ + phpbb.history.alterUrl = function (mode, url, title, obj) { + var fn = mode + "State"; + + if (!url || !phpbb.history.isSupported(fn)) { + return; + } + if (!title) { + title = document.title; + } + if (!obj) { + obj = null; + } + + history[fn](obj, title, url); + }; + + /** + * Wrapper for the native history.replaceState method. + * + * @param {string} url New URL. + * @param {string} [title] Optional page title. + * @param {object} [obj] Optional state object. + */ + phpbb.history.replaceUrl = function (url, title, obj) { + phpbb.history.alterUrl("replace", url, title, obj); + }; + + /** + * Wrapper for the native history.pushState method. + * + * @param {string} url New URL. + * @param {string} [title] Optional page title. + * @param {object} [obj] Optional state object. + */ + phpbb.history.pushUrl = function (url, title, obj) { + phpbb.history.alterUrl("push", url, title, obj); + }; + + /** + * Hide the optgroups that are not the selected timezone + * + * @param {bool} keepSelection Shall we keep the value selected, or shall the + * user be forced to repick one. + */ + phpbb.timezoneSwitchDate = function (keepSelection) { + var $timezoneCopy = $("#timezone_copy"); + var $timezone = $("#timezone"); + var $tzDate = $("#tz_date"); + var $tzSelectDateSuggest = $("#tz_select_date_suggest"); + + if ($timezoneCopy.length === 0) { + // We make a backup of the original dropdown, so we can remove optgroups + // instead of setting display to none, because IE and chrome will not + // hide options inside of optgroups and selects via css + $timezone + .clone() + .attr("id", "timezone_copy") + .css("display", "none") + .attr("name", "tz_copy") + .insertAfter("#timezone"); + } else { + // Copy the content of our backup, so we can remove all unneeded options + $timezone.html($timezoneCopy.html()); + } + + if ($tzDate.val() !== "") { + $timezone + .children("optgroup") + .remove(':not([data-tz-value="' + $tzDate.val() + '"])'); + } + + if ($tzDate.val() === $tzSelectDateSuggest.attr("data-suggested-tz")) { + $tzSelectDateSuggest.css("display", "none"); + } else { + $tzSelectDateSuggest.css("display", "inline"); + } + + var $tzOptions = $timezone + .children('optgroup[data-tz-value="' + $tzDate.val() + '"]') + .children("option"); + + if ($tzOptions.length === 1) { + // If there is only one timezone for the selected date, we just select that automatically. + $tzOptions.prop("selected", true); + keepSelection = true; + } + + if (typeof keepSelection !== "undefined" && !keepSelection) { + var $timezoneOptions = $timezone.find("optgroup option"); + if ($timezoneOptions.filter(":selected").length <= 0) { + $timezoneOptions.filter(":first").prop("selected", true); + } + } + }; + + /** + * Display the date/time select + */ + phpbb.timezoneEnableDateSelection = function () { + $("#tz_select_date").css("display", "block"); + }; + + /** + * Preselect a date/time or suggest one, if it is not picked. + * + * @param {bool} forceSelector Shall we select the suggestion? + */ + phpbb.timezonePreselectSelect = function (forceSelector) { + // The offset returned here is in minutes and negated. + var offset = new Date().getTimezoneOffset(); + var sign = "-"; + + if (offset < 0) { + sign = "+"; + offset = -offset; + } + + var minutes = offset % 60; + var hours = (offset - minutes) / 60; + + if (hours === 0) { + hours = "00"; + sign = "+"; + } else if (hours < 10) { + hours = "0" + hours.toString(); + } else { + hours = hours.toString(); + } + + if (minutes < 10) { + minutes = "0" + minutes.toString(); + } else { + minutes = minutes.toString(); + } + + var prefix = "UTC" + sign + hours + ":" + minutes; + var prefixLength = prefix.length; + var selectorOptions = $("option", "#tz_date"); + var i; + + var $tzSelectDateSuggest = $("#tz_select_date_suggest"); + + for (i = 0; i < selectorOptions.length; ++i) { + var option = selectorOptions[i]; + + if (option.value.substring(0, prefixLength) === prefix) { + if ($("#tz_date").val() !== option.value && !forceSelector) { + // We do not select the option for the user, but notify him, + // that we would suggest a different setting. + phpbb.timezoneSwitchDate(true); + $tzSelectDateSuggest.css("display", "inline"); + } else { + option.selected = true; + phpbb.timezoneSwitchDate(!forceSelector); + $tzSelectDateSuggest.css("display", "none"); + } + + var suggestion = $tzSelectDateSuggest.attr("data-l-suggestion"); + + $tzSelectDateSuggest.attr( + "title", + suggestion.replace("%s", option.innerHTML) + ); + $tzSelectDateSuggest.attr( + "value", + suggestion.replace("%s", option.innerHTML.substring(0, 9)) + ); + $tzSelectDateSuggest.attr("data-suggested-tz", option.innerHTML); + + // Found the suggestion, there cannot be more, so return from here. + return; + } + } + }; + + phpbb.ajaxCallbacks = {}; + + /** + * Adds an AJAX callback to be used by phpbb.ajaxify. + * + * See the phpbb.ajaxify comments for information on stuff like parameters. + * + * @param {string} id The name of the callback. + * @param {function} callback The callback to be called. + */ + phpbb.addAjaxCallback = function (id, callback) { + if (typeof callback === "function") { + phpbb.ajaxCallbacks[id] = callback; + } + return this; + }; + + /** + * This callback handles live member searches. + */ + phpbb.addAjaxCallback("member_search", function (res) { + phpbb.search.handleResponse( + res, + $(this), + false, + phpbb.getFunctionByName("phpbb.search.setValueOnClick") + ); + }); + + /** + * This callback alternates text - it replaces the current text with the text in + * the alt-text data attribute, and replaces the text in the attribute with the + * current text so that the process can be repeated. + */ + phpbb.addAjaxCallback("alt_text", function () { + var $anchor, + updateAll = $(this).data("update-all"), + altText; + + if (updateAll !== undefined && updateAll.length) { + $anchor = $(updateAll); + } else { + $anchor = $(this); + } + + $anchor.each(function () { + var $this = $(this); + altText = $this.attr("data-alt-text"); + $this.attr("data-alt-text", $.trim($this.text())); + $this.attr("title", altText); + $this.children("span").text(altText); + }); + }); + + /** + * This callback is based on the alt_text callback. + * + * It replaces the current text with the text in the alt-text data attribute, + * and replaces the text in the attribute with the current text so that the + * process can be repeated. + * Additionally it replaces the class of the link's parent + * and changes the link itself. + */ + phpbb.addAjaxCallback("toggle_link", function () { + var $anchor, + updateAll = $(this).data("update-all"), + toggleText, + toggleUrl, + toggleClass; + + if (updateAll !== undefined && updateAll.length) { + $anchor = $(updateAll); + } else { + $anchor = $(this); + } + + $anchor.each(function () { + var $this = $(this); + + // Toggle link url + toggleUrl = $this.attr("data-toggle-url"); + $this.attr("data-toggle-url", $this.attr("href")); + $this.attr("href", toggleUrl); + + // Toggle class of link parent + toggleClass = $this.attr("data-toggle-class"); + $this.attr("data-toggle-class", $this.children().attr("class")); + $this.children(".icon").attr("class", toggleClass); + + // Toggle link text + toggleText = $this.attr("data-toggle-text"); + $this.attr("data-toggle-text", $this.children("span").text()); + $this.attr("title", $.trim(toggleText)); + $this.children("span").text(toggleText); + }); + }); + + /** * Automatically resize textarea * * This function automatically resizes textarea elements when user @@ -1111,753 +1178,795 @@ phpbb.addAjaxCallback('toggle_link', function() { * this points to DOM object * item is a jQuery object, same as this */ -phpbb.resizeTextArea = function($items, options) { - // Configuration - var configuration = { - minWindowHeight: 500, - minHeight: 200, - maxHeight: 500, - heightDiff: 200, - resizeCallback: function() {}, - resetCallback: function() {} - }; - - if (phpbb.isTouch) { - return; - } - - if (arguments.length > 1) { - configuration = $.extend(configuration, options); - } - - function resetAutoResize(item) { - var $item = $(item); - if ($item.hasClass('auto-resized')) { - $(item) - .css({ height: '', resize: '' }) - .removeClass('auto-resized'); - configuration.resetCallback.call(item, $item); - } - } - - function autoResize(item) { - function setHeight(height) { - height += parseInt($item.css('height'), 10) - $item.innerHeight(); - $item - .css({ height: height + 'px', resize: 'none' }) - .addClass('auto-resized'); - configuration.resizeCallback.call(item, $item); - } - - var windowHeight = $(window).height(); - - if (windowHeight < configuration.minWindowHeight) { - resetAutoResize(item); - return; - } - - var maxHeight = Math.min( - Math.max(windowHeight - configuration.heightDiff, configuration.minHeight), - configuration.maxHeight - ), - $item = $(item), - height = parseInt($item.innerHeight(), 10), - scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0; - - if (height < 0) { - return; - } - - if (height > maxHeight) { - setHeight(maxHeight); - } else if (scrollHeight > (height + 5)) { - setHeight(Math.min(maxHeight, scrollHeight)); - } - } - - $items.on('focus change keyup', function() { - $(this).each(function() { - autoResize(this); - }); - }).change(); - - $(window).resize(function() { - $items.each(function() { - if ($(this).hasClass('auto-resized')) { - autoResize(this); - } - }); - }); -}; - -/** -* Check if cursor in textarea is currently inside a bbcode tag -* -* @param {object} textarea Textarea DOM object -* @param {Array} startTags List of start tags to look for -* For example, Array('[code]', '[code=') -* @param {Array} endTags List of end tags to look for -* For example, Array('[/code]') -* -* @returns {boolean} True if cursor is in bbcode tag -*/ -phpbb.inBBCodeTag = function(textarea, startTags, endTags) { - var start = textarea.selectionStart, - lastEnd = -1, - lastStart = -1, - i, index, value; - - if (typeof start !== 'number') { - return false; - } - - value = textarea.value.toLowerCase(); - - for (i = 0; i < startTags.length; i++) { - var tagLength = startTags[i].length; - if (start >= tagLength) { - index = value.lastIndexOf(startTags[i], start - tagLength); - lastStart = Math.max(lastStart, index); - } - } - if (lastStart === -1) { - return false; - } - - if (start > 0) { - for (i = 0; i < endTags.length; i++) { - index = value.lastIndexOf(endTags[i], start - 1); - lastEnd = Math.max(lastEnd, index); - } - } - - return (lastEnd < lastStart); -}; - - -/** -* Adjust textarea to manage code bbcode -* -* This function allows to use tab characters when typing code -* and keeps indentation of previous line of code when adding new -* line while typing code. -* -* Editor's functionality is changed only when cursor is between -* [code] and [/code] bbcode tags. -* -* @param {object} textarea Textarea DOM object to apply editor to -*/ -phpbb.applyCodeEditor = function(textarea) { - // list of allowed start and end bbcode code tags, in lower case - var startTags = ['[code]', '[code='], - startTagsEnd = ']', - endTags = ['[/code]']; - - if (!textarea || typeof textarea.selectionStart !== 'number') { - return; - } - - if ($(textarea).data('code-editor') === true) { - return; - } - - function inTag() { - return phpbb.inBBCodeTag(textarea, startTags, endTags); - } - - /** - * Get line of text before cursor - * - * @param {boolean} stripCodeStart If true, only part of line - * after [code] tag will be returned. - * - * @returns {string} Line of text - */ - function getLastLine(stripCodeStart) { - var start = textarea.selectionStart, - value = textarea.value, - index = value.lastIndexOf('\n', start - 1); - - value = value.substring(index + 1, start); - - if (stripCodeStart) { - for (var i = 0; i < startTags.length; i++) { - index = value.lastIndexOf(startTags[i]); - if (index >= 0) { - var tagLength = startTags[i].length; - - value = value.substring(index + tagLength); - if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) { - index = value.indexOf(startTagsEnd); - - if (index >= 0) { - value = value.substr(index + 1); - } - } - } - } - } - - return value; - } - - /** - * Append text at cursor position - * - * @param {string} text Text to append - */ - function appendText(text) { - var start = textarea.selectionStart, - end = textarea.selectionEnd, - value = textarea.value; - - textarea.value = value.substr(0, start) + text + value.substr(end); - textarea.selectionStart = textarea.selectionEnd = start + text.length; - } - - $(textarea).data('code-editor', true).on('keydown', function(event) { - var key = event.keyCode || event.which; - - // intercept tabs - if (key === keymap.TAB && - !event.ctrlKey && - !event.shiftKey && - !event.altKey && - !event.metaKey) { - if (inTag()) { - appendText('\t'); - event.preventDefault(); - return; - } - } - - // intercept new line characters - if (key === keymap.ENTER) { - if (inTag()) { - var lastLine = getLastLine(true), - code = '' + /^\s*/g.exec(lastLine); - - if (code.length > 0) { - appendText('\n' + code); - event.preventDefault(); - } - } - } - }); -}; - -/** - * Show drag and drop animation when textarea is present - * - * This function will enable the drag and drop animation for a specified - * textarea. - * - * @param {HTMLElement} textarea Textarea DOM object to apply editor to - */ -phpbb.showDragNDrop = function(textarea) { - if (!textarea) { - return; - } - - $('body').on('dragenter dragover', function () { - $(textarea).addClass('drag-n-drop'); - }).on('dragleave dragout dragend drop', function() { - $(textarea).removeClass('drag-n-drop'); - }); - $(textarea).on('dragenter dragover', function () { - $(textarea).addClass('drag-n-drop-highlight'); - }).on('dragleave dragout dragend drop', function() { - $(textarea).removeClass('drag-n-drop-highlight'); - }); -}; - -/** -* List of classes that toggle dropdown menu, -* list of classes that contain visible dropdown menu -* -* Add your own classes to strings with comma (probably you -* will never need to do that) -*/ -phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle'; -phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible'; - -/** -* Dropdown toggle event handler -* This handler is used by phpBB.registerDropdown() and other functions -*/ -phpbb.toggleDropdown = function() { - var $this = $(this), - options = $this.data('dropdown-options'), - parent = options.parent, - visible = parent.hasClass('dropdown-visible'), - direction; - - if (!visible) { - // Hide other dropdown menus - $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); - - // Figure out direction of dropdown - direction = options.direction; - var verticalDirection = options.verticalDirection, - offset = $this.offset(); - - if (direction === 'auto') { - if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) { - direction = 'right'; - } else { - direction = 'left'; - } - } - parent.toggleClass(options.leftClass, direction === 'left') - .toggleClass(options.rightClass, direction === 'right'); - - if (verticalDirection === 'auto') { - var height = $(window).height(), - top = offset.top - $(window).scrollTop(); - - verticalDirection = (top < height * 0.7) ? 'down' : 'up'; - } - parent.toggleClass(options.upClass, verticalDirection === 'up') - .toggleClass(options.downClass, verticalDirection === 'down'); - } - - options.dropdown.toggle(); - parent.toggleClass(options.visibleClass, !visible) - .toggleClass('dropdown-visible', !visible); - - // Check dimensions when showing dropdown - // !visible because variable shows state of dropdown before it was toggled - if (!visible) { - var windowWidth = $(window).width(); - - options.dropdown.find('.dropdown-contents').each(function() { - var $this = $(this); - - $this.css({ - marginLeft: 0, - left: 0, - marginRight: 0, - maxWidth: (windowWidth - 4) + 'px' - }); - - var offset = $this.offset().left, - width = $this.outerWidth(true); - - if (offset < 2) { - $this.css('left', (2 - offset) + 'px'); - } else if ((offset + width + 2) > windowWidth) { - $this.css('margin-left', (windowWidth - offset - width - 2) + 'px'); - } - - // Check whether the vertical scrollbar is present. - $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight()); - - }); - var freeSpace = parent.offset().left - 4; - - if (direction === 'left') { - options.dropdown.css('margin-left', '-' + freeSpace + 'px'); - - // Try to position the notification dropdown correctly in RTL-responsive mode - if (options.dropdown.hasClass('dropdown-extended')) { - var contentWidth, - fullFreeSpace = freeSpace + parent.outerWidth(); - - options.dropdown.find('.dropdown-contents').each(function() { - contentWidth = parseInt($(this).outerWidth(), 10); - $(this).css({ marginLeft: 0, left: 0 }); - }); - - var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px'; - options.dropdown.css({ - width: maxOffset, - marginLeft: -maxOffset - }); - } - } else { - options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px'); - } - } - - // Prevent event propagation - if (arguments.length > 0) { - try { - var e = arguments[0]; - e.preventDefault(); - e.stopPropagation(); - } catch (error) { } - } - return false; -}; - -/** -* Toggle dropdown submenu -*/ -phpbb.toggleSubmenu = function(e) { - $(this).siblings('.dropdown-submenu').toggle(); - e.preventDefault(); -}; - -/** -* Register dropdown menu -* Shows/hides dropdown, decides which side to open to -* -* @param {jQuery} toggle Link that toggles dropdown. -* @param {jQuery} dropdown Dropdown menu. -* @param {Object} options List of options. Optional. -*/ -phpbb.registerDropdown = function(toggle, dropdown, options) { - var ops = { - parent: toggle.parent(), // Parent item to add classes to - direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right - verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down - visibleClass: 'visible', // Class to add to parent item when dropdown is visible - leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side - rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side - upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item - downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item - }; - if (options) { - ops = $.extend(ops, options); - } - ops.dropdown = dropdown; - - ops.parent.addClass('dropdown-container'); - toggle.addClass('dropdown-toggle'); - - toggle.data('dropdown-options', ops); - - toggle.click(phpbb.toggleDropdown); - $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu); -}; - -/** -* Get the HTML for a color palette table. -* -* @param {string} dir Palette direction - either v or h -* @param {int} width Palette cell width. -* @param {int} height Palette cell height. -*/ -phpbb.colorPalette = function(dir, width, height) { - var r, g, b, - numberList = new Array(6), - color = '', - html = ''; - - numberList[0] = '00'; - numberList[1] = '40'; - numberList[2] = '80'; - numberList[3] = 'BF'; - numberList[4] = 'FF'; - - var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette'; - html += ''; - - for (r = 0; r < 5; r++) { - if (dir === 'h') { - html += ''; - } - - for (g = 0; g < 5; g++) { - if (dir === 'v') { - html += ''; - } - - for (b = 0; b < 5; b++) { - color = '' + numberList[r] + numberList[g] + numberList[b]; - html += ''; - } - - if (dir === 'v') { - html += ''; - } - } - - if (dir === 'h') { - html += ''; - } - } - html += '
'; - html += '
'; - return html; -}; - -/** -* Register a color palette. -* -* @param {jQuery} el jQuery object for the palette container. -*/ -phpbb.registerPalette = function(el) { - var orientation = el.attr('data-color-palette') || el.attr('data-orientation'), // data-orientation kept for backwards compat. - height = el.attr('data-height'), - width = el.attr('data-width'), - target = el.attr('data-target'), - bbcode = el.attr('data-bbcode'); - - // Insert the palette HTML into the container. - el.html(phpbb.colorPalette(orientation, width, height)); - - // Add toggle control. - $('#color_palette_toggle').click(function(e) { - el.toggle(); - e.preventDefault(); - }); - - // Attach event handler when a palette cell is clicked. - $(el).on('click', 'a', function(e) { - var color = $(this).attr('data-color'); - - if (bbcode) { - bbfontstyle('[color=#' + color + ']', '[/color]'); - } else { - $(target).val(color); - } - e.preventDefault(); - }); -}; - -/** -* Set display of page element -* -* @param {string} id The ID of the element to change -* @param {int} action Set to 0 if element display should be toggled, -1 for -* hiding the element, and 1 for showing it. -* @param {string} type Display type that should be used, e.g. inline, block or -* other CSS "display" types -*/ -phpbb.toggleDisplay = function(id, action, type) { - if (!type) { - type = 'block'; - } - - var $element = $('#' + id); - - var display = $element.css('display'); - if (!action) { - action = (display === '' || display === type) ? -1 : 1; - } - $element.css('display', ((action === 1) ? type : 'none')); -}; - -/** -* Toggle additional settings based on the selected -* option of select element. -* -* @param {jQuery} el jQuery select element object. -*/ -phpbb.toggleSelectSettings = function(el) { - el.children().each(function() { - var $this = $(this), - $setting = $($this.data('toggle-setting')); - $setting.toggle($this.is(':selected')); - - // Disable any input elements that are not visible right now - if ($this.is(':selected')) { - $($this.data('toggle-setting') + ' input').prop('disabled', false); - } else { - $($this.data('toggle-setting') + ' input').prop('disabled', true); - } - }); -}; - -/** -* Get function from name. -* Based on http://stackoverflow.com/a/359910 -* -* @param {string} functionName Function to get. -* @returns function -*/ -phpbb.getFunctionByName = function (functionName) { - var namespaces = functionName.split('.'), - func = namespaces.pop(), - context = window; - - for (var i = 0; i < namespaces.length; i++) { - context = context[namespaces[i]]; - } - return context[func]; -}; - -/** -* Register page dropdowns. -*/ -phpbb.registerPageDropdowns = function() { - var $body = $('body'); - - $body.find('.dropdown-container').each(function() { - var $this = $(this), - $trigger = $this.find('.dropdown-trigger:first'), - $contents = $this.find('.dropdown'), - options = { - direction: 'auto', - verticalDirection: 'auto' - }, - data; - - if (!$trigger.length) { - data = $this.attr('data-dropdown-trigger'); - $trigger = data ? $this.children(data) : $this.children('a:first'); - } - - if (!$contents.length) { - data = $this.attr('data-dropdown-contents'); - $contents = data ? $this.children(data) : $this.children('div:first'); - } - - if (!$trigger.length || !$contents.length) { - return; - } - - if ($this.hasClass('dropdown-up')) { - options.verticalDirection = 'up'; - } - if ($this.hasClass('dropdown-down')) { - options.verticalDirection = 'down'; - } - if ($this.hasClass('dropdown-left')) { - options.direction = 'left'; - } - if ($this.hasClass('dropdown-right')) { - options.direction = 'right'; - } - - phpbb.registerDropdown($trigger, $contents, options); - }); - - // Hide active dropdowns when click event happens outside - $body.click(function(e) { - var $parents = $(e.target).parents(); - if (!$parents.is(phpbb.dropdownVisibleContainers)) { - $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); - } - }); -}; - -/** - * Handle avatars to be lazy loaded. - */ -phpbb.lazyLoadAvatars = function loadAvatars() { - $('.avatar[data-src]').each(function () { - var $avatar = $(this); - - $avatar - .attr('src', $avatar.data('src')) - .removeAttr('data-src'); - }); -}; - -phpbb.recaptcha = { - button: null, - ready: false, - - token: $('input[name="recaptcha_token"]'), - form: $('.g-recaptcha').parents('form'), - v3: $('[data-recaptcha-v3]'), - - load: function() { - phpbb.recaptcha.bindButton(); - phpbb.recaptcha.bindForm(); - }, - bindButton: function() { - phpbb.recaptcha.form.find('input[type="submit"]').on('click', function() { - // Listen to all the submit buttons for the form that has reCAPTCHA protection, - // and store it so we can click the exact same button later on when we are ready. - phpbb.recaptcha.button = this; - }); - }, - bindForm: function() { - phpbb.recaptcha.form.on('submit', function(e) { - // If ready is false, it means the user pressed a submit button. - // And the form was not submitted by us, after the token was loaded. - if (!phpbb.recaptcha.ready) { - // If version 3 is used, we need to make a different execution, - // including the action and the site key. - if (phpbb.recaptcha.v3.length) { - grecaptcha.execute( - phpbb.recaptcha.v3.data('recaptcha-v3'), - {action: phpbb.recaptcha.v3.val()} - ).then(function(token) { - // Place the token inside the form - phpbb.recaptcha.token.val(token); - - // And now we submit the form. - phpbb.recaptcha.submitForm(); - }); - } else { - // Regular version 2 execution - grecaptcha.execute(); - } - - // Do not submit the form - e.preventDefault(); - } - }); - }, - submitForm: function() { - // Now we are ready, so set it to true. - // so the 'submit' event doesn't run multiple times. - phpbb.recaptcha.ready = true; - - if (phpbb.recaptcha.button) { - // If there was a specific button pressed initially, trigger the same button - phpbb.recaptcha.button.click(); - } else { - if (typeof phpbb.recaptcha.form.submit !== 'function') { - // Rename input[name="submit"] so that we can submit the form - phpbb.recaptcha.form.submit.name = 'submit_btn'; - } - - phpbb.recaptcha.form.submit(); - } - } -}; - -// reCAPTCHA v2 doesn't accept callback functions nested inside objects -// so we need to make this helper functions here -window.phpbbRecaptchaOnLoad = function() { - phpbb.recaptcha.load(); -}; - -window.phpbbRecaptchaOnSubmit = function() { - phpbb.recaptcha.submitForm(); -}; - -$(window).on('load', phpbb.lazyLoadAvatars); - -/** -* Apply code editor to all textarea elements with data-bbcode attribute -*/ -$(function() { - // reCAPTCHA v3 needs to be initialized - if (phpbb.recaptcha.v3.length) { - phpbb.recaptcha.load(); - } - - $('textarea[data-bbcode]').each(function() { - phpbb.applyCodeEditor(this); - }); - - phpbb.registerPageDropdowns(); - - $('[data-color-palette], [data-orientation]').each(function() { - phpbb.registerPalette($(this)); - }); - - // Update browser history URL to point to specific post in viewtopic.php - // when using view=unread#unread link. - phpbb.history.replaceUrl($('#unread[data-url]').data('url')); - - // Hide settings that are not selected via select element. - $('select[data-togglable-settings]').each(function() { - var $this = $(this); - - $this.change(function() { - phpbb.toggleSelectSettings($this); - }); - phpbb.toggleSelectSettings($this); - }); -}); - + phpbb.resizeTextArea = function ($items, options) { + // Configuration + var configuration = { + minWindowHeight: 500, + minHeight: 200, + maxHeight: 500, + heightDiff: 200, + resizeCallback: function () {}, + resetCallback: function () {}, + }; + + if (phpbb.isTouch) { + return; + } + + if (arguments.length > 1) { + configuration = $.extend(configuration, options); + } + + function resetAutoResize(item) { + var $item = $(item); + if ($item.hasClass("auto-resized")) { + $(item).css({ height: "", resize: "" }).removeClass("auto-resized"); + configuration.resetCallback.call(item, $item); + } + } + + function autoResize(item) { + function setHeight(height) { + height += parseInt($item.css("height"), 10) - $item.innerHeight(); + $item + .css({ height: height + "px", resize: "none" }) + .addClass("auto-resized"); + configuration.resizeCallback.call(item, $item); + } + + var windowHeight = $(window).height(); + + if (windowHeight < configuration.minWindowHeight) { + resetAutoResize(item); + return; + } + + var maxHeight = Math.min( + Math.max( + windowHeight - configuration.heightDiff, + configuration.minHeight + ), + configuration.maxHeight + ), + $item = $(item), + height = parseInt($item.innerHeight(), 10), + scrollHeight = item.scrollHeight ? item.scrollHeight : 0; + + if (height < 0) { + return; + } + + if (height > maxHeight) { + setHeight(maxHeight); + } else if (scrollHeight > height + 5) { + setHeight(Math.min(maxHeight, scrollHeight)); + } + } + + $items + .on("focus change keyup", function () { + $(this).each(function () { + autoResize(this); + }); + }) + .change(); + + $(window).resize(function () { + $items.each(function () { + if ($(this).hasClass("auto-resized")) { + autoResize(this); + } + }); + }); + }; + + /** + * Check if cursor in textarea is currently inside a bbcode tag + * + * @param {object} textarea Textarea DOM object + * @param {Array} startTags List of start tags to look for + * For example, Array('[code]', '[code=') + * @param {Array} endTags List of end tags to look for + * For example, Array('[/code]') + * + * @returns {boolean} True if cursor is in bbcode tag + */ + phpbb.inBBCodeTag = function (textarea, startTags, endTags) { + var start = textarea.selectionStart, + lastEnd = -1, + lastStart = -1, + i, + index, + value; + + if (typeof start !== "number") { + return false; + } + + value = textarea.value.toLowerCase(); + + for (i = 0; i < startTags.length; i++) { + var tagLength = startTags[i].length; + if (start >= tagLength) { + index = value.lastIndexOf(startTags[i], start - tagLength); + lastStart = Math.max(lastStart, index); + } + } + if (lastStart === -1) { + return false; + } + + if (start > 0) { + for (i = 0; i < endTags.length; i++) { + index = value.lastIndexOf(endTags[i], start - 1); + lastEnd = Math.max(lastEnd, index); + } + } + + return lastEnd < lastStart; + }; + + /** + * Adjust textarea to manage code bbcode + * + * This function allows to use tab characters when typing code + * and keeps indentation of previous line of code when adding new + * line while typing code. + * + * Editor's functionality is changed only when cursor is between + * [code] and [/code] bbcode tags. + * + * @param {object} textarea Textarea DOM object to apply editor to + */ + phpbb.applyCodeEditor = function (textarea) { + // list of allowed start and end bbcode code tags, in lower case + var startTags = ["[code]", "[code="], + startTagsEnd = "]", + endTags = ["[/code]"]; + + if (!textarea || typeof textarea.selectionStart !== "number") { + return; + } + + if ($(textarea).data("code-editor") === true) { + return; + } + + function inTag() { + return phpbb.inBBCodeTag(textarea, startTags, endTags); + } + + /** + * Get line of text before cursor + * + * @param {boolean} stripCodeStart If true, only part of line + * after [code] tag will be returned. + * + * @returns {string} Line of text + */ + function getLastLine(stripCodeStart) { + var start = textarea.selectionStart, + value = textarea.value, + index = value.lastIndexOf("\n", start - 1); + + value = value.substring(index + 1, start); + + if (stripCodeStart) { + for (var i = 0; i < startTags.length; i++) { + index = value.lastIndexOf(startTags[i]); + if (index >= 0) { + var tagLength = startTags[i].length; + + value = value.substring(index + tagLength); + if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) { + index = value.indexOf(startTagsEnd); + + if (index >= 0) { + value = value.substr(index + 1); + } + } + } + } + } + + return value; + } + + /** + * Append text at cursor position + * + * @param {string} text Text to append + */ + function appendText(text) { + var start = textarea.selectionStart, + end = textarea.selectionEnd, + value = textarea.value; + + textarea.value = value.substr(0, start) + text + value.substr(end); + textarea.selectionStart = textarea.selectionEnd = start + text.length; + } + + $(textarea) + .data("code-editor", true) + .on("keydown", function (event) { + var key = event.keyCode || event.which; + + // intercept tabs + if ( + key === keymap.TAB && + !event.ctrlKey && + !event.shiftKey && + !event.altKey && + !event.metaKey + ) { + if (inTag()) { + appendText("\t"); + event.preventDefault(); + return; + } + } + + // intercept new line characters + if (key === keymap.ENTER) { + if (inTag()) { + var lastLine = getLastLine(true), + code = "" + /^\s*/g.exec(lastLine); + + if (code.length > 0) { + appendText("\n" + code); + event.preventDefault(); + } + } + } + }); + }; + + /** + * Show drag and drop animation when textarea is present + * + * This function will enable the drag and drop animation for a specified + * textarea. + * + * @param {HTMLElement} textarea Textarea DOM object to apply editor to + */ + phpbb.showDragNDrop = function (textarea) { + if (!textarea) { + return; + } + + $("body") + .on("dragenter dragover", function () { + $(textarea).addClass("drag-n-drop"); + }) + .on("dragleave dragout dragend drop", function () { + $(textarea).removeClass("drag-n-drop"); + }); + $(textarea) + .on("dragenter dragover", function () { + $(textarea).addClass("drag-n-drop-highlight"); + }) + .on("dragleave dragout dragend drop", function () { + $(textarea).removeClass("drag-n-drop-highlight"); + }); + }; + + /** + * List of classes that toggle dropdown menu, + * list of classes that contain visible dropdown menu + * + * Add your own classes to strings with comma (probably you + * will never need to do that) + */ + phpbb.dropdownHandles = + ".dropdown-container.dropdown-visible .dropdown-toggle"; + phpbb.dropdownVisibleContainers = ".dropdown-container.dropdown-visible"; + + /** + * Dropdown toggle event handler + * This handler is used by phpBB.registerDropdown() and other functions + */ + phpbb.toggleDropdown = function () { + var $this = $(this), + options = $this.data("dropdown-options"), + parent = options.parent, + visible = parent.hasClass("dropdown-visible"), + direction; + + if (!visible) { + // Hide other dropdown menus + $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); + + // Figure out direction of dropdown + direction = options.direction; + var verticalDirection = options.verticalDirection, + offset = $this.offset(); + + if (direction === "auto") { + if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) { + direction = "right"; + } else { + direction = "left"; + } + } + parent + .toggleClass(options.leftClass, direction === "left") + .toggleClass(options.rightClass, direction === "right"); + + if (verticalDirection === "auto") { + var height = $(window).height(), + top = offset.top - $(window).scrollTop(); + + verticalDirection = top < height * 0.7 ? "down" : "up"; + } + parent + .toggleClass(options.upClass, verticalDirection === "up") + .toggleClass(options.downClass, verticalDirection === "down"); + } + + options.dropdown.toggle(); + parent + .toggleClass(options.visibleClass, !visible) + .toggleClass("dropdown-visible", !visible); + + // Check dimensions when showing dropdown + // !visible because variable shows state of dropdown before it was toggled + if (!visible) { + var windowWidth = $(window).width(); + + options.dropdown.find(".dropdown-contents").each(function () { + var $this = $(this); + + $this.css({ + marginLeft: 0, + left: 0, + marginRight: 0, + maxWidth: windowWidth - 4 + "px", + }); + + var offset = $this.offset().left, + width = $this.outerWidth(true); + + if (offset < 2) { + $this.css("left", 2 - offset + "px"); + } else if (offset + width + 2 > windowWidth) { + $this.css("margin-left", windowWidth - offset - width - 2 + "px"); + } + + // Check whether the vertical scrollbar is present. + $this.toggleClass( + "dropdown-nonscroll", + this.scrollHeight === $this.innerHeight() + ); + }); + var freeSpace = parent.offset().left - 4; + + if (direction === "left") { + options.dropdown.css("margin-left", "-" + freeSpace + "px"); + + // Try to position the notification dropdown correctly in RTL-responsive mode + if (options.dropdown.hasClass("dropdown-extended")) { + var contentWidth, + fullFreeSpace = freeSpace + parent.outerWidth(); + + options.dropdown.find(".dropdown-contents").each(function () { + contentWidth = parseInt($(this).outerWidth(), 10); + $(this).css({ marginLeft: 0, left: 0 }); + }); + + var maxOffset = Math.min(contentWidth, fullFreeSpace) + "px"; + options.dropdown.css({ + width: maxOffset, + marginLeft: -maxOffset, + }); + } + } else { + options.dropdown.css( + "margin-right", + "-" + (windowWidth + freeSpace) + "px" + ); + } + } + + // Prevent event propagation + if (arguments.length > 0) { + try { + var e = arguments[0]; + e.preventDefault(); + e.stopPropagation(); + } catch (error) {} + } + return false; + }; + + /** + * Toggle dropdown submenu + */ + phpbb.toggleSubmenu = function (e) { + $(this).siblings(".dropdown-submenu").toggle(); + e.preventDefault(); + }; + + /** + * Register dropdown menu + * Shows/hides dropdown, decides which side to open to + * + * @param {jQuery} toggle Link that toggles dropdown. + * @param {jQuery} dropdown Dropdown menu. + * @param {Object} options List of options. Optional. + */ + phpbb.registerDropdown = function (toggle, dropdown, options) { + var ops = { + parent: toggle.parent(), // Parent item to add classes to + direction: "auto", // Direction of dropdown menu. Possible values: auto, left, right + verticalDirection: "auto", // Vertical direction. Possible values: auto, up, down + visibleClass: "visible", // Class to add to parent item when dropdown is visible + leftClass: "dropdown-left", // Class to add to parent item when dropdown opens to left side + rightClass: "dropdown-right", // Class to add to parent item when dropdown opens to right side + upClass: "dropdown-up", // Class to add to parent item when dropdown opens above menu item + downClass: "dropdown-down", // Class to add to parent item when dropdown opens below menu item + }; + if (options) { + ops = $.extend(ops, options); + } + ops.dropdown = dropdown; + + ops.parent.addClass("dropdown-container"); + toggle.addClass("dropdown-toggle"); + + toggle.data("dropdown-options", ops); + + toggle.click(phpbb.toggleDropdown); + $(".dropdown-toggle-submenu", ops.parent).click(phpbb.toggleSubmenu); + }; + + /** + * Get the HTML for a color palette table. + * + * @param {string} dir Palette direction - either v or h + * @param {int} width Palette cell width. + * @param {int} height Palette cell height. + */ + phpbb.colorPalette = function (dir, width, height) { + var r, + g, + b, + numberList = new Array(6), + color = "", + html = ""; + + numberList[0] = "00"; + numberList[1] = "40"; + numberList[2] = "80"; + numberList[3] = "BF"; + numberList[4] = "FF"; + + var tableClass = dir === "h" ? "horizontal-palette" : "vertical-palette"; + html += + ''; + + for (r = 0; r < 5; r++) { + if (dir === "h") { + html += ""; + } + + for (g = 0; g < 5; g++) { + if (dir === "v") { + html += ""; + } + + for (b = 0; b < 5; b++) { + color = "" + numberList[r] + numberList[g] + numberList[b]; + html += + '"; + } + + if (dir === "v") { + html += ""; + } + } + + if (dir === "h") { + html += ""; + } + } + html += "
'; + html += "
"; + return html; + }; + + /** + * Register a color palette. + * + * @param {jQuery} el jQuery object for the palette container. + */ + phpbb.registerPalette = function (el) { + var orientation = + el.attr("data-color-palette") || el.attr("data-orientation"), // data-orientation kept for backwards compat. + height = el.attr("data-height"), + width = el.attr("data-width"), + target = el.attr("data-target"), + bbcode = el.attr("data-bbcode"); + + // Insert the palette HTML into the container. + el.html(phpbb.colorPalette(orientation, width, height)); + + // Add toggle control. + $("#color_palette_toggle").click(function (e) { + el.toggle(); + e.preventDefault(); + }); + + // Attach event handler when a palette cell is clicked. + $(el).on("click", "a", function (e) { + var color = $(this).attr("data-color"); + + if (bbcode) { + bbfontstyle("[color=#" + color + "]", "[/color]"); + } else { + $(target).val(color); + } + e.preventDefault(); + }); + }; + + /** + * Set display of page element + * + * @param {string} id The ID of the element to change + * @param {int} action Set to 0 if element display should be toggled, -1 for + * hiding the element, and 1 for showing it. + * @param {string} type Display type that should be used, e.g. inline, block or + * other CSS "display" types + */ + phpbb.toggleDisplay = function (id, action, type) { + if (!type) { + type = "block"; + } + + var $element = $("#" + id); + + var display = $element.css("display"); + if (!action) { + action = display === "" || display === type ? -1 : 1; + } + $element.css("display", action === 1 ? type : "none"); + }; + + /** + * Toggle additional settings based on the selected + * option of select element. + * + * @param {jQuery} el jQuery select element object. + */ + phpbb.toggleSelectSettings = function (el) { + el.children().each(function () { + var $this = $(this), + $setting = $($this.data("toggle-setting")); + $setting.toggle($this.is(":selected")); + + // Disable any input elements that are not visible right now + if ($this.is(":selected")) { + $($this.data("toggle-setting") + " input").prop("disabled", false); + } else { + $($this.data("toggle-setting") + " input").prop("disabled", true); + } + }); + }; + + /** + * Get function from name. + * Based on http://stackoverflow.com/a/359910 + * + * @param {string} functionName Function to get. + * @returns function + */ + phpbb.getFunctionByName = function (functionName) { + var namespaces = functionName.split("."), + func = namespaces.pop(), + context = window; + + for (var i = 0; i < namespaces.length; i++) { + context = context[namespaces[i]]; + } + return context[func]; + }; + + /** + * Register page dropdowns. + */ + phpbb.registerPageDropdowns = function () { + var $body = $("body"); + + $body.find(".dropdown-container").each(function () { + var $this = $(this), + $trigger = $this.find(".dropdown-trigger:first"), + $contents = $this.find(".dropdown"), + options = { + direction: "auto", + verticalDirection: "auto", + }, + data; + + if (!$trigger.length) { + data = $this.attr("data-dropdown-trigger"); + $trigger = data ? $this.children(data) : $this.children("a:first"); + } + + if (!$contents.length) { + data = $this.attr("data-dropdown-contents"); + $contents = data ? $this.children(data) : $this.children("div:first"); + } + + if (!$trigger.length || !$contents.length) { + return; + } + + if ($this.hasClass("dropdown-up")) { + options.verticalDirection = "up"; + } + if ($this.hasClass("dropdown-down")) { + options.verticalDirection = "down"; + } + if ($this.hasClass("dropdown-left")) { + options.direction = "left"; + } + if ($this.hasClass("dropdown-right")) { + options.direction = "right"; + } + + phpbb.registerDropdown($trigger, $contents, options); + }); + + // Hide active dropdowns when click event happens outside + $body.click(function (e) { + var $parents = $(e.target).parents(); + if (!$parents.is(phpbb.dropdownVisibleContainers)) { + $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); + } + }); + }; + + /** + * Handle avatars to be lazy loaded. + */ + phpbb.lazyLoadAvatars = function loadAvatars() { + $(".avatar[data-src]").each(function () { + var $avatar = $(this); + + $avatar.attr("src", $avatar.data("src")).removeAttr("data-src"); + }); + }; + + phpbb.recaptcha = { + button: null, + ready: false, + + token: $('input[name="recaptcha_token"]'), + form: $(".g-recaptcha").parents("form"), + v3: $("[data-recaptcha-v3]"), + + load: function () { + phpbb.recaptcha.bindButton(); + phpbb.recaptcha.bindForm(); + }, + bindButton: function () { + phpbb.recaptcha.form + .find('input[type="submit"]') + .on("click", function () { + // Listen to all the submit buttons for the form that has reCAPTCHA protection, + // and store it so we can click the exact same button later on when we are ready. + phpbb.recaptcha.button = this; + }); + }, + bindForm: function () { + phpbb.recaptcha.form.on("submit", function (e) { + // If ready is false, it means the user pressed a submit button. + // And the form was not submitted by us, after the token was loaded. + if (!phpbb.recaptcha.ready) { + // If version 3 is used, we need to make a different execution, + // including the action and the site key. + if (phpbb.recaptcha.v3.length) { + grecaptcha + .execute(phpbb.recaptcha.v3.data("recaptcha-v3"), { + action: phpbb.recaptcha.v3.val(), + }) + .then(function (token) { + // Place the token inside the form + phpbb.recaptcha.token.val(token); + + // And now we submit the form. + phpbb.recaptcha.submitForm(); + }); + } else { + // Regular version 2 execution + grecaptcha.execute(); + } + + // Do not submit the form + e.preventDefault(); + } + }); + }, + submitForm: function () { + // Now we are ready, so set it to true. + // so the 'submit' event doesn't run multiple times. + phpbb.recaptcha.ready = true; + + if (phpbb.recaptcha.button) { + // If there was a specific button pressed initially, trigger the same button + phpbb.recaptcha.button.click(); + } else { + if (typeof phpbb.recaptcha.form.submit !== "function") { + // Rename input[name="submit"] so that we can submit the form + phpbb.recaptcha.form.submit.name = "submit_btn"; + } + + phpbb.recaptcha.form.submit(); + } + }, + }; + + // reCAPTCHA v2 doesn't accept callback functions nested inside objects + // so we need to make this helper functions here + window.phpbbRecaptchaOnLoad = function () { + phpbb.recaptcha.load(); + }; + + window.phpbbRecaptchaOnSubmit = function () { + phpbb.recaptcha.submitForm(); + }; + + $(window).on("load", phpbb.lazyLoadAvatars); + + /** + * Apply code editor to all textarea elements with data-bbcode attribute + */ + $(function () { + // reCAPTCHA v3 needs to be initialized + if (phpbb.recaptcha.v3.length) { + phpbb.recaptcha.load(); + } + + $("textarea[data-bbcode]").each(function () { + phpbb.applyCodeEditor(this); + }); + + phpbb.registerPageDropdowns(); + + $("[data-color-palette], [data-orientation]").each(function () { + phpbb.registerPalette($(this)); + }); + + // Update browser history URL to point to specific post in viewtopic.php + // when using view=unread#unread link. + phpbb.history.replaceUrl($("#unread[data-url]").data("url")); + + // Hide settings that are not selected via select element. + $("select[data-togglable-settings]").each(function () { + var $this = $(this); + + $this.change(function () { + phpbb.toggleSelectSettings($this); + }); + phpbb.toggleSelectSettings($this); + }); + }); })(jQuery); // Avoid conflicts with other libraries diff --git a/assets/javascript/editor.js b/assets/javascript/editor.js index 24cbc09..63f80b1 100644 --- a/assets/javascript/editor.js +++ b/assets/javascript/editor.js @@ -1,7 +1,7 @@ /** -* bbCode control by subBlue design [ www.subBlue.com ] -* Includes unixsafe colour palette selector by SHS` -*/ + * bbCode control by subBlue design [ www.subBlue.com ] + * Includes unixsafe colour palette selector by SHS` + */ // Startup variables var imageTag = false; @@ -13,406 +13,426 @@ var bbcodeEnabled = true; var clientPC = navigator.userAgent.toLowerCase(); // Get client info var clientVer = parseInt(navigator.appVersion, 10); // Get browser version -var is_ie = ((clientPC.indexOf('msie') !== -1) && (clientPC.indexOf('opera') === -1)); -var is_win = ((clientPC.indexOf('win') !== -1) || (clientPC.indexOf('16bit') !== -1)); +var is_ie = clientPC.indexOf("msie") !== -1 && clientPC.indexOf("opera") === -1; +var is_win = clientPC.indexOf("win") !== -1 || clientPC.indexOf("16bit") !== -1; var baseHeight; /** -* Fix a bug involving the TextRange object. From -* http://www.frostjedi.com/terra/scripts/demo/caretBug.html -*/ + * Fix a bug involving the TextRange object. From + * http://www.frostjedi.com/terra/scripts/demo/caretBug.html + */ function initInsertions() { - var doc; + var doc; - if (document.forms[form_name]) { - doc = document; - } else { - doc = opener.document; - } + if (document.forms[form_name]) { + doc = document; + } else { + doc = opener.document; + } - var textarea = doc.forms[form_name].elements[text_name]; + var textarea = doc.forms[form_name].elements[text_name]; - if (is_ie && typeof(baseHeight) !== 'number') { - textarea.focus(); - baseHeight = doc.selection.createRange().duplicate().boundingHeight; + if (is_ie && typeof baseHeight !== "number") { + textarea.focus(); + baseHeight = doc.selection.createRange().duplicate().boundingHeight; - if (!document.forms[form_name]) { - document.body.focus(); - } - } + if (!document.forms[form_name]) { + document.body.focus(); + } + } } /** -* bbstyle -*/ + * bbstyle + */ function bbstyle(bbnumber) { - if (bbnumber !== -1) { - bbfontstyle(bbtags[bbnumber], bbtags[bbnumber+1]); - } else { - insert_text('[*]'); - document.forms[form_name].elements[text_name].focus(); - } + if (bbnumber !== -1) { + bbfontstyle(bbtags[bbnumber], bbtags[bbnumber + 1]); + } else { + insert_text("[*]"); + document.forms[form_name].elements[text_name].focus(); + } } /** -* Apply bbcodes -*/ + * Apply bbcodes + */ function bbfontstyle(bbopen, bbclose) { - theSelection = false; - - var textarea = document.forms[form_name].elements[text_name]; - - textarea.focus(); - - if ((clientVer >= 4) && is_ie && is_win) { - // Get text selection - theSelection = document.selection.createRange().text; - - if (theSelection) { - // Add tags around selection - document.selection.createRange().text = bbopen + theSelection + bbclose; - textarea.focus(); - theSelection = ''; - return; - } - } else if (textarea.selectionEnd && (textarea.selectionEnd - textarea.selectionStart > 0)) { - mozWrap(textarea, bbopen, bbclose); - textarea.focus(); - theSelection = ''; - return; - } - - //The new position for the cursor after adding the bbcode - var caret_pos = getCaretPosition(textarea).start; - var new_pos = caret_pos + bbopen.length; - - // Open tag - insert_text(bbopen + bbclose); - - // Center the cursor when we don't have a selection - // Gecko and proper browsers - if (!isNaN(textarea.selectionStart)) { - textarea.selectionStart = new_pos; - textarea.selectionEnd = new_pos; - } - // IE - else if (document.selection) { - var range = textarea.createTextRange(); - range.move("character", new_pos); - range.select(); - storeCaret(textarea); - } - - textarea.focus(); + theSelection = false; + + var textarea = document.forms[form_name].elements[text_name]; + + textarea.focus(); + + if (clientVer >= 4 && is_ie && is_win) { + // Get text selection + theSelection = document.selection.createRange().text; + + if (theSelection) { + // Add tags around selection + document.selection.createRange().text = bbopen + theSelection + bbclose; + textarea.focus(); + theSelection = ""; + return; + } + } else if ( + textarea.selectionEnd && + textarea.selectionEnd - textarea.selectionStart > 0 + ) { + mozWrap(textarea, bbopen, bbclose); + textarea.focus(); + theSelection = ""; + return; + } + + //The new position for the cursor after adding the bbcode + var caret_pos = getCaretPosition(textarea).start; + var new_pos = caret_pos + bbopen.length; + + // Open tag + insert_text(bbopen + bbclose); + + // Center the cursor when we don't have a selection + // Gecko and proper browsers + if (!isNaN(textarea.selectionStart)) { + textarea.selectionStart = new_pos; + textarea.selectionEnd = new_pos; + } + // IE + else if (document.selection) { + var range = textarea.createTextRange(); + range.move("character", new_pos); + range.select(); + storeCaret(textarea); + } + + textarea.focus(); } /** -* Insert text at position -*/ + * Insert text at position + */ function insert_text(text, spaces, popup) { - var textarea; - - if (!popup) { - textarea = document.forms[form_name].elements[text_name]; - } else { - textarea = opener.document.forms[form_name].elements[text_name]; - } - - if (spaces) { - text = ' ' + text + ' '; - } - - // Since IE9, IE also has textarea.selectionStart, but it still needs to be treated the old way. - // Therefore we simply add a !is_ie here until IE fixes the text-selection completely. - if (!isNaN(textarea.selectionStart) && !is_ie) { - var sel_start = textarea.selectionStart; - var sel_end = textarea.selectionEnd; - - mozWrap(textarea, text, ''); - textarea.selectionStart = sel_start + text.length; - textarea.selectionEnd = sel_end + text.length; - } else if (textarea.createTextRange && textarea.caretPos) { - if (baseHeight !== textarea.caretPos.boundingHeight) { - textarea.focus(); - storeCaret(textarea); - } - - var caret_pos = textarea.caretPos; - caret_pos.text = caret_pos.text.charAt(caret_pos.text.length - 1) === ' ' ? caret_pos.text + text + ' ' : caret_pos.text + text; - } else { - textarea.value = textarea.value + text; - } - - if (!popup) { - textarea.focus(); - } + var textarea; + + if (!popup) { + textarea = document.forms[form_name].elements[text_name]; + } else { + textarea = opener.document.forms[form_name].elements[text_name]; + } + + if (spaces) { + text = " " + text + " "; + } + + // Since IE9, IE also has textarea.selectionStart, but it still needs to be treated the old way. + // Therefore we simply add a !is_ie here until IE fixes the text-selection completely. + if (!isNaN(textarea.selectionStart) && !is_ie) { + var sel_start = textarea.selectionStart; + var sel_end = textarea.selectionEnd; + + mozWrap(textarea, text, ""); + textarea.selectionStart = sel_start + text.length; + textarea.selectionEnd = sel_end + text.length; + } else if (textarea.createTextRange && textarea.caretPos) { + if (baseHeight !== textarea.caretPos.boundingHeight) { + textarea.focus(); + storeCaret(textarea); + } + + var caret_pos = textarea.caretPos; + caret_pos.text = + caret_pos.text.charAt(caret_pos.text.length - 1) === " " + ? caret_pos.text + text + " " + : caret_pos.text + text; + } else { + textarea.value = textarea.value + text; + } + + if (!popup) { + textarea.focus(); + } } /** -* Add inline attachment at position -*/ + * Add inline attachment at position + */ function attachInline(index, filename) { - insert_text('[attachment=' + index + ']' + filename + '[/attachment]'); - document.forms[form_name].elements[text_name].focus(); + insert_text("[attachment=" + index + "]" + filename + "[/attachment]"); + document.forms[form_name].elements[text_name].focus(); } /** -* Add quote text to message -*/ + * Add quote text to message + */ function addquote(post_id, username, l_wrote, attributes) { - var message_name = 'message_' + post_id; - var theSelection = ''; - var divarea = false; - var i; - - if (l_wrote === undefined) { - // Backwards compatibility - l_wrote = 'wrote'; - } - if (typeof attributes !== 'object') { - attributes = {}; - } - - if (document.all) { - divarea = document.all[message_name]; - } else { - divarea = document.getElementById(message_name); - } - - // Get text selection - not only the post content :( - // IE9 must use the document.selection method but has the *.getSelection so we just force no IE - if (window.getSelection && !is_ie && !window.opera) { - theSelection = window.getSelection().toString(); - } else if (document.getSelection && !is_ie) { - theSelection = document.getSelection(); - } else if (document.selection) { - theSelection = document.selection.createRange().text; - } - - if (theSelection === '' || typeof theSelection === 'undefined' || theSelection === null) { - if (divarea.innerHTML) { - theSelection = divarea.innerHTML.replace(/
/ig, '\n'); - theSelection = theSelection.replace(//ig, '\n'); - theSelection = theSelection.replace(/<\;/ig, '<'); - theSelection = theSelection.replace(/>\;/ig, '>'); - theSelection = theSelection.replace(/&\;/ig, '&'); - theSelection = theSelection.replace(/ \;/ig, ' '); - } else if (document.all) { - theSelection = divarea.innerText; - } else if (divarea.textContent) { - theSelection = divarea.textContent; - } else if (divarea.firstChild.nodeValue) { - theSelection = divarea.firstChild.nodeValue; - } - } - - if (theSelection) { - if (bbcodeEnabled) { - attributes.author = username; - insert_text(generateQuote(theSelection, attributes)); - } else { - insert_text(username + ' ' + l_wrote + ':' + '\n'); - var lines = split_lines(theSelection); - for (i = 0; i < lines.length; i++) { - insert_text('> ' + lines[i] + '\n'); - } - } - } + var message_name = "message_" + post_id; + var theSelection = ""; + var divarea = false; + var i; + + if (l_wrote === undefined) { + // Backwards compatibility + l_wrote = "wrote"; + } + if (typeof attributes !== "object") { + attributes = {}; + } + + if (document.all) { + divarea = document.all[message_name]; + } else { + divarea = document.getElementById(message_name); + } + + // Get text selection - not only the post content :( + // IE9 must use the document.selection method but has the *.getSelection so we just force no IE + if (window.getSelection && !is_ie && !window.opera) { + theSelection = window.getSelection().toString(); + } else if (document.getSelection && !is_ie) { + theSelection = document.getSelection(); + } else if (document.selection) { + theSelection = document.selection.createRange().text; + } + + if ( + theSelection === "" || + typeof theSelection === "undefined" || + theSelection === null + ) { + if (divarea.innerHTML) { + theSelection = divarea.innerHTML.replace(/
/gi, "\n"); + theSelection = theSelection.replace(//gi, "\n"); + theSelection = theSelection.replace(/<\;/gi, "<"); + theSelection = theSelection.replace(/>\;/gi, ">"); + theSelection = theSelection.replace(/&\;/gi, "&"); + theSelection = theSelection.replace(/ \;/gi, " "); + } else if (document.all) { + theSelection = divarea.innerText; + } else if (divarea.textContent) { + theSelection = divarea.textContent; + } else if (divarea.firstChild.nodeValue) { + theSelection = divarea.firstChild.nodeValue; + } + } + + if (theSelection) { + if (bbcodeEnabled) { + attributes.author = username; + insert_text(generateQuote(theSelection, attributes)); + } else { + insert_text(username + " " + l_wrote + ":" + "\n"); + var lines = split_lines(theSelection); + for (i = 0; i < lines.length; i++) { + insert_text("> " + lines[i] + "\n"); + } + } + } } /** -* Create a quote block for given text -* -* Possible attributes: -* - author: author's name (usually a username) -* - post_id: post_id of the post being quoted -* - user_id: user_id of the user being quoted -* - time: timestamp of the original message -* -* @param {!string} text Quote's text -* @param {!Object} attributes Quote's attributes -* @return {!string} Quote block to be used in a new post/text -*/ + * Create a quote block for given text + * + * Possible attributes: + * - author: author's name (usually a username) + * - post_id: post_id of the post being quoted + * - user_id: user_id of the user being quoted + * - time: timestamp of the original message + * + * @param {!string} text Quote's text + * @param {!Object} attributes Quote's attributes + * @return {!string} Quote block to be used in a new post/text + */ function generateQuote(text, attributes) { - text = text.replace(/^\s+/, '').replace(/\s+$/, ''); - var quote = '[quote'; - if (attributes.author) { - // Add the author as the BBCode's default attribute - quote += '=' + formatAttributeValue(attributes.author); - delete attributes.author; - } - for (var name in attributes) { - if (attributes.hasOwnProperty(name)) { - var value = attributes[name]; - quote += ' ' + name + '=' + formatAttributeValue(value.toString()); - } - } - quote += ']'; - var newline = ((quote + text + '[/quote]').length > 80 || text.indexOf('\n') > -1) ? '\n' : ''; - quote += newline + text + newline + '[/quote]'; - - return quote; + text = text.replace(/^\s+/, "").replace(/\s+$/, ""); + var quote = "[quote"; + if (attributes.author) { + // Add the author as the BBCode's default attribute + quote += "=" + formatAttributeValue(attributes.author); + delete attributes.author; + } + for (var name in attributes) { + if (attributes.hasOwnProperty(name)) { + var value = attributes[name]; + quote += " " + name + "=" + formatAttributeValue(value.toString()); + } + } + quote += "]"; + var newline = + (quote + text + "[/quote]").length > 80 || text.indexOf("\n") > -1 + ? "\n" + : ""; + quote += newline + text + newline + "[/quote]"; + + return quote; } /** -* Format given string to be used as an attribute value -* -* Will return the string as-is if it can be used in a BBCode without quotes. Otherwise, -* it will use either single- or double- quotes depending on whichever requires less escaping. -* Quotes and backslashes are escaped with backslashes where necessary -* -* @param {!string} str Original string -* @return {!string} Same string if possible, escaped string within quotes otherwise -*/ + * Format given string to be used as an attribute value + * + * Will return the string as-is if it can be used in a BBCode without quotes. Otherwise, + * it will use either single- or double- quotes depending on whichever requires less escaping. + * Quotes and backslashes are escaped with backslashes where necessary + * + * @param {!string} str Original string + * @return {!string} Same string if possible, escaped string within quotes otherwise + */ function formatAttributeValue(str) { - if (!/[ "'\\\]]/.test(str)) { - // Return as-is if it contains none of: space, ' " \ or ] - return str; - } - var singleQuoted = "'" + str.replace(/[\\']/g, '\\$&') + "'", - doubleQuoted = '"' + str.replace(/[\\"]/g, '\\$&') + '"'; - - return (singleQuoted.length < doubleQuoted.length) ? singleQuoted : doubleQuoted; + if (!/[ "'\\\]]/.test(str)) { + // Return as-is if it contains none of: space, ' " \ or ] + return str; + } + var singleQuoted = "'" + str.replace(/[\\']/g, "\\$&") + "'", + doubleQuoted = '"' + str.replace(/[\\"]/g, "\\$&") + '"'; + + return singleQuoted.length < doubleQuoted.length + ? singleQuoted + : doubleQuoted; } function split_lines(text) { - var lines = text.split('\n'); - var splitLines = new Array(); - var j = 0; - var i; - - for(i = 0; i < lines.length; i++) { - if (lines[i].length <= 80) { - splitLines[j] = lines[i]; - j++; - } else { - var line = lines[i]; - var splitAt; - do { - splitAt = line.indexOf(' ', 80); - - if (splitAt === -1) { - splitLines[j] = line; - j++; - } else { - splitLines[j] = line.substring(0, splitAt); - line = line.substring(splitAt); - j++; - } - } - while(splitAt !== -1); - } - } - return splitLines; + var lines = text.split("\n"); + var splitLines = new Array(); + var j = 0; + var i; + + for (i = 0; i < lines.length; i++) { + if (lines[i].length <= 80) { + splitLines[j] = lines[i]; + j++; + } else { + var line = lines[i]; + var splitAt; + do { + splitAt = line.indexOf(" ", 80); + + if (splitAt === -1) { + splitLines[j] = line; + j++; + } else { + splitLines[j] = line.substring(0, splitAt); + line = line.substring(splitAt); + j++; + } + } while (splitAt !== -1); + } + } + return splitLines; } /** -* From http://www.massless.org/mozedit/ -*/ + * From http://www.massless.org/mozedit/ + */ function mozWrap(txtarea, open, close) { - var selLength = (typeof(txtarea.textLength) === 'undefined') ? txtarea.value.length : txtarea.textLength; - var selStart = txtarea.selectionStart; - var selEnd = txtarea.selectionEnd; - var scrollTop = txtarea.scrollTop; - - var s1 = (txtarea.value).substring(0,selStart); - var s2 = (txtarea.value).substring(selStart, selEnd); - var s3 = (txtarea.value).substring(selEnd, selLength); - - txtarea.value = s1 + open + s2 + close + s3; - txtarea.selectionStart = selStart + open.length; - txtarea.selectionEnd = selEnd + open.length; - txtarea.focus(); - txtarea.scrollTop = scrollTop; - - return; + var selLength = + typeof txtarea.textLength === "undefined" + ? txtarea.value.length + : txtarea.textLength; + var selStart = txtarea.selectionStart; + var selEnd = txtarea.selectionEnd; + var scrollTop = txtarea.scrollTop; + + var s1 = txtarea.value.substring(0, selStart); + var s2 = txtarea.value.substring(selStart, selEnd); + var s3 = txtarea.value.substring(selEnd, selLength); + + txtarea.value = s1 + open + s2 + close + s3; + txtarea.selectionStart = selStart + open.length; + txtarea.selectionEnd = selEnd + open.length; + txtarea.focus(); + txtarea.scrollTop = scrollTop; + + return; } /** -* Insert at Caret position. Code from -* http://www.faqts.com/knowledge_base/view.phtml/aid/1052/fid/130 -*/ + * Insert at Caret position. Code from + * http://www.faqts.com/knowledge_base/view.phtml/aid/1052/fid/130 + */ function storeCaret(textEl) { - if (textEl.createTextRange && document.selection) { - textEl.caretPos = document.selection.createRange().duplicate(); - } + if (textEl.createTextRange && document.selection) { + textEl.caretPos = document.selection.createRange().duplicate(); + } } /** -* Caret Position object -*/ + * Caret Position object + */ function caretPosition() { - var start = null; - var end = null; + var start = null; + var end = null; } /** -* Get the caret position in an textarea -*/ + * Get the caret position in an textarea + */ function getCaretPosition(txtarea) { - var caretPos = new caretPosition(); - - // simple Gecko/Opera way - if (txtarea.selectionStart || txtarea.selectionStart === 0) { - caretPos.start = txtarea.selectionStart; - caretPos.end = txtarea.selectionEnd; - } - // dirty and slow IE way - else if (document.selection) { - // get current selection - var range = document.selection.createRange(); - - // a new selection of the whole textarea - var range_all = document.body.createTextRange(); - range_all.moveToElementText(txtarea); - - // calculate selection start point by moving beginning of range_all to beginning of range - var sel_start; - for (sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start++) { - range_all.moveStart('character', 1); - } - - txtarea.sel_start = sel_start; - - // we ignore the end value for IE, this is already dirty enough and we don't need it - caretPos.start = txtarea.sel_start; - caretPos.end = txtarea.sel_start; - } - - return caretPos; + var caretPos = new caretPosition(); + + // simple Gecko/Opera way + if (txtarea.selectionStart || txtarea.selectionStart === 0) { + caretPos.start = txtarea.selectionStart; + caretPos.end = txtarea.selectionEnd; + } + // dirty and slow IE way + else if (document.selection) { + // get current selection + var range = document.selection.createRange(); + + // a new selection of the whole textarea + var range_all = document.body.createTextRange(); + range_all.moveToElementText(txtarea); + + // calculate selection start point by moving beginning of range_all to beginning of range + var sel_start; + for ( + sel_start = 0; + range_all.compareEndPoints("StartToStart", range) < 0; + sel_start++ + ) { + range_all.moveStart("character", 1); + } + + txtarea.sel_start = sel_start; + + // we ignore the end value for IE, this is already dirty enough and we don't need it + caretPos.start = txtarea.sel_start; + caretPos.end = txtarea.sel_start; + } + + return caretPos; } /** -* Allow to use tab character when typing code -* Keep indentation of last line of code when typing code -*/ -(function($) { - $(document).ready(function() { - var doc, textarea; - - // find textarea, make sure browser supports necessary functions - if (document.forms[form_name]) { - doc = document; - } else { - doc = opener.document; - } - - if (!doc.forms[form_name]) { - return; - } - - textarea = doc.forms[form_name].elements[text_name]; - - phpbb.applyCodeEditor(textarea); - if ($('#attach-panel').length) { - phpbb.showDragNDrop(textarea); - } - - $('textarea').on('keydown', function (e) { - if (e.which === 13 && (e.metaKey || e.ctrlKey)) { - $(this).closest('form').find(':submit').click(); - } - }); - }); + * Allow to use tab character when typing code + * Keep indentation of last line of code when typing code + */ +(function ($) { + $(document).ready(function () { + var doc, textarea; + + // find textarea, make sure browser supports necessary functions + if (document.forms[form_name]) { + doc = document; + } else { + doc = opener.document; + } + + if (!doc.forms[form_name]) { + return; + } + + textarea = doc.forms[form_name].elements[text_name]; + + phpbb.applyCodeEditor(textarea); + if ($("#attach-panel").length) { + phpbb.showDragNDrop(textarea); + } + + $("textarea").on("keydown", function (e) { + if (e.which === 13 && (e.metaKey || e.ctrlKey)) { + $(this).closest("form").find(":submit").click(); + } + }); + }); })(jQuery); - diff --git a/assets/javascript/forum_fn.js b/assets/javascript/forum_fn.js index 60a2fd8..f1aa89b 100644 --- a/assets/javascript/forum_fn.js +++ b/assets/javascript/forum_fn.js @@ -1,937 +1,1038 @@ /* global phpbb */ /** -* phpBB3 forum functions -*/ + * phpBB3 forum functions + */ /** -* Find a member -*/ + * Find a member + */ function find_username(url) { - 'use strict'; + "use strict"; - popup(url, 760, 570, '_usersearch'); - return false; + popup(url, 760, 570, "_usersearch"); + return false; } /** -* Window popup -*/ + * Window popup + */ function popup(url, width, height, name) { - 'use strict'; - - if (!name) { - name = '_popup'; - } - - window.open(url.replace(/&/g, '&'), name, 'height=' + height + ',resizable=yes,scrollbars=yes, width=' + width); - return false; + "use strict"; + + if (!name) { + name = "_popup"; + } + + window.open( + url.replace(/&/g, "&"), + name, + "height=" + height + ",resizable=yes,scrollbars=yes, width=" + width + ); + return false; } /** -* Jump to page -*/ + * Jump to page + */ function pageJump(item) { - 'use strict'; - - var page = parseInt(item.val(), 10), - perPage = item.attr('data-per-page'), - baseUrl = item.attr('data-base-url'), - startName = item.attr('data-start-name'); - - if (page !== null && !isNaN(page) && page === Math.floor(page) && page > 0) { - if (baseUrl.indexOf('?') === -1) { - document.location.href = baseUrl + '?' + startName + '=' + ((page - 1) * perPage); - } else { - document.location.href = baseUrl.replace(/&/g, '&') + '&' + startName + '=' + ((page - 1) * perPage); - } - } + "use strict"; + + var page = parseInt(item.val(), 10), + perPage = item.attr("data-per-page"), + baseUrl = item.attr("data-base-url"), + startName = item.attr("data-start-name"); + + if (page !== null && !isNaN(page) && page === Math.floor(page) && page > 0) { + if (baseUrl.indexOf("?") === -1) { + document.location.href = + baseUrl + "?" + startName + "=" + (page - 1) * perPage; + } else { + document.location.href = + baseUrl.replace(/&/g, "&") + + "&" + + startName + + "=" + + (page - 1) * perPage; + } + } } /** -* Mark/unmark checklist -* id = ID of parent container, name = name prefix, state = state [true/false] -*/ + * Mark/unmark checklist + * id = ID of parent container, name = name prefix, state = state [true/false] + */ function marklist(id, name, state) { - 'use strict'; - - jQuery('#' + id + ' input[type=checkbox][name]').each(function() { - var $this = jQuery(this); - if ($this.attr('name').substr(0, name.length) === name && !$this.prop('disabled')) { - $this.prop('checked', state); - } - }); + "use strict"; + + jQuery("#" + id + " input[type=checkbox][name]").each(function () { + var $this = jQuery(this); + if ( + $this.attr("name").substr(0, name.length) === name && + !$this.prop("disabled") + ) { + $this.prop("checked", state); + } + }); } /** -* Resize viewable area for attached image or topic review panel (possibly others to come) -* e = element -*/ + * Resize viewable area for attached image or topic review panel (possibly others to come) + * e = element + */ function viewableArea(e, itself) { - 'use strict'; - - if (!e) { - return; - } - - if (!itself) { - e = e.parentNode; - } - - if (!e.vaHeight) { - // Store viewable area height before changing style to auto - e.vaHeight = e.offsetHeight; - e.vaMaxHeight = e.style.maxHeight; - e.style.height = 'auto'; - e.style.maxHeight = 'none'; - e.style.overflow = 'visible'; - } else { - // Restore viewable area height to the default - e.style.height = e.vaHeight + 'px'; - e.style.overflow = 'auto'; - e.style.maxHeight = e.vaMaxHeight; - e.vaHeight = false; - } + "use strict"; + + if (!e) { + return; + } + + if (!itself) { + e = e.parentNode; + } + + if (!e.vaHeight) { + // Store viewable area height before changing style to auto + e.vaHeight = e.offsetHeight; + e.vaMaxHeight = e.style.maxHeight; + e.style.height = "auto"; + e.style.maxHeight = "none"; + e.style.overflow = "visible"; + } else { + // Restore viewable area height to the default + e.style.height = e.vaHeight + "px"; + e.style.overflow = "auto"; + e.style.maxHeight = e.vaMaxHeight; + e.vaHeight = false; + } } /** -* Alternate display of subPanels -*/ -jQuery(function($) { - 'use strict'; - - $('.sub-panels').each(function() { - - var $childNodes = $('a[data-subpanel]', this), - panels = $childNodes.map(function () { - return this.getAttribute('data-subpanel'); - }), - showPanel = this.getAttribute('data-show-panel'); - - if (panels.length) { - activateSubPanel(showPanel, panels); - $childNodes.click(function () { - activateSubPanel(this.getAttribute('data-subpanel'), panels); - return false; - }); - } - }); + * Alternate display of subPanels + */ +jQuery(function ($) { + "use strict"; + + $(".sub-panels").each(function () { + var $childNodes = $("a[data-subpanel]", this), + panels = $childNodes.map(function () { + return this.getAttribute("data-subpanel"); + }), + showPanel = this.getAttribute("data-show-panel"); + + if (panels.length) { + activateSubPanel(showPanel, panels); + $childNodes.click(function () { + activateSubPanel(this.getAttribute("data-subpanel"), panels); + return false; + }); + } + }); }); /** -* Activate specific subPanel -*/ + * Activate specific subPanel + */ function activateSubPanel(p, panels) { - 'use strict'; - - var i, showPanel; - - if (typeof p === 'string') { - showPanel = p; - } - $('input[name="show_panel"]').val(showPanel); - - if (typeof panels === 'undefined') { - panels = jQuery('.sub-panels a[data-subpanel]').map(function() { - return this.getAttribute('data-subpanel'); - }); - } - - for (i = 0; i < panels.length; i++) { - jQuery('#' + panels[i]).css('display', panels[i] === showPanel ? 'block' : 'none'); - jQuery('#' + panels[i] + '-tab').toggleClass('activetab', panels[i] === showPanel); - } + "use strict"; + + var i, showPanel; + + if (typeof p === "string") { + showPanel = p; + } + $('input[name="show_panel"]').val(showPanel); + + if (typeof panels === "undefined") { + panels = jQuery(".sub-panels a[data-subpanel]").map(function () { + return this.getAttribute("data-subpanel"); + }); + } + + for (i = 0; i < panels.length; i++) { + jQuery("#" + panels[i]).css( + "display", + panels[i] === showPanel ? "block" : "none" + ); + jQuery("#" + panels[i] + "-tab").toggleClass( + "activetab", + panels[i] === showPanel + ); + } } function selectCode(a) { - 'use strict'; - - // Get ID of code block - var e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; - var s, r; - - // Not IE and IE9+ - if (window.getSelection) { - s = window.getSelection(); - // Safari and Chrome - if (s.setBaseAndExtent) { - var l = (e.innerText.length > 1) ? e.innerText.length - 1 : 1; - try { - s.setBaseAndExtent(e, 0, e, l); - } catch (error) { - r = document.createRange(); - r.selectNodeContents(e); - s.removeAllRanges(); - s.addRange(r); - } - } - // Firefox and Opera - else { - // workaround for bug # 42885 - if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) === '
') { - e.innerHTML = e.innerHTML + ' '; - } - - r = document.createRange(); - r.selectNodeContents(e); - s.removeAllRanges(); - s.addRange(r); - } - } - // Some older browsers - else if (document.getSelection) { - s = document.getSelection(); - r = document.createRange(); - r.selectNodeContents(e); - s.removeAllRanges(); - s.addRange(r); - } - // IE - else if (document.selection) { - r = document.body.createTextRange(); - r.moveToElementText(e); - r.select(); - } + "use strict"; + + // Get ID of code block + var e = a.parentNode.parentNode.getElementsByTagName("CODE")[0]; + var s, r; + + // Not IE and IE9+ + if (window.getSelection) { + s = window.getSelection(); + // Safari and Chrome + if (s.setBaseAndExtent) { + var l = e.innerText.length > 1 ? e.innerText.length - 1 : 1; + try { + s.setBaseAndExtent(e, 0, e, l); + } catch (error) { + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + } + // Firefox and Opera + else { + // workaround for bug # 42885 + if ( + window.opera && + e.innerHTML.substring(e.innerHTML.length - 4) === "
" + ) { + e.innerHTML = e.innerHTML + " "; + } + + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + } + // Some older browsers + else if (document.getSelection) { + s = document.getSelection(); + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + // IE + else if (document.selection) { + r = document.body.createTextRange(); + r.moveToElementText(e); + r.select(); + } } var inAutocomplete = false; -var lastKeyEntered = ''; +var lastKeyEntered = ""; /** -* Check event key -*/ + * Check event key + */ function phpbbCheckKey(event) { - 'use strict'; - - // Keycode is array down or up? - if (event.keyCode && (event.keyCode === 40 || event.keyCode === 38)) { - inAutocomplete = true; - } - - // Make sure we are not within an "autocompletion" field - if (inAutocomplete) { - // If return pressed and key changed we reset the autocompletion - if (!lastKeyEntered || lastKeyEntered === event.which) { - inAutocomplete = false; - return true; - } - } - - // Keycode is not return, then return. ;) - if (event.which !== 13) { - lastKeyEntered = event.which; - return true; - } - - return false; + "use strict"; + + // Keycode is array down or up? + if (event.keyCode && (event.keyCode === 40 || event.keyCode === 38)) { + inAutocomplete = true; + } + + // Make sure we are not within an "autocompletion" field + if (inAutocomplete) { + // If return pressed and key changed we reset the autocompletion + if (!lastKeyEntered || lastKeyEntered === event.which) { + inAutocomplete = false; + return true; + } + } + + // Keycode is not return, then return. ;) + if (event.which !== 13) { + lastKeyEntered = event.which; + return true; + } + + return false; } /** -* Apply onkeypress event for forcing default submit button on ENTER key press -*/ -jQuery(function($) { - 'use strict'; - - $('form input[type=text], form input[type=password]').on('keypress', function (e) { - var defaultButton = $(this).parents('form').find('input[type=submit].default-submit-action'); - - if (!defaultButton || defaultButton.length <= 0) { - return true; - } - - if (phpbbCheckKey(e)) { - return true; - } - - if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) { - defaultButton.click(); - return false; - } - - return true; - }); + * Apply onkeypress event for forcing default submit button on ENTER key press + */ +jQuery(function ($) { + "use strict"; + + $("form input[type=text], form input[type=password]").on( + "keypress", + function (e) { + var defaultButton = $(this) + .parents("form") + .find("input[type=submit].default-submit-action"); + + if (!defaultButton || defaultButton.length <= 0) { + return true; + } + + if (phpbbCheckKey(e)) { + return true; + } + + if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) { + defaultButton.click(); + return false; + } + + return true; + } + ); }); /** -* Functions for user search popup -*/ + * Functions for user search popup + */ function insertUser(formId, value) { - 'use strict'; + "use strict"; - var $form = jQuery(formId), - formName = $form.attr('data-form-name'), - fieldName = $form.attr('data-field-name'), - item = opener.document.forms[formName][fieldName]; + var $form = jQuery(formId), + formName = $form.attr("data-form-name"), + fieldName = $form.attr("data-field-name"), + item = opener.document.forms[formName][fieldName]; - if (item.value.length && item.type === 'textarea') { - value = item.value + '\n' + value; - } + if (item.value.length && item.type === "textarea") { + value = item.value + "\n" + value; + } - item.value = value; + item.value = value; } function insert_marked_users(formId, users) { - 'use strict'; + "use strict"; - $(users).filter(':checked').each(function() { - insertUser(formId, this.value); - }); + $(users) + .filter(":checked") + .each(function () { + insertUser(formId, this.value); + }); - window.close(); + window.close(); } function insert_single_user(formId, user) { - 'use strict'; + "use strict"; - insertUser(formId, user); - window.close(); + insertUser(formId, user); + window.close(); } /** -* Parse document block -*/ + * Parse document block + */ function parseDocument($container) { - 'use strict'; - - var test = document.createElement('div'), - oldBrowser = (typeof test.style.borderRadius === 'undefined'), - $body = $('body'); - - /** - * Reset avatar dimensions when changing URL or EMAIL - */ - $container.find('input[data-reset-on-edit]').on('keyup', function() { - $(this.getAttribute('data-reset-on-edit')).val(''); - }); - - /** - * Pagination - */ - $container.find('.pagination .page-jump-form :button').click(function() { - var $input = $(this).siblings('input.inputbox'); - pageJump($input); - }); - - $container.find('.pagination .page-jump-form input.inputbox').on('keypress', function(event) { - if (event.which === 13 || event.keyCode === 13) { - event.preventDefault(); - pageJump($(this)); - } - }); - - $container.find('.pagination .dropdown-trigger').click(function() { - var $dropdownContainer = $(this).parent(); - // Wait a little bit to make sure the dropdown has activated - setTimeout(function() { - if ($dropdownContainer.hasClass('dropdown-visible')) { - $dropdownContainer.find('input.inputbox').focus(); - } - }, 100); - }); - - /** - * Adjust HTML code for IE8 and older versions - */ - // if (oldBrowser) { - // // Fix .linklist.bulletin lists - // $container - // .find('ul.linklist.bulletin > li') - // .filter(':first-child, .rightside:last-child') - // .addClass('no-bulletin'); - // } - - /** - * Resize navigation (breadcrumbs) block to keep all links on same line - */ - $container.find('.navlinks').each(function() { - var $this = $(this), - $left = $this.children().not('.rightside'), - $right = $this.children('.rightside'); - - if ($left.length !== 1 || !$right.length) { - return; - } - - function resize() { - var width = 0, - diff = $left.outerWidth(true) - $left.width(), - minWidth = Math.max($this.width() / 3, 240), - maxWidth; - - $right.each(function() { - var $this = $(this); - if ($this.is(':visible')) { - width += $this.outerWidth(true); - } - }); - - maxWidth = $this.width() - width - diff; - $left.css('max-width', Math.floor(Math.max(maxWidth, minWidth)) + 'px'); - } - - resize(); - $(window).resize(resize); - }); - - /** - * Makes breadcrumbs responsive - */ - $container.find('.breadcrumbs:not([data-skip-responsive])').each(function() { - var $this = $(this), - $links = $this.find('.crumb'), - length = $links.length, - classes = ['wrapped-max', 'wrapped-wide', 'wrapped-medium', 'wrapped-small', 'wrapped-tiny'], - classesLength = classes.length, - maxHeight = 0, - lastWidth = false, - wrapped = false; - - // Set tooltips - $this.find('a').each(function() { - var $link = $(this); - $link.attr('title', $link.text()); - }); - - // Function that checks breadcrumbs - function check() { - var height = $this.height(), - width; - - // Test max-width set in code for .navlinks above - width = parseInt($this.css('max-width'), 10); - if (!width) { - width = $body.width(); - } - - maxHeight = parseInt($this.css('line-height'), 10); - $links.each(function() { - if ($(this).height() > 0) { - maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); - } - }); - - if (height <= maxHeight) { - if (!wrapped || lastWidth === false || lastWidth >= width) { - return; - } - } - lastWidth = width; - - if (wrapped) { - $this.removeClass('wrapped').find('.crumb.wrapped').removeClass('wrapped ' + classes.join(' ')); - if ($this.height() <= maxHeight) { - return; - } - } - - wrapped = true; - $this.addClass('wrapped'); - if ($this.height() <= maxHeight) { - return; - } - - for (var i = 0; i < classesLength; i++) { - for (var j = length - 1; j >= 0; j--) { - $links.eq(j).addClass('wrapped ' + classes[i]); - if ($this.height() <= maxHeight) { - return; - } - } - } - } - - // Run function and set event - check(); - $(window).resize(check); - }); - - /** - * Responsive link lists - */ - var selector = '.linklist:not(.navlinks, [data-skip-responsive]),' + - '.postbody .post-buttons:not([data-skip-responsive])'; - $container.find(selector).each(function() { - var $this = $(this), - filterSkip = '.breadcrumbs, [data-skip-responsive]', - filterLast = '.edit-icon, .quote-icon, [data-last-responsive]', - $linksAll = $this.children(), - $linksNotSkip = $linksAll.not(filterSkip), // All items that can potentially be hidden - $linksFirst = $linksNotSkip.not(filterLast), // The items that will be hidden first - $linksLast = $linksNotSkip.filter(filterLast), // The items that will be hidden last - persistent = $this.attr('id') === 'nav-main', // Does this list already have a menu (such as quick-links)? - html = '', - slack = 3; // Vertical slack space (in pixels). Determines how sensitive the script is in determining whether a line-break has occurred. - - // Add a hidden drop-down menu to each links list (except those that already have one) - if (!persistent) { - if ($linksNotSkip.is('.rightside')) { - $linksNotSkip.filter('.rightside:first').before(html); - $this.children('.responsive-menu').addClass('rightside'); - } else { - $this.append(html); - } - } - - // Set some object references and initial states - var $menu = $this.children('.responsive-menu'), - $menuContents = $menu.find('.dropdown-contents'), - persistentContent = $menuContents.find('li:not(.separator)').length, - lastWidth = false, - compact = false, - responsive1 = false, - responsive2 = false, - copied1 = false, - copied2 = false, - maxHeight = 0; - - // Find the tallest element in the list (we assume that all elements are roughly the same height) - $linksAll.each(function() { - if (!$(this).height()) { - return; - } - maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); - }); - if (maxHeight < 1) { - return; // Shouldn't be possible, but just in case, abort - } else { - maxHeight = maxHeight + slack; - } - - function check() { - var width = $body.width(); - // We can't make it any smaller than this, so just skip - if (responsive2 && compact && (width <= lastWidth)) { - return; - } - lastWidth = width; - - // Reset responsive and compact layout - if (responsive1 || responsive2) { - $linksNotSkip.removeClass('hidden'); - $menuContents.children('.clone').addClass('hidden'); - responsive1 = responsive2 = false; - } - if (compact) { - $this.removeClass('compact'); - compact = false; - } - - // Unhide the quick-links menu if it has "persistent" content - if (persistent && persistentContent) { - $menu.removeClass('hidden'); - } else { - $menu.addClass('hidden'); - } - - // Nothing to resize if block's height is not bigger than tallest element's height - if ($this.height() <= maxHeight) { - return; - } - - // STEP 1: Compact - if (!compact) { - $this.addClass('compact'); - compact = true; - } - if ($this.height() <= maxHeight) { - return; - } - - // STEP 2: First responsive set - compact - if (compact) { - $this.removeClass('compact'); - compact = false; - } - // Copy the list items to the dropdown - if (!copied1) { - var $clones1 = $linksFirst.clone(true); - $menuContents.prepend($clones1.addClass('clone clone-first').removeClass('leftside rightside')); - - if ($this.hasClass('post-buttons')) { - $('.button', $menuContents).removeClass('button'); - $('.sr-only', $menuContents).removeClass('sr-only'); - $('.js-responsive-menu-link').addClass('button').addClass('button-icon-only'); - $('.js-responsive-menu-link .icon').removeClass('fa-bars').addClass('fa-ellipsis-h'); - } - copied1 = true; - } - if (!responsive1) { - $linksFirst.addClass('hidden'); - responsive1 = true; - $menuContents.children('.clone-first').removeClass('hidden'); - $menu.removeClass('hidden'); - } - if ($this.height() <= maxHeight) { - return; - } - - // STEP 3: First responsive set + compact - if (!compact) { - $this.addClass('compact'); - compact = true; - } - if ($this.height() <= maxHeight) { - return; - } - - // STEP 4: Last responsive set - compact - if (!$linksLast.length) { - return; // No other links to hide, can't do more - } - if (compact) { - $this.removeClass('compact'); - compact = false; - } - // Copy the list items to the dropdown - if (!copied2) { - var $clones2 = $linksLast.clone(); - $menuContents.prepend($clones2.addClass('clone clone-last').removeClass('leftside rightside')); - copied2 = true; - } - if (!responsive2) { - $linksLast.addClass('hidden'); - responsive2 = true; - $menuContents.children('.clone-last').removeClass('hidden'); - } - if ($this.height() <= maxHeight) { - return; - } - - // STEP 5: Last responsive set + compact - if (!compact) { - $this.addClass('compact'); - compact = true; - } - } - - if (!persistent) { - phpbb.registerDropdown($menu.find('a.js-responsive-menu-link'), $menu.find('.dropdown'), false); - } - - // If there are any images in the links list, run the check again after they have loaded - $linksAll.find('img').each(function() { - $(this).on('load', function() { - check(); - }); - }); - - check(); - $(window).resize(check); - }); - - /** - * Do not run functions below for old browsers - */ - if (oldBrowser) { - return; - } - - /** - * Adjust topiclist lists with check boxes - */ - $container.find('ul.topiclist dd.mark').siblings('dt').children('.list-inner').addClass('with-mark'); - - /** - * Appends contents of all extra columns to first column in - * .topiclist lists for mobile devices. Copies contents as is. - * - * To add that functionality to .topiclist list simply add - * responsive-show-all to list of classes - */ - $container.find('.topiclist.responsive-show-all > li > dl').each(function() { - var $this = $(this), - $block = $this.find('dt .responsive-show:last-child'), - first = true; - - // Create block that is visible only on mobile devices - if (!$block.length) { - $this.find('dt > .list-inner').append('