diff --git a/.editorconfig b/.editorconfig index 6219a927b2698e3718655e625a8c2c7e522c7462..23d04f8dd4e5b27c092351e7b524fe2d54a149f5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.{js,coffee,html}] +[*.{js,coffee,html,less,json}] indent_style = tab [*.md] diff --git a/.eslintignore b/.eslintignore index d4b84b0e78ca44bd8a088ba89bb1c670749352a6..000185fe150693094f8b62fd8dca842311d9ffb3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,6 +14,7 @@ packages/rocketchat-ui/lib/Modernizr.js packages/rocketchat-ui/lib/recorderjs/recorder.js packages/rocketchat-ui/lib/textarea-autogrow.js packages/rocketchat-videobridge/client/public/external_api.js +packages/rocketchat-theme/client/vendor/ private/moment-locales/ public/livechat/ public/recorderWorker.js diff --git a/.gitignore b/.gitignore index f9ab2485e51781866c54d71cc68466c97c759bd0..ce142f9c01dd8ff109b970dd3277de4900791695 100644 --- a/.gitignore +++ b/.gitignore @@ -39,12 +39,15 @@ .metadata .meteor/local* .meteor/meteorite +.meteor/dev_bundle +packages/rocketchat-livechat/app/.meteor/dev_bundle .mule .pmd .project .sass-cache .settings .Spotlight-V100 +tatus .Trashes .wtpmodules \#*\# diff --git a/.meteor/packages b/.meteor/packages index 00c4512aeab89348bdf5e09d57d33e443a17056f..d0fabc22b516026883557bcad465a00c2d140512 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -64,6 +64,7 @@ rocketchat:iframe-login rocketchat:importer rocketchat:importer-csv rocketchat:importer-hipchat +rocketchat:importer-hipchat-enterprise rocketchat:importer-slack rocketchat:integrations rocketchat:internal-hubot @@ -149,7 +150,6 @@ nimble:restivus nooitaf:colors ostrio:cookies pauli:accounts-linkedin -perak:codemirror percolate:synced-cron raix:handlebar-helpers raix:push diff --git a/.meteor/versions b/.meteor/versions index cca9fdb3b4389b0d7756156b187515863722ab3d..f27bdcd655ba6b701e9cab52f476925892192f8b 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -13,7 +13,7 @@ babel-compiler@6.13.0 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 -blaze@2.2.0 +blaze@2.2.1 blaze-html-templates@1.0.5 blaze-tools@1.0.10 boilerplate-generator@1.0.11 @@ -62,7 +62,7 @@ kadira:flow-router@2.12.1 kenton:accounts-sandstorm@0.5.1 konecty:change-case@2.3.0 konecty:delayed-task@1.0.0 -konecty:mongo-counter@0.0.5_2 +konecty:mongo-counter@0.0.5_3 konecty:multiple-instances-status@1.0.6_1 konecty:nrr@2.0.2 konecty:user-presence@1.2.9 @@ -86,7 +86,7 @@ mizzao:timesync@0.3.4 mobile-experience@1.0.4 mobile-status-bar@1.0.13 modules@0.7.7 -modules-runtime@0.7.7 +modules-runtime@0.7.8 mongo@1.1.14 mongo-id@1.0.6 mongo-livedata@1.0.12 @@ -95,7 +95,7 @@ mystor:device-detection@0.2.0 nimble:restivus@0.8.11 nooitaf:colors@1.1.2_1 npm-bcrypt@0.9.2 -npm-mongo@2.2.11_2 +npm-mongo@2.2.16_1 oauth@1.1.12 oauth1@1.1.11 oauth2@1.1.11 @@ -106,9 +106,7 @@ pauli:accounts-linkedin@1.3.1 pauli:linkedin@1.3.1 peerlibrary:aws-sdk@2.4.9_1 peerlibrary:blocking@0.5.2 -perak:codemirror@1.3.1 percolate:synced-cron@1.3.2 -pntbr:js-yaml-client@0.0.1 promise@0.8.8 raix:eventemitter@0.1.3 raix:eventstate@0.0.4 @@ -149,6 +147,7 @@ rocketchat:iframe-login@1.0.0 rocketchat:importer@0.0.1 rocketchat:importer-csv@1.0.0 rocketchat:importer-hipchat@0.0.1 +rocketchat:importer-hipchat-enterprise@1.0.0 rocketchat:importer-slack@0.0.1 rocketchat:integrations@0.0.1 rocketchat:internal-hubot@0.0.1 @@ -170,7 +169,7 @@ rocketchat:message-pin@0.0.1 rocketchat:message-snippet@0.0.1 rocketchat:message-star@0.0.1 rocketchat:migrations@0.0.1 -rocketchat:oauth2-server@1.4.0 +rocketchat:oauth2-server@2.0.0 rocketchat:oauth2-server-config@1.0.0 rocketchat:oembed@0.0.1 rocketchat:otr@0.0.1 diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index bebb8d08f6e75224cbea6beec5ce9845329cc567..f27a0ea3eeeb5b9be9333d8789b6ed091cfbb72e 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -19,9 +19,9 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Rocket.Chat"), - appVersion = 47, # Increment this for every release. + appVersion = 50, # Increment this for every release. - appMarketingVersion = (defaultText = "0.47.0-develop"), + appMarketingVersion = (defaultText = "0.50.0-develop"), # Human-readable representation of appVersion. Should match the way you # identify versions of your app in documentation and marketing. diff --git a/.scripts/start-xvfb.sh b/.scripts/start-xvfb.sh new file mode 100755 index 0000000000000000000000000000000000000000..70be0b2e6bd76a35f87862a934504c8023372a20 --- /dev/null +++ b/.scripts/start-xvfb.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + sh -e /etc/init.d/xvfb start + sleep 3 +fi diff --git a/.scripts/start.js b/.scripts/start.js new file mode 100644 index 0000000000000000000000000000000000000000..fee3f78b90e85ea29a37abd8ebb57edacea993a9 --- /dev/null +++ b/.scripts/start.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node +var path = require('path'), + fs = require('fs'), + extend = require('util')._extend, + exec = require('child_process').exec, + processes = []; + +var baseDir = path.resolve(__dirname, '..'), + srcDir = path.resolve(baseDir); + +var appOptions = { + env: { + PORT: 3000, + ROOT_URL: 'http://localhost:3000' + } +}; + +function startProcess(opts, callback) { + var proc = exec( + opts.command, + opts.options + ); + + if (opts.waitForMessage) { + proc.stdout.on('data', function waitForMessage(data) { + if (data.toString().match(opts.waitForMessage)) { + if (callback) { + callback(); + } + } + }); + } + + if (!opts.silent) { + proc.stdout.pipe(process.stdout); + proc.stderr.pipe(process.stderr); + } + + if (opts.logFile) { + var logStream = fs.createWriteStream(opts.logFile, {flags: 'a'}); + proc.stdout.pipe(logStream); + proc.stderr.pipe(logStream); + } + + proc.on('close', function(code) { + console.log(opts.name, 'exited with code ' + code); + for (var i = 0; i < processes.length; i += 1) { + processes[i].kill(); + } + process.exit(code); + }); + processes.push(proc); +} + +function startApp(callback) { + startProcess({ + name: 'Meteor App', + command: 'node /tmp/build-test/bundle/main.js', + waitForMessage: appOptions.waitForMessage, + options: { + cwd: srcDir, + env: extend(appOptions.env, process.env) + } + }, callback); +} + +function startChimp() { + startProcess({ + name: 'Chimp', + command: 'meteor npm run chimp-test', + options: { + env: Object.assign({}, process.env, { + NODE_PATH: process.env.NODE_PATH + + path.delimiter + srcDir + + path.delimiter + srcDir + '/node_modules' + }) + } + }); +} + +function chimpNoMirror() { + appOptions.waitForMessage = 'SERVER RUNNING'; + startApp(function() { + startChimp(); + }); +} + +chimpNoMirror(); diff --git a/.snapcraft/edge/snapcraft.yaml b/.snapcraft/edge/snapcraft.yaml index 2b5db0e6cf4f8f0edf4d23f8ee954ad15c852ceb..cacb0f1d21af952f3ab90db3f369221c3339cf8f 100644 --- a/.snapcraft/edge/snapcraft.yaml +++ b/.snapcraft/edge/snapcraft.yaml @@ -7,13 +7,13 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 0.47.0-develop +version: 0.50.0-develop summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict apps: rocketchat-server: - command: env BABEL_CACHE_DIR=/tmp ROOT_URL=http://localhost PORT=3000 MONGO_URL=mongodb://localhost:27017/parties Accounts_AvatarStorePath=$SNAP_COMMON/uploads node $SNAP/main.js >$SNAP_DATA/server.log 2>&1 + command: env BABEL_CACHE_DIR=/tmp ROOT_URL=http://localhost PORT=3000 MONGO_URL=mongodb://localhost:27017/parties Accounts_AvatarStorePath=$SNAP_COMMON/uploads node $SNAP/main.js daemon: simple plugs: [network, network-bind] rocketchat-mongo: @@ -23,7 +23,7 @@ apps: parts: node: plugin: nodejs - node-engine: 4.6.2 + node-engine: 4.7.1 node-packages: - promise - fibers diff --git a/.snapcraft/stable/snapcraft.yaml b/.snapcraft/stable/snapcraft.yaml index dd5d765e04c1b20afad9f5085a77408be41200de..35c3312b8b0414279fa3fcca1ea5dbe759bc3198 100644 --- a/.snapcraft/stable/snapcraft.yaml +++ b/.snapcraft/stable/snapcraft.yaml @@ -7,13 +7,13 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 0.47.0-develop +version: 0.50.0-develop summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict apps: rocketchat-server: - command: env BABEL_CACHE_DIR=/tmp ROOT_URL=http://localhost PORT=3000 MONGO_URL=mongodb://localhost:27017/parties Accounts_AvatarStorePath=$SNAP_COMMON/uploads node $SNAP/main.js >$SNAP_DATA/server.log 2>&1 + command: env BABEL_CACHE_DIR=/tmp ROOT_URL=http://localhost PORT=3000 MONGO_URL=mongodb://localhost:27017/parties Accounts_AvatarStorePath=$SNAP_COMMON/uploads node $SNAP/main.js daemon: simple plugs: [network, network-bind] rocketchat-mongo: @@ -23,7 +23,7 @@ apps: parts: node: plugin: nodejs - node-engine: 4.6.2 + node-engine: 4.7.1 node-packages: - promise - fibers diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000000000000000000000000000000000000..5baac3a35e97342e3d785fa8427808fc61974cde --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,2 @@ +**/lesshat.less +**/_lesshat.import.less \ No newline at end of file diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000000000000000000000000000000000000..376a8b29f50ff72e178e1a534815a6b70236720d --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,115 @@ +{ + "rules": { + "at-rule-empty-line-before": [ "always", { + except: [ + "blockless-after-same-name-blockless", + "first-nested", + ], + ignore: ["after-comment"], + } ], + "at-rule-name-case": "lower", + "at-rule-name-space-after": "always", + "at-rule-semicolon-newline-after": "always", + "block-closing-brace-empty-line-before": "never", + "block-closing-brace-newline-after": "always", + "block-closing-brace-newline-before": "always", + "block-closing-brace-space-before": "never-single-line", + "block-no-empty": true, + "block-opening-brace-newline-after": "always", + "block-opening-brace-space-after": "never-single-line", + "block-opening-brace-space-before": "always", + "color-hex-case": "lower", + "color-hex-length": "long", + "color-no-invalid-hex": true, + "comment-empty-line-before": [ "always", { + except: ["first-nested"], + ignore: ["stylelint-commands"], + } ], + "comment-no-empty": true, + "comment-whitespace-inside": "always", + "custom-property-empty-line-before": "never", + "declaration-bang-space-after": "never", + "declaration-bang-space-before": "always", + "declaration-block-no-duplicate-properties": [ true, { + ignore: ["consecutive-duplicates-with-different-values"], + } ], + "declaration-block-no-redundant-longhand-properties": true, + "declaration-block-no-shorthand-property-overrides": true, + "declaration-block-semicolon-newline-after": "always", + "declaration-block-semicolon-space-after": "always-single-line", + "declaration-block-semicolon-space-before": "never", + "declaration-block-single-line-max-declarations": 1, + "declaration-block-trailing-semicolon": "always", + "declaration-colon-newline-after": "always-multi-line", + "declaration-colon-space-after": "always-single-line", + "declaration-colon-space-before": "never", + "declaration-empty-line-before": "never", + "font-family-no-duplicate-names": true, + "function-calc-no-unspaced-operator": true, + "function-comma-newline-after": "always-multi-line", + "function-comma-space-after": "always-single-line", + "function-comma-space-before": "never", + "function-linear-gradient-no-nonstandard-direction": true, + "function-max-empty-lines": 0, + "function-name-case": "lower", + "function-parentheses-newline-inside": "always-multi-line", + "function-parentheses-space-inside": "never-single-line", + "function-whitespace-after": "always", + "indentation": "tab", + "keyframe-declaration-no-important": true, + "length-zero-no-unit": true, + "max-empty-lines": 1, + "media-feature-colon-space-after": "always", + "media-feature-colon-space-before": "never", + "media-feature-name-case": "lower", + "media-feature-name-no-unknown": true, + "media-feature-parentheses-space-inside": "never", + "media-feature-range-operator-space-after": "always", + "media-feature-range-operator-space-before": "always", + "media-query-list-comma-newline-after": "always-multi-line", + "media-query-list-comma-space-after": "always-single-line", + "media-query-list-comma-space-before": "never", + "no-duplicate-selectors": true, + "no-empty-source": true, + "no-eol-whitespace": true, + "no-extra-semicolons": true, + "no-missing-end-of-source-newline": true, + "number-leading-zero": "always", + "number-no-trailing-zeros": true, + "property-case": "lower", + "property-no-unknown": true, + "rule-nested-empty-line-before": [ "always", { + except: ["first-nested"], + ignore: ["after-comment"], + } ], + "rule-non-nested-empty-line-before": [ "always", { + ignore: ["after-comment"], + } ], + "selector-attribute-brackets-space-inside": "never", + "selector-attribute-operator-space-after": "never", + "selector-attribute-operator-space-before": "never", + "selector-combinator-space-after": "always", + "selector-combinator-space-before": "always", + "selector-descendant-combinator-no-non-space": true, + "selector-list-comma-newline-after": "always", + "selector-list-comma-space-before": "never", + "selector-max-empty-lines": 0, + "selector-pseudo-class-case": "lower", + "selector-pseudo-class-no-unknown": true, + "selector-pseudo-class-parentheses-space-inside": "never", + "selector-pseudo-element-case": "lower", + "selector-pseudo-element-colon-notation": "double", + "selector-pseudo-element-no-unknown": true, + "selector-type-case": "lower", + "selector-type-no-unknown": true, + "shorthand-property-no-redundant-values": true, + "string-no-newline": true, + "unit-case": "lower", + "unit-no-unknown": true, + "value-list-comma-newline-after": "always-multi-line", + "value-list-comma-space-after": "always-single-line", + "value-list-comma-space-before": "never", + "value-list-max-empty-lines": 0, + }, + "ignoreFiles": "packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less" +} diff --git a/.travis.yml b/.travis.yml index f59ebf8fb82c1c835f40b293d403f76d0991dba0..ee5356efffca7b2c81885a8b3ca0a58d02cc8793 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: node_js services: - docker +- mongodb branches: only: - develop @@ -13,9 +14,12 @@ node_js: addons: apt: sources: + - google-chrome - ubuntu-toolchain-r-test packages: + - google-chrome-stable - g++-4.8 + firefox: "latest" before_cache: - rm -rf $HOME/build/RocketChat/Rocket.Chat/.meteor/local/log - rm -rf $HOME/build/RocketChat/Rocket.Chat/.meteor/local/run @@ -33,14 +37,35 @@ cache: - "$HOME/build/RocketChat/Rocket.Chat/packages/rocketchat-livechat/app/.meteor/local" before_install: - if [ ! -e "$HOME/.meteor/meteor" ]; then curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh; fi +# Start X Virtual Frame Buffer for headless testing with real browsers +- .scripts/start-xvfb.sh install: - export PATH="$HOME/.meteor:$PATH" before_script: +- echo "replication:" | sudo tee -a /etc/mongod.conf +- |- + echo " replSetName: \"rs0\"" | sudo tee -a /etc/mongod.conf +- sudo service mongod restart - mkdir /tmp/build - travis_retry npm install +- |- + mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' - npm run lint -script: +- npm run stylelint - travis_retry meteor build --headless /tmp/build +- mkdir /tmp/build-test +- tar -xf /tmp/build/Rocket.Chat.tar.gz -C /tmp/build-test/ +- cd /tmp/build-test/bundle/programs/server +- npm install -g node-gyp node-pre-gyp +- npm install +- cd - +- mongo --eval 'rs.status()' +- mongo meteor --eval 'db.getCollectionNames()' +script: +- npm test +- mongo meteor --eval 'db.dropDatabase()' +- unset MONGO_OPLOG_URL +- npm test before_deploy: - source ".travis/setartname.sh" - source ".travis/setdeploydir.sh" @@ -64,7 +89,11 @@ after_deploy: - ".travis/snap.sh" env: global: + - DISPLAY=:99.0 - CXX=g++-4.8 - secure: HrPOM5sBibYkMcf9aeQThYPCDiXeLkg0Xgv0HvH88/ku/gphDpNEjHNReHZM3cyfm9y3RhHpVdD+Zzy38S2goKyewRzpXJsuyerOYkjND0v3tivhs9CAX8PAUxj1U5zllTyH4bgW2ZwRtNnwnmtIM/JJlnySMpKVDqIZBpbhn3ph9bJ2J+BW3D3Jw8meQ1vCX8szIibyJK/5QX6HG2RBFXJGYoQ8DmR8jQv0aJQvT1Az5DO4yImk8tX4NP95qOc19Jywr1DsbaSBZeJ8lFJAmBpIGx7KAmUVCcxSxfbXGRhs2K4iEYb3rJ/dU6KiyPsKGUG4aYNGgbvcX0ZxX/BZ6ZU9ff0E4IIf43IxoN3ElrOqOFk5msJAXbrJEreINSzDqKOy8NFYtCQ49E2gwzfage4ZXkhFyx3wMPa5bzpr3ncsTceMjMVz03uL781X6NLuCkUmXv+n8K2MNhJU9Xinpdx1GRJm+0lXJspNNJ1ruHeJtls4epj4bmCwKmmZbFKPXqa5e8xVcMIkwt1LMiHduhE+WgKNHdOMhXrCcTxF62ybLlsHXmyLLJeNjTeKS8QG2XSoonClDAz/1R41I1DsMPblcgz9uvYCf7UtyftbhJ83bnJeEmOYQiwijLG0+QMq+B2+mmZan3Z7Hl7O53dnwuLxz7EO7EhQhY+CqHVgc6s= - secure: w55v/9dmQ9wKbc+fhkZdWfaXIGi+5Qo365J0/IEZRCle2jFWUMBSzEghUuubw9Pys56uAcivOzKozT7+qozVaQW9kSgTLK4bSUSWKeSuynF7Too0CNdzt8CWgjcxGvYVWP0vlp+2eTI0x9+HJlFZn1hP+3v9C+ASH6+sqXvi24kNOPANsUvlbwIC7+T3kOdOOFzrA/tNLkXp2NGs9OaHVnvOVrZlHIn0TL8kGV1HyW4k4KLPgAcwgDq8DRWQBWk+E59HyTGXMyt/GxAJkjEebcJ/TnrbCiExbrTY+OmHjwk8Yfp9CbWsEtEYWSdLoNaFMhUrJRGGKjtansqQWktfkrN/Ro6Scl+lorQQ3eBu+ZVdmx3BXj8TRyqkhh6sqbola5XGhffp61fKxLcbYFE6Ph7g9twvFArCt2/zifmKYxH/NniCKGir5eZZnfFwTE6y2b+m37gjd7jd4t8SEcbFKH7tvOVRNm79VDqBQo0rNieCM5fPKsZkYD7sjB9G7cuZaZLAKUJf1K/FYqCeGeNV0Wcxi95nBh5usenm88xdajShxxKJedi1n77qqj03KxW7ictDnt/sEzz6vHjyf1O2pFUuPZ2eVwenpa0utDOvBTYd10OSlqlWaG/ldNJ8ofl/fawm0of4xp0rY3UNaTYdCZsPm6qfq7NIocefb/qeAJ4= - secure: iihMgVr2PaP9d0GXuEe3JU/eK5agWWHWDH0WFOTHQ42L1x6wT6oom3zJfwGmFL28BDVp5qQQ3V5isc0p1Vca7PCsRWUht/37EzzLbfrutLDvCpvzGqzqTkvKeW9WINYcIwEQoDE0M2I12WZN9NhGCaT5Z3WanwYX1x6AzjHX4GK/gBzX0WWOqKp7UQijLpuWpdlMUhoWKatGKi9ft+PCBgOonhoqy6Pk9QYNG+jrq86RydOP428DE2Mo5aFxPwhSoMFukOlTIPQylJ3q3q9NDHI9XhV6AJwty/CE9UXr4wAI5RAR/9q0cHkoXj7P/y7cj+SI9pZMt5EaS4DPs4MVb8vvVP+7K0VfbVc5kcoXZoUsORtxiaPk5yPeqaqpB89xp7BjQizr5fOXeTp3Xe8UiZeWvpnbvzWEUAW7XXbMLJjyKT6OLRyVmZ240aMY7zdgC/e3nNePaam1LHYJRejJGM0aFIuwDqryGN9KJMjMFOL6LHm+OBQcjfDokBLc1wjfomNXcIrWWjdNTckpDEIhAFzwgxy9OlJQBJqtpPajPrcU1bxfrnbLks0KeQj9YTSTR0D7FknhVF8OpOnDZDPWdUBJL/dGd4eIf0UIr5xW7ZdFJ0xxhGXpJuH5ITG/C8CMOFsFvUMR1EG+cY4UN8YlVcUyp/S6eJgEzJM9fyWwDgs= + - MONGO_OPLOG_URL: "mongodb://localhost:27017/local" + - MONGO_URL: "mongodb://localhost:27017/meteor" + - TEST_MODE: "true" diff --git a/FEATURES.md b/FEATURES.md index 3a05264d2413459a96f8c03a319b2bdff7e4b4dc..381c09a0baf021677dcf49ee01cc0c7eb7ce01ba 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,3 +1,5 @@ +# Features + - Self Host - Docker - Multiple Deployment Options (Heroku, Digital Ocean, Sandstorm, etc.) diff --git a/HISTORY.md b/HISTORY.md index 83fb60c8b1f42c3b9da2a9f13b86fa6f36cf0d05..8e436690ac762e4763ffea533dccbfd56b059115 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,164 @@ # History -## 0.46.0, 2016-Nov-21 +## 0.49.0 - 2017-Jan-11 + +### Now uses NodeJS 4.7.1 + +- Add basic support for RFC 7233 +- Add Button to block direct message +- Add caching layer using LokiDB +- Add custom fields to user's profile +- Add discard and reset button to admin panels +- Add email address validation to livechat offline messages +- Add file name and description on file upload +- Add Livechat domains validations +- Add many API endpoints, see https://rocket.chat/docs/developer-guides/rest-api/ +- Add methods from rest api 0.5 to v1 +- Add stylelint to CSS and LESS files +- Add the migration for bots to be able to create rooms +- Allow alias, avatar, and emoji in the sendFileMessage. +- Allow query, sort, and fields on the queryParams of the rest api +- Allow to merge users with LDAP on bulk sync +- Bi-directional Sladk message edit, delete and reactions +- Disable animations when TEST_MODE=true +- Do not require .jpg for avatar url and return correct content type +- Enable CDN_PREFIX for avatars +- Fix crash at startup if Slack bridge enabled and slack.com is not reachable #5426 +- Fix importer relying on os file type, use file signature. Closes #3050 +- Fix issue creating users with username from OAuth +- Fix screen sharing bug when receiving audio call. issue #5286 +- Migrate livechat visitors' emails field to visitorEmails +- New livechat layout +- Normalize favicons, tiles and touchicons +- Refactored API endpoints to more closely conform to Slack API conventions and naming conventions +- Remove alpha colors and add disabled buttons style +- Sets default avatar after setting username for the first time by default +- Several performance improvements +- Styles cleanup (#5354) (#5364) +- Support SAML IDP-initiated login mode +- Update docker-compose to version 2 +- Use CodeMirror from Npm + +## 0.48.2 - 2016-Dec-20 + +- Add button to refresh aouth services +- Fix download on electron +- Fix issue creating users with username from OAuth +- Fix message when username field not exists in OAuth data +- Fix OAuth global variable + +## 0.48.1 - 2016-Dec-13 + +### Now uses NodeJS 4.7.0 + +- Fix integration payload JSON.parse + +## 0.48.0 - 2016-Dec-12 + +- Add CustomOAuth logger +- Add env var to disable animations +- Add new options (username-field and merge-users) to CustomOAuth +- Add search field in admin +- Add support to set own avatar from URL on REST API +- Add validateNewUser check to compare against whitelist +- Allow setting other users avatars if you have permissions +- Change all 'Has more' with loading animation +- Change CustomOAuth setting format +- Change field name to roles and type to Array. +- Change from loading cert from a file to storing the cert +- Don't allow changing the room type if you only have permission to create one and not the other +- Fix accountFlex highlight on hover issue +- Fix crash if a webhook payload had a field named "payload" +- Fix email being unverified when calling user.update +- Fix Geolocation button +- Fix handle saml urls with query strings. +- Fix katex +- Fix SAML logout +- Fix the chat.postMessage not returning any data about the sent message +- Fix the nameFilter being required on groupsList, since it isn't a requirement +- Fix to do saml http-redirect binding with signing. +- Fix typo in result ordering regex. +- Fix unread messages bar overlapping +- Hide Sandstorm offer button on Cordova +- Init API tests +- Made the logged user check more modular +- Make the server information of the api consistant with others +- Move joinDefaultChannels to internal APIs +- Move the channels to their own file and add several rest api methods +- Move the groups v1 api calls out of the huge routes.coffee file +- Move the rest of the current rest api to individual files +- Move the v1 settings into the v1 folder +- Only unwrapping webhook payloads if necessary +- Pick only departments that would shown on registration if none set +- Prevent register broadcastAuth more than one time +- Remove reactions when messages are removed, fixes #5164 +- Set username automaticaly +- Support username template in CustomOAuth +- Update momentjs to 2.17.1 +- Update slack-client to 2.0.6 + +## 0.47.1 - 2016-Dec-09 + +- Fix color migrations +- Fix to prevent register broadcastAuth more than one time + +## 0.47.0 - 2016-Dec-06 + +- Add 'clear OEmbed cache now' button +- Add a method and rest api to clean up a channel's history +- Add ability to choose a department from the API to livechat +- Add channel history rest api +- Add channel history rest api which is slack compatiable. +- Add ecmascript to all packages with coffeescript +- Add feature to clear OEmbed cache after user-defined amount of time +- Add heirarchy and refactor colour variables +- Add method do check if process is running inside docker +- Add migrations, label, toggle for minor colors +- Add option to disable file uploads in direct messages +- Add the feature to hide the file sharing btn and some fixes +- Allow load css from subdir +- Allow setting border colours in imports +- Allow simpler pinning and unpinning via the methods, only require _id and rid. +- Allow use expressions/variables as colors +- Change custom account box items to button +- Convert the channels.history from post to get +- Fix 'user is typing' break line +- Fix bug with Disable Embed for Users +- Fix button/bg colors and contrast +- Fix code that check for empty object +- Fix file list in cordova +- Fix improper use of head tag (replace with header) +- Fix improve unread mark +- Fix issue #4387, crash when using StartTLS and LDAP +- Fix issue #4813 +- Fix jitsi lib load in sub dir +- Fix login logo in subdir +- Fix missed styles and cull transparent variables +- Fix oauth client when client had previously authorized +- Fix redirectUrl after custom oauth successful login initiated by iframe command, fixes #5042 (#5043) +- Fix sandstorm call setPath on navigation +- Fix set user's email from REST API +- Fix to stop changing the instance IP if running in docker +- Fix windows issues on startup +- Improved performance of sidebar rendering. Fixed RTL sidebar opening. +- Inject meta tag via Inject.rawHead +- Load permissions styles through theme methods +- Migrating from GoogleSiteVerification_id to Meta_google-site-verification +- Move less mixins into separate import +- No longer allow invisible agents get livechats +- Recommend using meteor npm start +- Remove c from function param +- Remove the default value for the latest on the getChannelHistory +- Rename action-buttons-color primary-action-color +- Restore migrations post merge upstream versions +- Serve theme.css through WebApp.rawConnectHandlers +- Show 'connecting to agent..' message option on LIveChat client +- Simplify button classes, remove color names +- Update action link and permissions colors to use theme variables +- Updated to autolinker 1.4.0 +- Use toastr from npm + +## 0.46.0 - 2016-Nov-21 ### Upgraded to meteor 1.4.2.3 - Now uses NodeJS 4.6.2 @@ -68,7 +226,7 @@ - Using border-with on CSS to control borders - Validate user access on file upload -## 0.45.0, 2016-Oct-31 +## 0.45.0 - 2016-Oct-31 - Add global keydown event handler - Add hubot packages as default @@ -108,7 +266,7 @@ - Update ip-range-check to version 0.0.2 to get rid of debugger call Day8/ip-range-check#1 - Update all npm-shrinkwrap.json with npm 3.10.9 -## 0.44.0, 2016-Oct-25 +## 0.44.0 - 2016-Oct-25 - Add archive and unarchive api endpoints - Add check package dependency to the iframe-login package. (#4664) @@ -129,7 +287,7 @@ - Replace mrt:moment-timezone by aldeed:moment-timezone as it depend on the official moment package - Set tap:i18n version in i18n package to install the expected version when the package is used in other projects -## 0.43.0, 2016-Oct-17 +## 0.43.0 - 2016-Oct-17 - Add @here support for only notifying users that are active - Add base support for config via webservices @@ -155,7 +313,7 @@ - Set babel cache directory for integrations - Switch snap from imagemagick to graphicsmagick -## 0.42.0, 2016-Oct-04 +## 0.42.0 - 2016-Oct-04 - Add dependency to package with avatar template - Add ids for irc.server callbacks @@ -187,7 +345,7 @@ - Standardize settings endpoint return - Update Autolinker to 1.2.0 -## 0.41.0, 2016-Sep-27 +## 0.41.0 - 2016-Sep-27 - Add ability to close open livechats if an agent goes offline - Add basic channels tests @@ -218,7 +376,7 @@ - Replace autocomplte popups subscriptions with methods - Trigger global event to embedded images -## 0.40.1, 2016-Sep-21 +## 0.40.1 - 2016-Sep-21 - Allow Iframe login with default tokens - Fix embedded layout message box auto-resize @@ -230,7 +388,7 @@ - Show file type on file upload error (#3217) - Use the npm package of UAParser on LiveChat -## 0.40.0, 2016-Sep-20 +## 0.40.0 - 2016-Sep-20 ### Upgraded to meteor 1.4.1.1 - Now uses NodeJS 4.5 @@ -310,7 +468,7 @@ - Using faster npm bcrypt module - Verify permissions on spotlight list -## 0.39.0, 2016-Sep-05 +## 0.39.0 - 2016-Sep-05 - Accept username from SAML response - Add image attachment support when a bot (ex using giffy) posts just an image @@ -339,7 +497,7 @@ - UI improvements to login screen - Update the opened livechat room by token -## 0.38.0, 2016-Aug-30 +## 0.38.0 - 2016-Aug-30 - Action links improvements - Add global event unread-changed-by-subscription @@ -374,7 +532,7 @@ - Update to depend only on the gMaps API key, add i18n strings for geolocaiotn sharing - Updated loginform a11y and UX - labels instead of placeholders (#4075) -## 0.37.1, 2016-Aug-17 +## 0.37.1 - 2016-Aug-17 - Allow deletion of records with same id on settings - Created inital Iframe integration @@ -385,7 +543,7 @@ - Changed SlackBridge to import from begin to end - Suppress message-pinned notification from import -## 0.37.0, 2016-Aug-15 +## 0.37.0 - 2016-Aug-15 - Added an option to SlackBridge to exclude some bots messages from propagating. (#3813) - Added bot-helpers package (#3799) @@ -439,7 +597,7 @@ - Update default setting for file upload types to include video - Update side-nav with room counts (#3967) -## 0.36.0, 2016-Aug-02 +## 0.36.0 - 2016-Aug-02 ### Core updates @@ -515,7 +673,7 @@ - Update emojione to 2.2.5 (#3736) - Update hubot version to v.0.1.4 -## 0.35.0, 2016-Jun-28 +## 0.35.0 - 2016-Jun-28 - Add a list of reserved usernames - Add admin setting to disable merged groups and channels @@ -534,12 +692,12 @@ - Preventing message update on multiple sendMessage calls - Update for Dataporten closing #3580 (#3608) -## 0.34.0, 2016-Jun-14 +## 0.34.0 - 2016-Jun-14 - BETA JITSI INTEGRATION (#3476) - Add more config options to livechat (#3497) -## 0.33.0, 2016-Jun-07 +## 0.33.0 - 2016-Jun-07 - Add a method and api way to get a user's private groups, for external usage - Add ASCII art commands /tableflip /unflip /lennyface /gimme @@ -562,7 +720,7 @@ - Send livechat webhooks - Use <button/> rather than <i/> for tab buttons. -## 0.32.0, 2016-May-30 +## 0.32.0 - 2016-May-30 - Add autocomplete for adding users to roles - Add bad word filter to settings UI @@ -594,7 +752,7 @@ - Remove resize animation preventing scroll stay at bottom - Update user-presence package -## 0.31.0, 2016-May-16 +## 0.31.0 - 2016-May-16 - Add header and footer to e-mails - Add new livechat settings to livechat manager @@ -625,7 +783,7 @@ - Save room's name as the livechat visitor name - Use HTML emails instead of Text- -## 0.30.0, 2016-May-09 +## 0.30.0 - 2016-May-09 - Ability to run imports several times without duplicate messages (#3123) - Add /shrug command @@ -674,7 +832,7 @@ - Use native code to set file upload cookies - Wait until user is logged-in to add message listener -## 0.29.0, 2016-May-02 +## 0.29.0 - 2016-May-02 - Add a i18nDefaultQuery option to settings - Add a sequential code for livechat rooms @@ -725,7 +883,7 @@ - Use new placholders.js for sending mail through Mailer - Verify if user's emails and phone are arrays before showing them -## 0.28.0, 2016-Apr-25 +## 0.28.0 - 2016-Apr-25 - Add "by" and "at" to language files - Add API method to list online users in a room @@ -767,7 +925,7 @@ - Show all - RTL fix (#2957) - Use the logo from uploaded assets for the menu footer -## 0.27.0, 2016-Apr-18 +## 0.27.0 - 2016-Apr-18 - Add admin to default list of allowed roles on 'pin-message' (#2846) - Add date/time format settings (#2852) @@ -801,7 +959,7 @@ - Use different color for mentions "all" (#2865) - User info tab bar improvements (#2893) -## 0.26.0, 2016-Apr-11 +## 0.26.0 - 2016-Apr-11 - Add a download icon to file list (#2817) - Add ability to hide embedded media @@ -840,7 +998,7 @@ - Use RocketChat Logger as SyncedCron logger - When creating a room, set user only as owner, not moderator -## 0.25.0, 2016-Apr-04 +## 0.25.0 - 2016-Apr-04 - Add black list email list options - Add more indexes to users collection @@ -887,7 +1045,7 @@ - Use page-loading animation when waiting subs - Use ReadOnly globals -## 0.24.0, 2016-Mar-28 +## 0.24.0 - 2016-Mar-28 - Add a title with emoji's shortname on picker - Add Assets and Blaze to jshint global variables @@ -944,7 +1102,7 @@ - Use the login layout for the reset password screen - Using PNG emoji sprites for better performance -## 0.23.0, 2016-Mar-21 +## 0.23.0 - 2016-Mar-21 - Accept * for all media types - Add emoji picker @@ -988,7 +1146,7 @@ - Use login logo as asset - Use URL compatible token and do not sabe in user record -## 0.22.0, 2016-Mar-14 +## 0.22.0 - 2016-Mar-14 - Add AES encryption routines - Add CDN config option for file upload @@ -1032,7 +1190,7 @@ - Trim slashes from Site_Url - closes #2462 - Upload files to file system support -## 0.21.0, 2016-Mar-07 +## 0.21.0 - 2016-Mar-07 - Add ability for users to delete their own accounts - Add infinite scrolling to channels list @@ -1066,7 +1224,7 @@ - Shows OAuth Callback URLs - Support 'user_id' in addition to 'id' and 'ID' for service identifier -## 0.20.0, 2016-Feb-29 +## 0.20.0 - 2016-Feb-29 - Ability to disable sending nickname and message via push notification - Add back 'delete room' button - closes #2351 @@ -1104,7 +1262,7 @@ - Updated sweetalert - Uses the setting for validating rooms renaming - closes #2297 -## 0.19.0, 2016-Feb-22 +## 0.19.0 - 2016-Feb-22 - Add alerts for highlight words - Add button to show offline users in a room @@ -1143,7 +1301,7 @@ - Split CA cert into array of strings. - Switched CAS configuration from Meteor.settings to RocketChat.settings. -## 0.18.0, 2016-Feb-15 +## 0.18.0 - 2016-Feb-15 - Add .jshintrc to project - Add button to test desktop notifications @@ -1168,7 +1326,7 @@ - Terminal output should be displayed in LTR always - Using REST to send pushes through gateway -## 0.17.0, 2016-Feb-09 +## 0.17.0 - 2016-Feb-09 - Add a button to allow deleting an uploaded file - Add an example of how to send logs from server to client @@ -1203,7 +1361,7 @@ - Show that server is running on logs - Use the RocketChat.Info.version on headers -## 0.16.0, 2016-Feb-01 +## 0.16.0 - 2016-Feb-01 - Add option for admin to require user to change password - Add option for admins to manually add new users @@ -1218,7 +1376,7 @@ - Show "Room not Found" correctly - Update konecty:multiple-instances-status to 1.0.5 -## 0.15.0, 2016-Jan-25 +## 0.15.0 - 2016-Jan-25 - Ability to change email on account - Add "Default Domain" to LDAP config @@ -1239,7 +1397,7 @@ - Outgoing: Get the room from posted message to reply - Temporary fix for AM/PM timestamp breaking cog -## 0.14.0, 2016-Jan-18 +## 0.14.0 - 2016-Jan-18 - Add admin setting to Force SSL - Add connections status bar to login page @@ -1288,7 +1446,7 @@ - Using default values instead of integration data - Using processWebhookMessage on V1 APIs -## 0.13.0, 2016-Jan-11 +## 0.13.0 - 2016-Jan-11 - Add api `chat.messageExample` - Add apis 'integrations.create' and 'integrations.remove' @@ -1330,11 +1488,11 @@ - Update log.coffee - Use different ids for members info and user info tabbars -## 0.12.1, 2016-Jan-05 +## 0.12.1 - 2016-Jan-05 - Fix problem with middleware that tries to parse json body -## 0.12.0, 2016-Jan-04 +## 0.12.0 - 2016-Jan-04 - Add a setting to disable form-based login - Add request debug messages @@ -1352,7 +1510,7 @@ - Try to parse all request bodies as JSON - Upload build artifacts to GitHub and sign tgz for docker images -## 0.11.0, 2015-Dec-28 +## 0.11.0 - 2015-Dec-28 - Add "Jump to" and infinite scroll to message search results - Add infinite scroll to files list @@ -1395,15 +1553,15 @@ - Turn channel and triggerWords optional in triggers - Using branding image from main APP -## 0.10.2, 2015-Dec-22 +## 0.10.2 - 2015-Dec-22 - Fixes image preview bugs with filenames containing spaces -## 0.10.1, 2015-Dec-21 +## 0.10.1 - 2015-Dec-21 - Fix upload permissions introduced in raik:ufs 0.3.4 -## 0.10.0, 2015-Dec-21 +## 0.10.0 - 2015-Dec-21 - Accept property *msg* as text in attachments - Add "Room has been deleted" entry @@ -1503,7 +1661,7 @@ - Use attachments to render preview of uploads and use relative paths - Using flow-router group routes -## 0.9.0, 2015-Dec-14 +## 0.9.0 - 2015-Dec-14 - Add a new setting type "action" to call server methods - Add lib clipboard.js @@ -1545,7 +1703,7 @@ - Prompt users to install extentions to enable screen sharing - Shos if message is from bot and never render compact message version -## 0.8.0, 2015-Dec-8 +## 0.8.0 - 2015-Dec-8 - Add "Meiryo UI" to font-family - Add option to disable "Forgot Password" link on login page @@ -1576,6 +1734,6 @@ - Translate section of settings - Update the flex-nav hidden element for RTL -## 0.1.0, 2015-May-19 +## 0.1.0 - 2015-May-19 - Initial public launch diff --git a/README.md b/README.md index 2301f956ac32983d4abb080310525786779b8e81..7d974a5e5b97a248cff83b528d7e6b7e0d575e94 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [](https://demo.rocket.chat/) [](https://travis-ci.org/RocketChat/Rocket.Chat) +[](https://david-dm.org/RocketChat/Rocket.Chat) [](https://www.codacy.com/app/RocketChat/Rocket-Chat) [](https://coveralls.io/r/RocketChat/Rocket.Chat) [](https://codeclimate.com/github/RocketChat/Rocket.Chat) diff --git a/README.nitrous.md b/README.nitrous.md deleted file mode 100644 index 99ffe29084aa1918b55a297a280509829751ba4f..0000000000000000000000000000000000000000 --- a/README.nitrous.md +++ /dev/null @@ -1,16 +0,0 @@ -# Setup - -Welcome to your Rocket.Chat project on Nitrous. - -## Running the development server: - -In the [Nitrous IDE](https://community.nitrous.io/docs/ide-overview), enter the following commands in the terminal window: - -1. `cd ~/code/Rocket.Chat` -2. `meteor --port 0.0.0.0:3000` - -Now you've got a development server running and can see the output in the Nitrous terminal window. You can open up a new shell or utilize [tmux](https://community.nitrous.io/docs/tmux) to open new shells to run other commands. - -## Preview the app - -In the Nitrous IDE, open the "Preview" menu and click "Port 3000". diff --git a/app.json b/app.json index 2b703091f2a23a3326fc4d6f68a8c871164413d7..e4d3a3c577c562eaec88687bb9ff1417bc3589ba 100644 --- a/app.json +++ b/app.json @@ -6,7 +6,7 @@ "keywords": ["meteor", "social", "community", "chat"], "website": "https://rocket.chat", "env": { - "BUILDPACK_URL": "https://github.com/RocketChat/heroku-buildpack-meteor.git", + "BUILDPACK_URL": "https://github.com/RocketChat/meteor-buildpack-horse.git", "HEROKU_APP_NAME": { "description": "Please re-enter your App Name from the top.", "required": true diff --git a/client/helpers/log.coffee b/client/helpers/log.coffee deleted file mode 100644 index bc6666749306cd453967c769bdcc405499b14105..0000000000000000000000000000000000000000 --- a/client/helpers/log.coffee +++ /dev/null @@ -1,2 +0,0 @@ -Template.registerHelper 'log', -> - console.log.apply console, arguments diff --git a/client/helpers/log.js b/client/helpers/log.js new file mode 100644 index 0000000000000000000000000000000000000000..0c6ee1e3c000063941a032fa515b305f78e21ba5 --- /dev/null +++ b/client/helpers/log.js @@ -0,0 +1,3 @@ +Template.registerHelper('log', () => { + console.log.apply(console, arguments); +}); diff --git a/client/helpers/not.js b/client/helpers/not.js new file mode 100644 index 0000000000000000000000000000000000000000..91b4e19a3c86403181d72f49b23a6c73d904782d --- /dev/null +++ b/client/helpers/not.js @@ -0,0 +1,3 @@ +Template.registerHelper('not', (value) => { + return !value; +}); diff --git a/client/methods/deleteMessage.coffee b/client/methods/deleteMessage.coffee deleted file mode 100644 index fc2dc6f57747feb051ec215462ae4ee5b7881d3f..0000000000000000000000000000000000000000 --- a/client/methods/deleteMessage.coffee +++ /dev/null @@ -1,27 +0,0 @@ -import moment from 'moment' -import toastr from 'toastr' - -Meteor.methods - deleteMessage: (message) -> - if not Meteor.userId() - return false - - hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid) - deleteAllowed = RocketChat.settings.get 'Message_AllowDeleting' - deleteOwn = message?.u?._id is Meteor.userId() - - unless hasPermission or (deleteAllowed and deleteOwn) - return false - - blockDeleteInMinutes = RocketChat.settings.get 'Message_AllowDeleting_BlockDeleteInMinutes' - if blockDeleteInMinutes? and blockDeleteInMinutes isnt 0 - msgTs = moment(message.ts) if message.ts? - currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs? - if currentTsDiff > blockDeleteInMinutes - toastr.error t('error-message-deleting-blocked') - return false - - Tracker.nonreactive -> - ChatMessage.remove - _id: message._id - 'u._id': Meteor.userId() diff --git a/client/methods/deleteMessage.js b/client/methods/deleteMessage.js new file mode 100644 index 0000000000000000000000000000000000000000..2f77e1d825cf82e9fed9153f29b6278e06020b0a --- /dev/null +++ b/client/methods/deleteMessage.js @@ -0,0 +1,42 @@ +import moment from 'moment'; +import toastr from 'toastr'; + +Meteor.methods({ + deleteMessage(message) { + if (!Meteor.userId()) { + return false; + } + + const hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid); + const deleteAllowed = RocketChat.settings.get('Message_AllowDeleting'); + let deleteOwn = false; + if (message && message.u && message.u._id) { + deleteOwn = message.u._id === Meteor.userId(); + } + + if (!(hasPermission || (deleteAllowed && deleteOwn))) { + return false; + } + + const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes'); + if (_.isNumber(blockDeleteInMinutes) && blockDeleteInMinutes !== 0) { + if (message.ts) { + const msgTs = moment(message.ts); + if (msgTs) { + const currentTsDiff = moment().diff(msgTs, 'minutes'); + if (currentTsDiff > blockDeleteInMinutes) { + toastr.error(t('error-message-deleting-blocked')); + return false; + } + } + } + } + + Tracker.nonreactive(function() { + ChatMessage.remove({ + _id: message._id, + 'u._id': Meteor.userId() + }); + }); + } +}); diff --git a/client/methods/hideRoom.coffee b/client/methods/hideRoom.coffee deleted file mode 100644 index 09ea75739572981a49b4a58b1cc2b34b66296741..0000000000000000000000000000000000000000 --- a/client/methods/hideRoom.coffee +++ /dev/null @@ -1,12 +0,0 @@ -Meteor.methods - hideRoom: (rid) -> - if not Meteor.userId() - return false - - ChatSubscription.update - rid: rid - 'u._id': Meteor.userId() - , - $set: - alert: false - open: false diff --git a/client/methods/hideRoom.js b/client/methods/hideRoom.js new file mode 100644 index 0000000000000000000000000000000000000000..801f464e420b7d21950b53f3bd18eca15a26997d --- /dev/null +++ b/client/methods/hideRoom.js @@ -0,0 +1,17 @@ +Meteor.methods({ + hideRoom(rid) { + if (!Meteor.userId()) { + return false; + } + + ChatSubscription.update({ + rid: rid, + 'u._id': Meteor.userId() + }, { + $set: { + alert: false, + open: false + } + }); + } +}); diff --git a/client/methods/leaveRoom.coffee b/client/methods/leaveRoom.coffee deleted file mode 100644 index b9877543838f572f4b4311416c6898e7f0e407b4..0000000000000000000000000000000000000000 --- a/client/methods/leaveRoom.coffee +++ /dev/null @@ -1,13 +0,0 @@ -Meteor.methods - leaveRoom: (rid) -> - if not Meteor.userId() - return false - - ChatSubscription.remove - rid: rid - 'u._id': Meteor.userId() - - ChatRoom.update rid, - $pull: - usernames: Meteor.user().username - diff --git a/client/methods/leaveRoom.js b/client/methods/leaveRoom.js new file mode 100644 index 0000000000000000000000000000000000000000..8de1d369484975ed291f2c68f8751a310180aa19 --- /dev/null +++ b/client/methods/leaveRoom.js @@ -0,0 +1,18 @@ +Meteor.methods({ + leaveRoom(rid) { + if (!Meteor.userId()) { + return false; + } + + ChatSubscription.remove({ + rid: rid, + 'u._id': Meteor.userId() + }); + + ChatRoom.update(rid, { + $pull: { + usernames: Meteor.user().username + } + }); + } +}); diff --git a/client/methods/openRoom.coffee b/client/methods/openRoom.coffee deleted file mode 100644 index bb40b2bdbf3a0ef0e322f84532254202e5b9ca48..0000000000000000000000000000000000000000 --- a/client/methods/openRoom.coffee +++ /dev/null @@ -1,11 +0,0 @@ -Meteor.methods - openRoom: (rid) -> - if not Meteor.userId() - return false - - ChatSubscription.update - rid: rid - 'u._id': Meteor.userId() - , - $set: - open: true diff --git a/client/methods/openRoom.js b/client/methods/openRoom.js new file mode 100644 index 0000000000000000000000000000000000000000..9aa659e937b87b8c6c4a336a96e63f87599f8558 --- /dev/null +++ b/client/methods/openRoom.js @@ -0,0 +1,16 @@ +Meteor.methods({ + openRoom(rid) { + if (!Meteor.userId()) { + return false; + } + + ChatSubscription.update({ + rid: rid, + 'u._id': Meteor.userId() + }, { + $set: { + open: true + } + }); + } +}); diff --git a/client/methods/setUserActiveStatus.coffee b/client/methods/setUserActiveStatus.coffee deleted file mode 100644 index ac4c9614e43a6ccc9f20126642070e238c3d6ab3..0000000000000000000000000000000000000000 --- a/client/methods/setUserActiveStatus.coffee +++ /dev/null @@ -1,4 +0,0 @@ -Meteor.methods - setUserActiveStatus: (userId, active) -> - Meteor.users.update userId, { $set: { active: active } } - return true \ No newline at end of file diff --git a/client/methods/setUserActiveStatus.js b/client/methods/setUserActiveStatus.js new file mode 100644 index 0000000000000000000000000000000000000000..d61598877df422076d017fe2326bd3cb9990497b --- /dev/null +++ b/client/methods/setUserActiveStatus.js @@ -0,0 +1,6 @@ +Meteor.methods({ + setUserActiveStatus(userId, active) { + Meteor.users.update(userId, { $set: { active: active } }); + return true; + } +}); diff --git a/client/methods/toggleFavorite.coffee b/client/methods/toggleFavorite.coffee deleted file mode 100644 index 6348bc9cd536d7600211a043f7dce92b39971c8d..0000000000000000000000000000000000000000 --- a/client/methods/toggleFavorite.coffee +++ /dev/null @@ -1,11 +0,0 @@ -Meteor.methods - toggleFavorite: (rid, f) -> - if not Meteor.userId() - return false - - ChatSubscription.update - rid: rid - 'u._id': Meteor.userId() - , - $set: - f: f diff --git a/client/methods/toggleFavorite.js b/client/methods/toggleFavorite.js new file mode 100644 index 0000000000000000000000000000000000000000..e58b8eb71c0a084d9ceb2b5cdd06f509e6363baa --- /dev/null +++ b/client/methods/toggleFavorite.js @@ -0,0 +1,16 @@ +Meteor.methods({ + toggleFavorite(rid, f) { + if (!Meteor.userId()) { + return false; + } + + ChatSubscription.update({ + rid: rid, + 'u._id': Meteor.userId() + }, { + $set: { + f: f + } + }); + } +}); diff --git a/client/methods/updateMessage.coffee b/client/methods/updateMessage.coffee deleted file mode 100644 index f9a8cb9139a6ade3f42d2b853f900fd528939238..0000000000000000000000000000000000000000 --- a/client/methods/updateMessage.coffee +++ /dev/null @@ -1,49 +0,0 @@ -import moment from 'moment' -import toastr from 'toastr' - -Meteor.methods - updateMessage: (message) -> - if not Meteor.userId() - return false - - originalMessage = ChatMessage.findOne message._id - - hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid) - editAllowed = RocketChat.settings.get 'Message_AllowEditing' - editOwn = originalMessage?.u?._id is Meteor.userId() - - me = Meteor.users.findOne Meteor.userId() - - unless hasPermission or (editAllowed and editOwn) - toastr.error t('error-action-not-allowed', { action: t('Message_editing') }) - return false - - blockEditInMinutes = RocketChat.settings.get 'Message_AllowEditing_BlockEditInMinutes' - if blockEditInMinutes? and blockEditInMinutes isnt 0 - msgTs = moment(originalMessage.ts) if originalMessage.ts? - currentTsDiff = moment().diff(msgTs, 'minutes') if msgTs? - if currentTsDiff > blockEditInMinutes - toastr.error t('error-message-editing-blocked') - return false - - Tracker.nonreactive -> - - if isNaN(TimeSync.serverOffset()) - message.editedAt = new Date() - else - message.editedAt = new Date(Date.now() + TimeSync.serverOffset()) - - message.editedBy = - _id: Meteor.userId() - username: me.username - - message = RocketChat.callbacks.run 'beforeSaveMessage', message - - ChatMessage.update - _id: message._id - 'u._id': Meteor.userId() - , - $set: - "editedAt": message.editedAt - "editedBy": message.editedBy - msg: message.msg diff --git a/client/methods/updateMessage.js b/client/methods/updateMessage.js new file mode 100644 index 0000000000000000000000000000000000000000..2f5a5d87c05529bb2d774aacb283051db2344f74 --- /dev/null +++ b/client/methods/updateMessage.js @@ -0,0 +1,67 @@ +import moment from 'moment'; +import toastr from 'toastr'; + +Meteor.methods({ + updateMessage(message) { + if (!Meteor.userId()) { + return false; + } + + const originalMessage = ChatMessage.findOne(message._id); + + const hasPermission = RocketChat.authz.hasAtLeastOnePermission('edit-message', message.rid); + const editAllowed = RocketChat.settings.get('Message_AllowEditing'); + let editOwn = false; + if (originalMessage && originalMessage.u && originalMessage.u._id) { + editOwn = originalMessage.u._id === Meteor.userId(); + } + + const me = Meteor.users.findOne(Meteor.userId()); + + if (!(hasPermission || (editAllowed && editOwn))) { + toastr.error(t('error-action-not-allowed', { action: t('Message_editing') })); + return false; + } + + const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes'); + if (_.isNumber(blockEditInMinutes) && blockEditInMinutes !== 0) { + if (originalMessage.ts) { + const msgTs = moment(originalMessage.ts); + if (msgTs) { + const currentTsDiff = moment().diff(msgTs, 'minutes'); + if (currentTsDiff > blockEditInMinutes) { + toastr.error(t('error-message-editing-blocked')); + return false; + } + } + } + } + + Tracker.nonreactive(function() { + + if (isNaN(TimeSync.serverOffset())) { + message.editedAt = new Date(); + } else { + message.editedAt = new Date(Date.now() + TimeSync.serverOffset()); + } + + message.editedBy = { + _id: Meteor.userId(), + username: me.username + }; + + message = RocketChat.callbacks.run('beforeSaveMessage', message); + + ChatMessage.update({ + _id: message._id, + 'u._id': Meteor.userId() + }, { + $set: { + 'editedAt': message.editedAt, + 'editedBy': message.editedBy, + msg: message.msg + } + }); + }); + } +}); diff --git a/client/notifications/notification.coffee b/client/notifications/notification.coffee deleted file mode 100644 index a041691a38019c3f5ec9afcf850473d5446cf09c..0000000000000000000000000000000000000000 --- a/client/notifications/notification.coffee +++ /dev/null @@ -1,31 +0,0 @@ -# Show notifications and play a sound for new messages. -# We trust the server to only send notifications for interesting messages, e.g. direct messages or -# group messages in which the user is mentioned. - -Meteor.startup -> - Tracker.autorun -> - if Meteor.userId() - RocketChat.Notifications.onUser 'notification', (notification) -> - - openedRoomId = undefined - if FlowRouter.getRouteName() in ['channel', 'group', 'direct'] - openedRoomId = Session.get 'openedRoom' - - # This logic is duplicated in /client/startup/unread.coffee. - hasFocus = readMessage.isEnable() - messageIsInOpenedRoom = openedRoomId is notification.payload.rid - - fireGlobalEvent 'notification', - notification: notification - fromOpenedRoom: messageIsInOpenedRoom - hasFocus: hasFocus - - if RocketChat.Layout.isEmbedded() - if !hasFocus and messageIsInOpenedRoom - # Play a sound and show a notification. - KonchatNotification.newMessage() - KonchatNotification.showDesktop notification - else if !(hasFocus and messageIsInOpenedRoom) - # Play a sound and show a notification. - KonchatNotification.newMessage() - KonchatNotification.showDesktop notification diff --git a/client/notifications/notification.js b/client/notifications/notification.js new file mode 100644 index 0000000000000000000000000000000000000000..15cfa755f2de019f7fac8f42fb3323255332a3dc --- /dev/null +++ b/client/notifications/notification.js @@ -0,0 +1,41 @@ +/* globals KonchatNotification, fireGlobalEvent, readMessage */ + +// Show notifications and play a sound for new messages. +// We trust the server to only send notifications for interesting messages, e.g. direct messages or +// group messages in which the user is mentioned. + +Meteor.startup(function() { + Tracker.autorun(function() { + if (Meteor.userId()) { + RocketChat.Notifications.onUser('notification', function(notification) { + + let openedRoomId = undefined; + if (['channel', 'group', 'direct'].includes(FlowRouter.getRouteName())) { + openedRoomId = Session.get('openedRoom'); + } + + // This logic is duplicated in /client/startup/unread.coffee. + const hasFocus = readMessage.isEnable(); + const messageIsInOpenedRoom = openedRoomId === notification.payload.rid; + + fireGlobalEvent('notification', { + notification: notification, + fromOpenedRoom: messageIsInOpenedRoom, + hasFocus: hasFocus + }); + + if (RocketChat.Layout.isEmbedded()) { + if (!hasFocus && messageIsInOpenedRoom) { + // Play a sound and show a notification. + KonchatNotification.newMessage(); + KonchatNotification.showDesktop(notification); + } + } else if (!(hasFocus && messageIsInOpenedRoom)) { + // Play a sound and show a notification. + KonchatNotification.newMessage(); + KonchatNotification.showDesktop(notification); + } + }); + } + }); +}); diff --git a/client/notifications/updateAvatar.coffee b/client/notifications/updateAvatar.coffee deleted file mode 100644 index fc63c7b1a9723baf422d0221f55bac199e39ac04..0000000000000000000000000000000000000000 --- a/client/notifications/updateAvatar.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Meteor.startup -> - RocketChat.Notifications.onAll 'updateAvatar', (data) -> - updateAvatarOfUsername data.username diff --git a/client/notifications/updateAvatar.js b/client/notifications/updateAvatar.js new file mode 100644 index 0000000000000000000000000000000000000000..b534f3956318eff9724d2a2fc3ed6f5a46e4b8ce --- /dev/null +++ b/client/notifications/updateAvatar.js @@ -0,0 +1,7 @@ +/* globals updateAvatarOfUsername */ + +Meteor.startup(function() { + RocketChat.Notifications.onAll('updateAvatar', function(data) { + updateAvatarOfUsername(data.username); + }); +}); diff --git a/client/routes/adminRouter.coffee b/client/routes/adminRouter.coffee deleted file mode 100644 index c528ebba2175f5397a3a8f260d5c19dde13d4571..0000000000000000000000000000000000000000 --- a/client/routes/adminRouter.coffee +++ /dev/null @@ -1,38 +0,0 @@ -FlowRouter.route '/admin/users', - name: 'admin-users' - action: -> - RocketChat.TabBar.showGroup 'adminusers' - BlazeLayout.render 'main', {center: 'adminUsers'} - -FlowRouter.route '/admin/rooms', - name: 'admin-rooms' - action: -> - RocketChat.TabBar.showGroup 'adminrooms' - BlazeLayout.render 'main', {center: 'adminRooms'} - -FlowRouter.route '/admin/info', - name: 'admin-info' - action: -> - RocketChat.TabBar.showGroup 'adminInfo' - BlazeLayout.render 'main', {center: 'adminInfo'} - -FlowRouter.route '/admin/import', - name: 'admin-import' - action: -> - BlazeLayout.render 'main', {center: 'adminImport'} - -FlowRouter.route '/admin/import/prepare/:importer', - name: 'admin-import-prepare' - action: -> - BlazeLayout.render 'main', {center: 'adminImportPrepare'} - -FlowRouter.route '/admin/import/progress/:importer', - name: 'admin-import-progress' - action: -> - BlazeLayout.render 'main', {center: 'adminImportProgress'} - -FlowRouter.route '/admin/:group?', - name: 'admin' - action: -> - RocketChat.TabBar.showGroup 'admin' - BlazeLayout.render 'main', {center: 'admin'} diff --git a/client/routes/adminRouter.js b/client/routes/adminRouter.js new file mode 100644 index 0000000000000000000000000000000000000000..bc89c6b2104caae1cfb522ce5a5808a1646c6304 --- /dev/null +++ b/client/routes/adminRouter.js @@ -0,0 +1,52 @@ +FlowRouter.route('/admin/users', { + name: 'admin-users', + action() { + RocketChat.TabBar.showGroup('adminusers'); + BlazeLayout.render('main', {center: 'adminUsers'}); + } +}); + +FlowRouter.route('/admin/rooms', { + name: 'admin-rooms', + action() { + RocketChat.TabBar.showGroup('adminrooms'); + BlazeLayout.render('main', {center: 'adminRooms'}); + } +}); + +FlowRouter.route('/admin/info', { + name: 'admin-info', + action() { + RocketChat.TabBar.showGroup('adminInfo'); + BlazeLayout.render('main', {center: 'adminInfo'}); + } +}); + +FlowRouter.route('/admin/import', { + name: 'admin-import', + action() { + BlazeLayout.render('main', {center: 'adminImport'}); + } +}); + +FlowRouter.route('/admin/import/prepare/:importer', { + name: 'admin-import-prepare', + action() { + BlazeLayout.render('main', {center: 'adminImportPrepare'}); + } +}); + +FlowRouter.route('/admin/import/progress/:importer', { + name: 'admin-import-progress', + action() { + BlazeLayout.render('main', {center: 'adminImportProgress'}); + } +}); + +FlowRouter.route('/admin/:group?', { + name: 'admin', + action() { + RocketChat.TabBar.showGroup('admin'); + BlazeLayout.render('main', {center: 'admin'}); + } +}); diff --git a/client/routes/roomRoute.js b/client/routes/roomRoute.js index f3a067f0a6fce606d486437d6c4fff1118429aaa..f77e4d9b70bad989411dce080c9482348c5e01bb 100644 --- a/client/routes/roomRoute.js +++ b/client/routes/roomRoute.js @@ -1,6 +1,6 @@ FlowRouter.goToRoomById = (roomId) => { const subscription = ChatSubscription.findOne({rid: roomId}); if (subscription) { - FlowRouter.go(RocketChat.roomTypes.getRouteLink(subscription.t, subscription), null, FlowRouter.current().queryParams); + RocketChat.roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); } }; diff --git a/client/routes/router.coffee b/client/routes/router.coffee deleted file mode 100644 index ec68173e0fe86e936243af1bc7cad24ed7b67df8..0000000000000000000000000000000000000000 --- a/client/routes/router.coffee +++ /dev/null @@ -1,119 +0,0 @@ -Blaze.registerHelper 'pathFor', (path, kw) -> - return FlowRouter.path path, kw.hash - -BlazeLayout.setRoot 'body' - -FlowRouter.subscriptions = -> - Tracker.autorun => - if Meteor.userId() - @register 'userData', Meteor.subscribe('userData') - @register 'activeUsers', Meteor.subscribe('activeUsers') - - -FlowRouter.route '/', - name: 'index' - - action: -> - BlazeLayout.render 'main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' } - if not Meteor.userId() - return FlowRouter.go 'home' - - Tracker.autorun (c) -> - if FlowRouter.subsReady() is true - Meteor.defer -> - if Meteor.user().defaultRoom? - room = Meteor.user().defaultRoom.split('/') - FlowRouter.go room[0], { name: room[1] }, FlowRouter.current().queryParams - else - FlowRouter.go 'home' - c.stop() - - -FlowRouter.route '/login', - name: 'login' - - action: -> - FlowRouter.go 'home' - - -FlowRouter.route '/home', - name: 'home' - - action: -> - RocketChat.TabBar.showGroup 'home' - BlazeLayout.render 'main', {center: 'home'} - KonchatNotification.getDesktopPermission() - - -FlowRouter.route '/changeavatar', - name: 'changeAvatar' - - action: -> - RocketChat.TabBar.showGroup 'changeavatar' - BlazeLayout.render 'main', {center: 'avatarPrompt'} - -FlowRouter.route '/account/:group?', - name: 'account' - - action: (params) -> - unless params.group - params.group = 'Preferences' - params.group = _.capitalize params.group, true - RocketChat.TabBar.showGroup 'account' - BlazeLayout.render 'main', { center: "account#{params.group}" } - - -FlowRouter.route '/history/private', - name: 'privateHistory' - - subscriptions: (params, queryParams) -> - @register 'privateHistory', Meteor.subscribe('privateHistory') - - action: -> - Session.setDefault('historyFilter', '') - RocketChat.TabBar.showGroup 'private-history' - BlazeLayout.render 'main', {center: 'privateHistory'} - - -FlowRouter.route '/terms-of-service', - name: 'terms-of-service' - - action: -> - Session.set 'cmsPage', 'Layout_Terms_of_Service' - BlazeLayout.render 'cmsPage' - -FlowRouter.route '/privacy-policy', - name: 'privacy-policy' - - action: -> - Session.set 'cmsPage', 'Layout_Privacy_Policy' - BlazeLayout.render 'cmsPage' - -FlowRouter.route '/room-not-found/:type/:name', - name: 'room-not-found' - - action: (params) -> - Session.set 'roomNotFound', {type: params.type, name: params.name} - BlazeLayout.render 'main', {center: 'roomNotFound'} - -FlowRouter.route '/fxos', - name: 'firefox-os-install' - - action: -> - BlazeLayout.render 'fxOsInstallPrompt' - -FlowRouter.route '/register/:hash', - name: 'register-secret-url' - action: (params) -> - BlazeLayout.render 'secretURL' - - # if RocketChat.settings.get('Accounts_RegistrationForm') is 'Secret URL' - # Meteor.call 'checkRegistrationSecretURL', params.hash, (err, success) -> - # if success - # Session.set 'loginDefaultState', 'register' - # BlazeLayout.render 'main', {center: 'home'} - # KonchatNotification.getDesktopPermission() - # else - # BlazeLayout.render 'logoLayout', { render: 'invalidSecretURL' } - # else - # BlazeLayout.render 'logoLayout', { render: 'invalidSecretURL' } diff --git a/client/routes/router.js b/client/routes/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d2ee08c861d2d437af6d129d20a627675af56661 --- /dev/null +++ b/client/routes/router.js @@ -0,0 +1,160 @@ +/* globals KonchatNotification */ + +Blaze.registerHelper('pathFor', function(path, kw) { + return FlowRouter.path(path, kw.hash); +}); + +BlazeLayout.setRoot('body'); + +FlowRouter.subscriptions = function() { + Tracker.autorun(() => { + if (Meteor.userId()) { + this.register('userData', Meteor.subscribe('userData')); + this.register('activeUsers', Meteor.subscribe('activeUsers')); + } + }); +}; + + +FlowRouter.route('/', { + name: 'index', + action() { + BlazeLayout.render('main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' }); + if (!Meteor.userId()) { + return FlowRouter.go('home'); + } + + Tracker.autorun(function(c) { + if (FlowRouter.subsReady() === true) { + Meteor.defer(function() { + if (Meteor.user().defaultRoom) { + const room = Meteor.user().defaultRoom.split('/'); + FlowRouter.go(room[0], { name: room[1] }, FlowRouter.current().queryParams); + } else { + FlowRouter.go('home'); + } + }); + c.stop(); + } + }); + } +}); + + +FlowRouter.route('/login', { + name: 'login', + + action() { + FlowRouter.go('home'); + } +}); + +FlowRouter.route('/home', { + name: 'home', + + action(params, queryParams) { + RocketChat.TabBar.showGroup('home'); + KonchatNotification.getDesktopPermission(); + if (queryParams.saml_idp_credentialToken !== undefined) { + Accounts.callLoginMethod({ + methodArguments: [{ + saml: true, + credentialToken: queryParams.saml_idp_credentialToken + }], + userCallback: function() { BlazeLayout.render('main', {center: 'home'}); } + }); + } else { + BlazeLayout.render('main', {center: 'home'}); + } + } +}); + +FlowRouter.route('/changeavatar', { + name: 'changeAvatar', + + action() { + RocketChat.TabBar.showGroup('changeavatar'); + BlazeLayout.render('main', {center: 'avatarPrompt'}); + } +}); + +FlowRouter.route('/account/:group?', { + name: 'account', + + action(params) { + if (!params.group) { + params.group = 'Preferences'; + } + params.group = _.capitalize(params.group, true); + RocketChat.TabBar.showGroup('account'); + BlazeLayout.render('main', { center: `account${params.group}` }); + } +}); + +FlowRouter.route('/history/private', { + name: 'privateHistory', + + subscriptions(/*params, queryParams*/) { + this.register('privateHistory', Meteor.subscribe('privateHistory')); + }, + + action() { + Session.setDefault('historyFilter', ''); + RocketChat.TabBar.showGroup('private-history'); + BlazeLayout.render('main', {center: 'privateHistory'}); + } +}); + +FlowRouter.route('/terms-of-service', { + name: 'terms-of-service', + + action() { + Session.set('cmsPage', 'Layout_Terms_of_Service'); + BlazeLayout.render('cmsPage'); + } +}); + +FlowRouter.route('/privacy-policy', { + name: 'privacy-policy', + + action() { + Session.set('cmsPage', 'Layout_Privacy_Policy'); + BlazeLayout.render('cmsPage'); + } +}); + +FlowRouter.route('/room-not-found/:type/:name', { + name: 'room-not-found', + + action(params) { + Session.set('roomNotFound', {type: params.type, name: params.name}); + BlazeLayout.render('main', {center: 'roomNotFound'}); + } +}); + +FlowRouter.route('/fxos', { + name: 'firefox-os-install', + + action() { + BlazeLayout.render('fxOsInstallPrompt'); + } +}); + +FlowRouter.route('/register/:hash', { + name: 'register-secret-url', + + action(/*params*/) { + BlazeLayout.render('secretURL'); + + // if RocketChat.settings.get('Accounts_RegistrationForm') is 'Secret URL' + // Meteor.call 'checkRegistrationSecretURL', params.hash, (err, success) -> + // if success + // Session.set 'loginDefaultState', 'register' + // BlazeLayout.render 'main', {center: 'home'} + // KonchatNotification.getDesktopPermission() + // else + // BlazeLayout.render 'logoLayout', { render: 'invalidSecretURL' } + // else + // BlazeLayout.render 'logoLayout', { render: 'invalidSecretURL' } + } +}); diff --git a/client/startup/roomObserve.coffee b/client/startup/roomObserve.coffee deleted file mode 100644 index 2cbee3b767af57aeaa249a8536d11be8812ed433..0000000000000000000000000000000000000000 --- a/client/startup/roomObserve.coffee +++ /dev/null @@ -1,8 +0,0 @@ -Meteor.startup -> - ChatRoom.find().observe - added: (data) -> - Session.set('roomData' + data._id, data) - changed: (data) -> - Session.set('roomData' + data._id, data) - removed: (data) -> - Session.set('roomData' + data._id, undefined) diff --git a/client/startup/roomObserve.js b/client/startup/roomObserve.js new file mode 100644 index 0000000000000000000000000000000000000000..ea3222a1fc6ed85f0d188e90e0c225739be85b20 --- /dev/null +++ b/client/startup/roomObserve.js @@ -0,0 +1,13 @@ +Meteor.startup(function() { + ChatRoom.find().observe({ + added(data) { + Session.set('roomData' + data._id, data); + }, + changed(data) { + Session.set('roomData' + data._id, data); + }, + removed(data) { + Session.set('roomData' + data._id, undefined); + } + }); +}); diff --git a/client/startup/startup.coffee b/client/startup/startup.coffee deleted file mode 100644 index c7c0a16986160b855100cff2cf2919683078e8b2..0000000000000000000000000000000000000000 --- a/client/startup/startup.coffee +++ /dev/null @@ -1,69 +0,0 @@ -import moment from 'moment' - -Meteor.startup -> - TimeSync.loggingEnabled = false - - UserPresence.awayTime = 300000 - UserPresence.start() - Meteor.subscribe("activeUsers") - - Session.setDefault('AvatarRandom', 0) - - window.lastMessageWindow = {} - window.lastMessageWindowHistory = {} - - TAPi18n.conf.i18n_files_route = Meteor._relativeToSiteRootUrl('/tap-i18n') - - @defaultAppLanguage = -> - lng = window.navigator.userLanguage || window.navigator.language || 'en' - # Fix browsers having all-lowercase language settings eg. pt-br, en-us - re = /([a-z]{2}-)([a-z]{2})/ - if re.test lng - lng = lng.replace re, (match, parts...) -> return parts[0] + parts[1].toUpperCase() - return lng - - @defaultUserLanguage = -> - return RocketChat.settings.get('Language') || defaultAppLanguage() - - loadedLanguages = [] - - @setLanguage = (language) -> - if !language - return - - if loadedLanguages.indexOf(language) > -1 - return - - loadedLanguages.push language - - if isRtl language - $('html').addClass "rtl" - else - $('html').removeClass "rtl" - - language = language.split('-').shift() - TAPi18n.setLanguage(language) - - language = language.toLowerCase() - if language isnt 'en' - Meteor.call 'loadLocale', language, (err, localeFn) -> - Function(localeFn).call({moment: moment}); - moment.locale(language) - - Meteor.subscribe("userData", () -> - userLanguage = Meteor.user()?.language - userLanguage ?= defaultUserLanguage() - - if localStorage.getItem('userLanguage') isnt userLanguage - localStorage.setItem('userLanguage', userLanguage) - - setLanguage userLanguage - - status = undefined - Tracker.autorun -> - return if not Meteor.userId() - - if Meteor.user()?.status isnt status - status = Meteor.user().status - fireGlobalEvent('status-changed', status) - ) diff --git a/client/startup/startup.js b/client/startup/startup.js new file mode 100644 index 0000000000000000000000000000000000000000..e2356a29bc03800622d4075c45eb2584bdb46239 --- /dev/null +++ b/client/startup/startup.js @@ -0,0 +1,95 @@ +/* globals UserPresence, fireGlobalEvent, isRtl */ + +import moment from 'moment'; +import toastr from 'toastr'; + +if (window.DISABLE_ANIMATION) { + toastr.options.timeOut = 1; + toastr.options.showDuration = 0; + toastr.options.hideDuration = 0; + toastr.options.extendedTimeOut = 0; +} + +Meteor.startup(function() { + TimeSync.loggingEnabled = false; + + UserPresence.awayTime = 300000; + UserPresence.start(); + Meteor.subscribe('activeUsers'); + + Session.setDefault('AvatarRandom', 0); + + window.lastMessageWindow = {}; + window.lastMessageWindowHistory = {}; + + TAPi18n.conf.i18n_files_route = Meteor._relativeToSiteRootUrl('/tap-i18n'); + + const defaultAppLanguage = function() { + let lng = window.navigator.userLanguage || window.navigator.language || 'en'; + // Fix browsers having all-lowercase language settings eg. pt-br, en-us + const re = /([a-z]{2}-)([a-z]{2})/; + if (re.test(lng)) { + lng = lng.replace(re, (match, ...parts) => { + return parts[0] + parts[1].toUpperCase(); + }); + } + return lng; + }; + + window.defaultUserLanguage = function() { + return RocketChat.settings.get('Language') || defaultAppLanguage(); + }; + + const loadedLanguages = []; + + window.setLanguage = function(language) { + if (!language) { + return; + } + + if (loadedLanguages.indexOf(language) > -1) { + return; + } + + loadedLanguages.push(language); + + if (isRtl(language)) { + $('html').addClass('rtl'); + } else { + $('html').removeClass('rtl'); + } + + language = language.split('-').shift(); + TAPi18n.setLanguage(language); + + language = language.toLowerCase(); + if (language !== 'en') { + Meteor.call('loadLocale', language, (err, localeFn) => { + Function(localeFn).call({moment: moment}); + moment.locale(language); + }); + } + }; + + Meteor.subscribe('userData', function() { + const userLanguage = Meteor.user() ? Meteor.user().language : window.defaultUserLanguage(); + + if (localStorage.getItem('userLanguage') !== userLanguage) { + localStorage.setItem('userLanguage', userLanguage); + } + + window.setLanguage(userLanguage); + + let status = undefined; + Tracker.autorun(function() { + if (!Meteor.userId()) { + return; + } + + if (Meteor.user() && Meteor.user().status !== status) { + status = Meteor.user().status; + fireGlobalEvent('status-changed', status); + } + }); + }); +}); diff --git a/client/startup/unread.coffee b/client/startup/unread.coffee deleted file mode 100644 index cca1fd734559360193de6d15596ea104c1588033..0000000000000000000000000000000000000000 --- a/client/startup/unread.coffee +++ /dev/null @@ -1,62 +0,0 @@ -Meteor.startup -> - - Tracker.autorun -> - - unreadCount = 0 - unreadAlert = false - - subscriptions = ChatSubscription.find({open: true}, { fields: { unread: 1, alert: 1, rid: 1, t: 1, name: 1, ls: 1, unreadAlert: 1 } }) - - openedRoomId = undefined - Tracker.nonreactive -> - if FlowRouter.getRouteName() in ['channel', 'group', 'direct'] - openedRoomId = Session.get 'openedRoom' - - for subscription in subscriptions.fetch() - fireGlobalEvent 'unread-changed-by-subscription', subscription - - if subscription.alert or subscription.unread > 0 - # This logic is duplicated in /client/notifications/notification.coffee. - hasFocus = readMessage.isEnable() - subscriptionIsTheOpenedRoom = openedRoomId is subscription.rid - if hasFocus and subscriptionIsTheOpenedRoom - # The user has probably read all messages in this room. - # TODO: readNow() should return whether it has actually marked the room as read. - Meteor.setTimeout -> - readMessage.readNow() - , 500 - - # Increment the total unread count. - unreadCount += subscription.unread - if subscription.alert is true and subscription.unreadAlert isnt 'nothing' - if subscription.unreadAlert == 'all' or Meteor.user()?.settings?.preferences?.unreadAlert isnt false - unreadAlert = '•' - - if RoomManager.openedRooms[subscription.t + subscription.name] - readMessage.refreshUnreadMark(subscription.rid) - - menu.updateUnreadBars() - - if unreadCount > 0 - if unreadCount > 999 - Session.set 'unread', '999+' - else - Session.set 'unread', unreadCount - else if unreadAlert isnt false - Session.set 'unread', unreadAlert - else - Session.set 'unread', '' - -Meteor.startup -> - - window.favico = new Favico - position: 'up' - animation: 'none' - - Tracker.autorun -> - siteName = RocketChat.settings.get('Site_Name') or '' - - unread = Session.get 'unread' - fireGlobalEvent 'unread-changed', unread - favico?.badge unread, bgColor: if typeof unread isnt 'number' then '#3d8a3a' else '#ac1b1b' - document.title = if unread == '' then siteName else '(' + unread + ') '+ siteName diff --git a/client/startup/unread.js b/client/startup/unread.js new file mode 100644 index 0000000000000000000000000000000000000000..c72b87e7bc9063003beedc55a6254f50d0c6cea7 --- /dev/null +++ b/client/startup/unread.js @@ -0,0 +1,83 @@ +/* globals fireGlobalEvent, readMessage, RoomManager, Favico, favico, menu */ + +Meteor.startup(function() { + Tracker.autorun(function() { + let unreadCount = 0; + let unreadAlert = false; + + const subscriptions = ChatSubscription.find({open: true}, { fields: { unread: 1, alert: 1, rid: 1, t: 1, name: 1, ls: 1, unreadAlert: 1 } }); + + let openedRoomId = undefined; + Tracker.nonreactive(function() { + if (['channel', 'group', 'direct'].includes(FlowRouter.getRouteName())) { + openedRoomId = Session.get('openedRoom'); + } + }); + + for (let subscription of subscriptions.fetch()) { + fireGlobalEvent('unread-changed-by-subscription', subscription); + + if (subscription.alert || subscription.unread > 0) { + // This logic is duplicated in /client/notifications/notification.coffee. + const hasFocus = readMessage.isEnable(); + const subscriptionIsTheOpenedRoom = openedRoomId === subscription.rid; + if (hasFocus && subscriptionIsTheOpenedRoom) { + // The user has probably read all messages in this room. + // TODO: readNow() should return whether it has actually marked the room as read. + Meteor.setTimeout(function() { + readMessage.readNow(); + }, 500); + } + + // Increment the total unread count. + unreadCount += subscription.unread; + if (subscription.alert === true && subscription.unreadAlert !== 'nothing') { + const userUnreadAlert = Meteor.user() && Meteor.user().settings && Meteor.user().settings.preferences && Meteor.user().settings.preferences.unreadAlert; + if (subscription.unreadAlert === 'all' || userUnreadAlert !== false) { + unreadAlert = '•'; + } + } + } + + if (RoomManager.openedRooms[subscription.t + subscription.name]) { + readMessage.refreshUnreadMark(subscription.rid); + } + } + + menu.updateUnreadBars(); + + if (unreadCount > 0) { + if (unreadCount > 999) { + Session.set('unread', '999+'); + } else { + Session.set('unread', unreadCount); + } + } else if (unreadAlert !== false) { + Session.set('unread', unreadAlert); + } else { + Session.set('unread', ''); + } + }); +}); + +Meteor.startup(function() { + window.favico = new Favico({ + position: 'up', + animation: 'none' + }); + + Tracker.autorun(function() { + const siteName = RocketChat.settings.get('Site_Name') || ''; + + const unread = Session.get('unread'); + fireGlobalEvent('unread-changed', unread); + + if (favico) { + favico.badge(unread, { + bgColor: typeof unread !== 'number' ? '#3d8a3a' : '#ac1b1b' + }); + } + + document.title = unread === '' ? siteName : `(${unread}) ${siteName}`; + }); +}); diff --git a/client/startup/usersObserve.coffee b/client/startup/usersObserve.coffee deleted file mode 100644 index 4c5eccc4808754001aba39775556cdbc66fbc087..0000000000000000000000000000000000000000 --- a/client/startup/usersObserve.coffee +++ /dev/null @@ -1,11 +0,0 @@ -Meteor.startup -> - Meteor.users.find({}, { fields: { name: 1, username: 1, pictures: 1, status: 1, emails: 1, phone: 1, services: 1, utcOffset: 1 } }).observe - added: (user) -> - Session.set('user_' + user.username + '_status', user.status) - RoomManager.updateUserStatus user, user.status, user.utcOffset - changed: (user) -> - Session.set('user_' + user.username + '_status', user.status) - RoomManager.updateUserStatus user, user.status, user.utcOffset - removed: (user) -> - Session.set('user_' + user.username + '_status', null) - RoomManager.updateUserStatus user, 'offline', null diff --git a/client/startup/usersObserve.js b/client/startup/usersObserve.js new file mode 100644 index 0000000000000000000000000000000000000000..bb65f60304a739807f5c54f90f40d49be8717528 --- /dev/null +++ b/client/startup/usersObserve.js @@ -0,0 +1,18 @@ +/* globals RoomManager */ + +Meteor.startup(function() { + Meteor.users.find({}, { fields: { name: 1, username: 1, pictures: 1, status: 1, emails: 1, phone: 1, services: 1, utcOffset: 1 } }).observe({ + added(user) { + Session.set('user_' + user.username + '_status', user.status); + RoomManager.updateUserStatus(user, user.status, user.utcOffset); + }, + changed(user) { + Session.set('user_' + user.username + '_status', user.status); + RoomManager.updateUserStatus(user, user.status, user.utcOffset); + }, + removed(user) { + Session.set('user_' + user.username + '_status', null); + RoomManager.updateUserStatus(user, 'offline', null); + } + }); +}); diff --git a/docker-compose.yml b/docker-compose.yml index 22d773e362c44725caeccebc9580e00bd0df9e81..44c6f8384c081bf31478040918278e592696fd87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,64 +1,73 @@ -rocketchat: - image: rocketchat/rocket.chat:latest - volumes: - - ./uploads:/app/uploads - environment: - - PORT=3000 - - ROOT_URL=http://localhost:3000 - - MONGO_URL=mongodb://mongo:27017/rocketchat - - MONGO_OPLOG_URL=mongodb://mongo:27017/local - - MAIL_URL=smtp://smtp.email - - HTTP_PROXY=http://proxy.domain.com - - HTTPS_PROXY=http://proxy.domain.com - links: - - mongo:mongo - ports: - - 3000:3000 - labels: - - "traefik.backend=rocketchat" - - "traefik.frontend.rule=Host: your.domain.tld" +version: '2' -mongo: - image: mongo:3.2 - volumes: - - ./data/db:/data/db -# - ./data/dump:/dump - command: mongod --smallfiles --oplogSize 128 --replSet rs0 - labels: - - "traefik.enable=false" +services: + rocketchat: + image: rocketchat/rocket.chat:latest + restart: unless-stopped + volumes: + - ./uploads:/app/uploads + environment: + - PORT=3000 + - ROOT_URL=http://localhost:3000 + - MONGO_URL=mongodb://mongo:27017/rocketchat + - MONGO_OPLOG_URL=mongodb://mongo:27017/local + - MAIL_URL=smtp://smtp.email + - HTTP_PROXY=http://proxy.domain.com + - HTTPS_PROXY=http://proxy.domain.com + depends_on: + - mongo + ports: + - 3000:3000 + labels: + - "traefik.backend=rocketchat" + - "traefik.frontend.rule=Host: your.domain.tld" -mongo-init-replica: - image: mongo:3.2 - command: 'mongo mongo/rocketchat --eval "rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})"' - links: - - mongo:mongo + mongo: + image: mongo:3.2 + restart: unless-stopped + volumes: + - ./data/db:/data/db + #- ./data/dump:/dump + command: mongod --smallfiles --oplogSize 128 --replSet rs0 + labels: + - "traefik.enable=false" -# hubot, the popular chatbot (add the bot user first and change the password before starting this image) -hubot: - image: rocketchat/hubot-rocketchat:latest - environment: - - ROCKETCHAT_URL=rocketchat:3000 - - ROCKETCHAT_ROOM=GENERAL - - ROCKETCHAT_USER=bot - - ROCKETCHAT_PASSWORD=botpassword - - BOT_NAME=bot -# you can add more scripts as you'd like here, they need to be installable by npm - - EXTERNAL_SCRIPTS=hubot-help,hubot-seen,hubot-links,hubot-diagnostics - links: - - rocketchat:rocketchat - labels: - - "traefik.enable=false" - volumes: - - ./scripts:/home/hubot/scripts -# this is used to expose the hubot port for notifications on the host on port 3001, e.g. for hubot-jenkins-notifier - ports: - - 3001:8080 + # this container's job is just run the command to initialize the replica set. + # it will run the command and remove himself (it will not stay running) + mongo-init-replica: + image: mongo:3.2 + command: 'mongo mongo/rocketchat --eval "rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})"' + depends_on: + - mongo -#traefik: -# image: traefik:latest -# command: traefik --docker --acme=true --acme.domains='your.domain.tld' --acme.email='your@email.tld' --acme.entrypoint=https --acme.storagefile=acme.json --defaultentrypoints=http --defaultentrypoints=https --entryPoints='Name:http Address::80 Redirect.EntryPoint:https' --entryPoints='Name:https Address::443 TLS.Certificates:' -# ports: -# - 80:80 -# - 443:443 -# volumes: -# - /var/run/docker.sock:/var/run/docker.sock + # hubot, the popular chatbot (add the bot user first and change the password before starting this image) + hubot: + image: rocketchat/hubot-rocketchat:latest + restart: unless-stopped + environment: + - ROCKETCHAT_URL=rocketchat:3000 + - ROCKETCHAT_ROOM=GENERAL + - ROCKETCHAT_USER=bot + - ROCKETCHAT_PASSWORD=botpassword + - BOT_NAME=bot + # you can add more scripts as you'd like here, they need to be installable by npm + - EXTERNAL_SCRIPTS=hubot-help,hubot-seen,hubot-links,hubot-diagnostics + depends_on: + - rocketchat + labels: + - "traefik.enable=false" + volumes: + - ./scripts:/home/hubot/scripts + # this is used to expose the hubot port for notifications on the host on port 3001, e.g. for hubot-jenkins-notifier + ports: + - 3001:8080 + + #traefik: + # image: traefik:latest + # restart: unless-stopped + # command: traefik --docker --acme=true --acme.domains='your.domain.tld' --acme.email='your@email.tld' --acme.entrypoint=https --acme.storagefile=acme.json --defaultentrypoints=http --defaultentrypoints=https --entryPoints='Name:http Address::80 Redirect.EntryPoint:https' --entryPoints='Name:https Address::443 TLS.Certificates:' + # ports: + # - 80:80 + # - 443:443 + # volumes: + # - /var/run/docker.sock:/var/run/docker.sock diff --git a/lib/RegExp.coffee b/lib/RegExp.coffee deleted file mode 100644 index c068aefc58d9ccc7e3229b8ca064d15c351bb762..0000000000000000000000000000000000000000 --- a/lib/RegExp.coffee +++ /dev/null @@ -1,2 +0,0 @@ -RegExp.escape = (s) -> - return s.replace /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' \ No newline at end of file diff --git a/lib/RegExp.js b/lib/RegExp.js new file mode 100644 index 0000000000000000000000000000000000000000..60d252508d3d68367f8ae37a4d674a690569766a --- /dev/null +++ b/lib/RegExp.js @@ -0,0 +1,3 @@ +RegExp.escape = function(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +}; diff --git a/lib/fileUpload.coffee b/lib/fileUpload.coffee deleted file mode 100644 index ee0f2dc75046e72e0b28570bf8c78ac7ac600f91..0000000000000000000000000000000000000000 --- a/lib/fileUpload.coffee +++ /dev/null @@ -1,69 +0,0 @@ -if UploadFS? - RocketChat.models.Uploads.allow - insert: (userId, doc) -> - return userId - - update: (userId, doc) -> - return userId is doc.userId - - remove: (userId, doc) -> - return userId is doc.userId - - initFileStore = -> - cookie = new Cookies() - if Meteor.isClient - document.cookie = 'rc_uid=' + escape(Meteor.userId()) + '; path=/' - document.cookie = 'rc_token=' + escape(Accounts._storedLoginToken()) + '; path=/' - - Meteor.fileStore = new UploadFS.store.GridFS - collection: RocketChat.models.Uploads.model - name: 'rocketchat_uploads' - collectionName: 'rocketchat_uploads' - filter: new UploadFS.Filter - onCheck: FileUpload.validateFileUpload - transformWrite: (readStream, writeStream, fileId, file) -> - if RocketChatFile.enabled is false or not /^image\/.+/.test(file.type) - return readStream.pipe writeStream - - stream = undefined - - identify = (err, data) -> - if err? - return stream.pipe writeStream - - file.identify = - format: data.format - size: data.size - - if data.Orientation? and data.Orientation not in ['', 'Unknown', 'Undefined'] - RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream) - else - stream.pipe writeStream - - stream = RocketChatFile.gm(readStream).identify(identify).stream() - - onRead: (fileId, file, req, res) -> - if RocketChat.settings.get 'FileUpload_ProtectFiles' - rawCookies = req.headers.cookie if req?.headers?.cookie? - uid = cookie.get('rc_uid', rawCookies) if rawCookies? - token = cookie.get('rc_token', rawCookies) if rawCookies? - - if not uid? - uid = req.query.rc_uid - token = req.query.rc_token - - unless uid and token and RocketChat.models.Users.findOneByIdAndLoginToken(uid, token) - res.writeHead 403 - return false - - res.setHeader 'content-disposition', "attachment; filename=\"#{ encodeURIComponent(file.name) }\"" - return true - - Meteor.startup -> - if Meteor.isServer - initFileStore() - else - Tracker.autorun (c) -> - if Meteor.userId() and RocketChat.settings.cachedCollection.ready.get() - initFileStore() - c.stop() diff --git a/lib/fileUpload.js b/lib/fileUpload.js new file mode 100644 index 0000000000000000000000000000000000000000..e4503669686624f356b4849cc567975f29641bad --- /dev/null +++ b/lib/fileUpload.js @@ -0,0 +1,87 @@ +/* globals UploadFS, Cookies, FileUpload */ + +if (UploadFS) { + const initFileStore = function() { + const cookie = new Cookies(); + if (Meteor.isClient) { + document.cookie = 'rc_uid=' + escape(Meteor.userId()) + '; path=/'; + document.cookie = 'rc_token=' + escape(Accounts._storedLoginToken()) + '; path=/'; + } + + Meteor.fileStore = new UploadFS.store.GridFS({ + collection: RocketChat.models.Uploads.model, + name: 'rocketchat_uploads', + collectionName: 'rocketchat_uploads', + filter: new UploadFS.Filter({ + onCheck: FileUpload.validateFileUpload + }), + transformWrite(readStream, writeStream, fileId, file) { + if (RocketChatFile.enabled === false || !/^image\/.+/.test(file.type)) { + return readStream.pipe(writeStream); + } + + let stream = undefined; + + const identify = function(err, data) { + if (err) { + return stream.pipe(writeStream); + } + + file.identify = { + format: data.format, + size: data.size + }; + + if (data.Orientation && !['', 'Unknown', 'Undefined'].includes(data.Orientation)) { + RocketChatFile.gm(stream).autoOrient().stream().pipe(writeStream); + } else { + stream.pipe(writeStream); + } + }; + + stream = RocketChatFile.gm(readStream).identify(identify).stream(); + }, + + onRead(fileId, file, req, res) { + if (RocketChat.settings.get('FileUpload_ProtectFiles')) { + let uid, token; + + if (req && req.headers && req.headers.cookie) { + const rawCookies = req.headers.cookie; + + if (rawCookies) { + uid = cookie.get('rc_uid', rawCookies) ; + token = cookie.get('rc_token', rawCookies); + } + } + + if (!uid) { + uid = req.query.rc_uid; + token = req.query.rc_token; + } + + if (!uid || !token || !RocketChat.models.Users.findOneByIdAndLoginToken(uid, token)) { + res.writeHead(403); + return false; + } + } + + res.setHeader('content-disposition', `attachment; filename="${ encodeURIComponent(file.name) }"`); + return true; + } + }); + }; + + Meteor.startup(function() { + if (Meteor.isServer) { + initFileStore(); + } else { + Tracker.autorun(function(c) { + if (Meteor.userId() && RocketChat.settings.cachedCollection.ready.get()) { + initFileStore(); + c.stop(); + } + }); + } + }); +} diff --git a/lib/francocatena_fix.coffee b/lib/francocatena_fix.coffee deleted file mode 100644 index a2ef678fb65b074b0af12cf5a7596260fa475d78..0000000000000000000000000000000000000000 --- a/lib/francocatena_fix.coffee +++ /dev/null @@ -1,2 +0,0 @@ -@i18n_status_func = (key,options) -> - return TAPi18n.__(key,options) diff --git a/lib/francocatena_fix.js b/lib/francocatena_fix.js new file mode 100644 index 0000000000000000000000000000000000000000..12125e91853975bc793ba249edb0fd3e61ef0fff --- /dev/null +++ b/lib/francocatena_fix.js @@ -0,0 +1,3 @@ +this.i18n_status_func = function(key, options) { + return TAPi18n.__(key, options); +}; diff --git a/lib/underscore.string.coffee b/lib/underscore.string.coffee deleted file mode 100644 index f0845a77574636b0103f03e14383aaa7084a6570..0000000000000000000000000000000000000000 --- a/lib/underscore.string.coffee +++ /dev/null @@ -1,15 +0,0 @@ -# This will add underscore.string methods to Underscore.js -# except for include, contains, reverse and join that are -# dropped because they collide with the functions already -# defined by Underscore.js. - -mixin = (obj) -> - _.each _.functions(obj), (name) -> - if not _[name] and not _.prototype[name]? - func = _[name] = obj[name] - _.prototype[name] = -> - args = [this._wrapped] - push.apply(args, arguments) - return result.call(this, func.apply(_, args)) - -mixin(s.exports()) \ No newline at end of file diff --git a/lib/underscore.string.js b/lib/underscore.string.js new file mode 100644 index 0000000000000000000000000000000000000000..f92095c585c7b05a287a280b8e7a3170529422ee --- /dev/null +++ b/lib/underscore.string.js @@ -0,0 +1,16 @@ +/* globals mixin */ + +// This will add underscore.string methods to Underscore.js +// except for include, contains, reverse and join that are +// dropped because they collide with the functions already +// defined by Underscore.js. + +mixin = function(obj) { + _.each(_.functions(obj), function(name) { + if (!_[name] && !_.prototype[name]) { + _[name] = obj[name]; + } + }); +}; + +mixin(s.exports()); diff --git a/package.json b/package.json index 1a42814e6956d6c0030a25061f821d5858b657a6..930626c966813252eedabcecc5e2c8cec20856e5 100644 --- a/package.json +++ b/package.json @@ -1,72 +1,79 @@ { - "name": "Rocket.Chat", - "description": "The Ultimate Open Source WebChat Platform", - "version": "0.47.0-develop", - "author": { - "name": "Rocket.Chat", - "url": "https://rocket.chat/" - }, - "contributors": [ - { - "name": "Aaron Ogle", - "email": "aaron.ogle@rocket.chat" - }, - { - "name": "Bradley Hilton", - "email": "bradley.hilton@rocket.chat" - }, - { - "name": "Diego Sampaio", - "email": "diego.sampaio@rocket.chat" - }, - { - "name": "Gabriel Engel", - "email": "gabriel.engel@rocket.chat" - }, - { - "name": "Marcelo Schmidt", - "email": "marcelo.schmidt@rocket.chat" - }, - { - "name": "Rodrigo Nascimento", - "email": "rodrigo.nascimento@rocket.chat" - }, - { - "name": "Sing Li", - "email": "sing.li@rocket.chat" - } - ], - "keywords": [ - "rocketchat", - "rocket", - "chat" - ], - "scripts": { - "start": "meteor npm i && meteor", - "lint": "eslint .", - "deploy": "npm run build && pm2 startOrRestart pm2.json", - "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests", - "chimp-test": "chimp --mocha --path=tests --mochaSlow=0" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/RocketChat/Rocket.Chat.git" - }, - "bugs": { - "url": "https://github.com/RocketChat/Rocket.Chat/issues", - "email": "support@rocket.chat" - }, - "devDependencies": { - "chimp": "^0.42.0", - "eslint": "^3.10.2" - }, - "dependencies": { - "babel-runtime": "^6.18.0", - "bcrypt": "^0.8.7", - "moment": "^2.16.0", - "moment-timezone": "^0.5.9", - "jquery": "^2.1.0", - "toastr": "^2.1.2" - } + "name": "Rocket.Chat", + "description": "The Ultimate Open Source WebChat Platform", + "version": "0.50.0-develop", + "author": { + "name": "Rocket.Chat", + "url": "https://rocket.chat/" + }, + "contributors": [ + { + "name": "Aaron Ogle", + "email": "aaron.ogle@rocket.chat" + }, + { + "name": "Bradley Hilton", + "email": "bradley.hilton@rocket.chat" + }, + { + "name": "Diego Sampaio", + "email": "diego.sampaio@rocket.chat" + }, + { + "name": "Gabriel Engel", + "email": "gabriel.engel@rocket.chat" + }, + { + "name": "Marcelo Schmidt", + "email": "marcelo.schmidt@rocket.chat" + }, + { + "name": "Rodrigo Nascimento", + "email": "rodrigo.nascimento@rocket.chat" + }, + { + "name": "Sing Li", + "email": "sing.li@rocket.chat" + } + ], + "keywords": [ + "rocketchat", + "rocket", + "chat" + ], + "scripts": { + "start": "meteor npm i && meteor", + "lint": "eslint .", + "stylelint": "stylelint **/*.less", + "test": "node .scripts/start.js", + "deploy": "npm run build && pm2 startOrRestart pm2.json", + "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests/steps", + "chimp-test": "chimp tests/chimp-config.js" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/RocketChat/Rocket.Chat.git" + }, + "bugs": { + "url": "https://github.com/RocketChat/Rocket.Chat/issues", + "email": "support@rocket.chat" + }, + "devDependencies": { + "chimp": "^0.45.1", + "eslint": "^3.13.1", + "stylelint": "^7.7.1", + "supertest": "^2.0.1" + }, + "dependencies": { + "jquery": "^2.1.0", + "babel-runtime": "^6.20.0", + "bcrypt": "^1.0.2", + "moment": "^2.17.1", + "moment-timezone": "^0.5.11", + "toastr": "^2.1.2", + "mime-types": "2.1.13", + "file-type": "4.0.0", + "codemirror": "5.22.0" + } } diff --git a/packages/meteor-accounts-saml/CHANGELOG.md b/packages/meteor-accounts-saml/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..c9c2caa094a58a229758916266b36faabf240c52 --- /dev/null +++ b/packages/meteor-accounts-saml/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +This is a changelog of changes made to the saml package for RocketChat. +The package is originally based on https://github.com/steffow/meteor-accounts-saml . + +## 9-Nov-2016 danb@catalyst-au.net + +* Only do `logoutWithSaml` if we are doing single logout (ie + `idpSLORedirectURL` is set). We get an error if we do + `logoutWithSaml` and `idpSLORedirectURL` is not set. This was + discovered when testing with simplesamlphp. In this latter case, + `logoutWithSaml` will try to use the standard saml endpoint to + request the logout resulting in an error. +* **TODO:** doing the standard `Meteor.logout` doesn't seem to be + enough when logging out. If the user logs back into RocketChat from + the same window, they will be automatically logged back in to + RocketChat, no credentials required. If you enable single logout + (ie set `idpSLORedirectURL`), then `logoutWithSaml` will be called + and the client should be properly logged out. +* Switch to using `Cookies` for the client when recording the saml + provider and whether single logout is enabled. If the client has + multiple rocketchat tabs or windows, this will ensure they all + log out properly with the idp - but only when single logout + (`idpSLORedirectURL`) is enabled. + +## 31-Oct-2016 danb@catalyst-au.net + +* Attempt at SP-initiated logout (**requires more work**). + * Added configuration setting for SLO (single logout) redirect url + to the IDP. + * A `Session` key is set called `saml_provider`; it is unset at + logout. Is there a better way? + * `Meteor.logout` on the client is overridden so that + `Meteor.logoutWithSaml` is called if `saml_provider` is set. This + isn't great - is there a better way? + * Tested using simplesamlphp as the idp + * login in using saml + * then logout using the normal RocketChat logout + * after some time, you should see the RocketChat login screen + * if you try to login again, you will have to login to the idp again + repeating the previous login flow + * **TODO:** If a user has 2 independent browser login sessions using + saml, both will log out, but only the one the user logged out from + appears to be properly logged out of the idp. + * this might be because we shouldn't be logging out the second session + but RocketChat / meteor is doing this. + +## 17-Oct-2016 + +* Fixed signing for redirect to idp. + * At login time, if the idp is configured to verify the redirect to its login screen, it will + expect `RelayState` as part of the signature. + Tested with **simplesamlphp** with `'redirect.validate' => TRUE` in the `sp-remote` metadata file + * Added `privateKey` - `SAML_Custom_Private_Key` + * Added `privateCert` - `SAML_Custom_Public_Cert` + * These input fields should point to locations on the the server eg `/path/to/private/certs/`. + * `privateKey` is used by RocketChat to sign saml requests. diff --git a/packages/meteor-accounts-saml/saml_client.js b/packages/meteor-accounts-saml/saml_client.js index 35bbe93151b1d098eb5460e141220c0b7ec03bef..325f84909e357e868eaf3d5f3c7c2fc46d3edfe0 100644 --- a/packages/meteor-accounts-saml/saml_client.js +++ b/packages/meteor-accounts-saml/saml_client.js @@ -4,6 +4,30 @@ if (!Accounts.saml) { Accounts.saml = {}; } +// Override the standard logout behaviour. +// +// If we find a samlProvider, and we are using single +// logout we will initiate logout from rocketchat via saml. +// If not using single logout, we just do the standard logout. +// +// TODO: This may need some work as it is not clear if we are really +// logging out of the idp when doing the standard logout. + +var MeteorLogout = Meteor.logout; + +Meteor.logout = function() { + var samlService = ServiceConfiguration.configurations.findOne({service: 'saml'}); + if (samlService) { + var provider = samlService.clientConfig && samlService.clientConfig.provider; + if (provider) { + if (samlService.idpSLORedirectURL) { + return Meteor.logoutWithSaml({ provider: provider }); + } + } + } + return MeteorLogout.apply(Meteor, arguments); +}; + var openCenteredPopup = function(url, width, height) { var newwindow; @@ -74,6 +98,7 @@ Accounts.saml.initiateLogin = function(options, callback, dimensions) { }, 100); }; + Meteor.loginWithSaml = function(options, callback) { options = options || {}; var credentialToken = Random.id(); @@ -93,7 +118,10 @@ Meteor.loginWithSaml = function(options, callback) { Meteor.logoutWithSaml = function(options/*, callback*/) { //Accounts.saml.idpInitiatedSLO(options, callback); Meteor.call('samlLogout', options.provider, function(err, result) { - console.log('LOC ' + result); + if (err || !result) { + MeteorLogout.apply(Meteor); + return; + } // A nasty bounce: 'result' has the SAML LogoutRequest but we need a proper 302 to redirected from the server. //window.location.replace(Meteor.absoluteUrl('_saml/sloRedirect/' + options.provider + '/?redirect='+result)); window.location.replace(Meteor.absoluteUrl('_saml/sloRedirect/' + options.provider + '/?redirect=' + encodeURIComponent(result))); diff --git a/packages/meteor-accounts-saml/saml_rocketchat.coffee b/packages/meteor-accounts-saml/saml_rocketchat.coffee index f5913eb3e4286264832a307dc545d33304395fe3..fbaff439b819a796c825ad2cc04dec2fa133539f 100644 --- a/packages/meteor-accounts-saml/saml_rocketchat.coffee +++ b/packages/meteor-accounts-saml/saml_rocketchat.coffee @@ -1,3 +1,5 @@ +fs = Npm.require('fs') + logger = new Logger 'steffo:meteor-accounts-saml', methods: updated: @@ -5,55 +7,107 @@ logger = new Logger 'steffo:meteor-accounts-saml', RocketChat.settings.addGroup 'SAML' Meteor.methods + + # Define configuration settings for each provider (name) in the + # admin SAML form. + addSamlService: (name) -> - RocketChat.settings.add "SAML_Custom_#{name}" , false , { type: 'boolean', group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Enable'} - RocketChat.settings.add "SAML_Custom_#{name}_provider" , 'openidp' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Provider'} - RocketChat.settings.add "SAML_Custom_#{name}_entry_point" , 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Entry_point'} - RocketChat.settings.add "SAML_Custom_#{name}_issuer" , 'https://rocket.chat/' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Issuer'} - RocketChat.settings.add "SAML_Custom_#{name}_cert" , '' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Cert'} + RocketChat.settings.add "SAML_Custom_#{name}" , false , { type: 'boolean', group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Enable'} + RocketChat.settings.add "SAML_Custom_#{name}_provider" , 'provider-name' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Provider'} + RocketChat.settings.add "SAML_Custom_#{name}_entry_point" , 'https://example.com/simplesaml/saml2/idp/SSOService.php' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Entry_point'} + RocketChat.settings.add "SAML_Custom_#{name}_idp_slo_redirect_url" , 'https://example.com/simplesaml/saml2/idp/SingleLogoutService.php', { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_IDP_SLO_Redirect_URL'} + RocketChat.settings.add "SAML_Custom_#{name}_issuer" , 'https://your-rocket-chat/_saml/metadata/provider-name' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Issuer'} + RocketChat.settings.add "SAML_Custom_#{name}_cert" , '' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Cert', multiline: true} + RocketChat.settings.add "SAML_Custom_#{name}_public_cert", '', { + type: 'string' , + group: 'SAML', + section: name, + multiline: true, + i18nLabel: 'SAML_Custom_Public_Cert' + } + RocketChat.settings.add "SAML_Custom_#{name}_private_key", '', { + type: 'string' , + group: 'SAML', + section: name, + multiline: true, + i18nLabel: 'SAML_Custom_Private_Key' + } RocketChat.settings.add "SAML_Custom_#{name}_button_label_text" , '' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text'} RocketChat.settings.add "SAML_Custom_#{name}_button_label_color", '#FFFFFF' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color'} RocketChat.settings.add "SAML_Custom_#{name}_button_color" , '#13679A' , { type: 'string' , group: 'SAML', section: name, i18nLabel: 'Accounts_OAuth_Custom_Button_Color'} RocketChat.settings.add "SAML_Custom_#{name}_generate_username" , false , { type: 'boolean', group: 'SAML', section: name, i18nLabel: 'SAML_Custom_Generate_Username'} timer = undefined + +# Find existing SAML services (idp's) in RocketChat settings and +# update the system. +# +# Updating the system includes: +# 1) Appending enabled providers to Accounts.saml.settings.providers. +# 2) Appending enabled providers to ServiceConfiguration.configurations. +# 3) Removing disabled providers from ServiceConfiguration.configurations. + updateServices = -> Meteor.clearTimeout timer if timer? - timer = Meteor.setTimeout -> services = RocketChat.settings.get(/^(SAML_Custom_)[a-z]+$/i) - Accounts.saml.settings.providers = [] - for service in services - logger.updated service.key - serviceName = 'saml' - if service.value is true - data = - buttonLabelText: RocketChat.settings.get("#{service.key}_button_label_text") - buttonLabelColor: RocketChat.settings.get("#{service.key}_button_label_color") - buttonColor: RocketChat.settings.get("#{service.key}_button_color") - clientConfig: - provider: RocketChat.settings.get("#{service.key}_provider") - - Accounts.saml.settings.generateUsername = RocketChat.settings.get("#{service.key}_generate_username") - - Accounts.saml.settings.providers.push - provider: data.clientConfig.provider - entryPoint: RocketChat.settings.get("#{service.key}_entry_point") - issuer: RocketChat.settings.get("#{service.key}_issuer") - cert: RocketChat.settings.get("#{service.key}_cert") - - ServiceConfiguration.configurations.upsert {service: serviceName.toLowerCase()}, $set: data + samlConfigs = getSamlConfigs(service) + configureSamlService(samlConfigs) + ServiceConfiguration.configurations.upsert {service: serviceName.toLowerCase()}, $set: samlConfigs else ServiceConfiguration.configurations.remove {service: serviceName.toLowerCase()} + logger.updated service.key , 2000 +# Fetch config settings from RocketChat for a given SAML service "SAML_Custom_<name>". + +getSamlConfigs = (service) -> + buttonLabelText: RocketChat.settings.get("#{service.key}_button_label_text") + buttonLabelColor: RocketChat.settings.get("#{service.key}_button_label_color") + buttonColor: RocketChat.settings.get("#{service.key}_button_color") + clientConfig: + provider: RocketChat.settings.get("#{service.key}_provider") + entryPoint: RocketChat.settings.get("#{service.key}_entry_point") + idpSLORedirectURL: RocketChat.settings.get("#{service.key}_idp_slo_redirect_url") + generateUsername: RocketChat.settings.get("#{service.key}_generate_username") + issuer: RocketChat.settings.get("#{service.key}_issuer") + secret: + privateKey: RocketChat.settings.get("#{service.key}_private_key") + publicCert: RocketChat.settings.get("#{service.key}_public_cert") + cert: RocketChat.settings.get("#{service.key}_cert") + +# Configure Meteor SAML. +# +# Get private key for signing and update Accounts.saml.settings. +# Meteor saml package uses Accounts.saml.settings. + +configureSamlService = (samlConfigs) -> + privateKey = false + privateCert = false + if samlConfigs.secret.privateKey and samlConfigs.secret.publicCert + privateKey = samlConfigs.secret.privateKey + privateCert = samlConfigs.secret.publicCert + else + if samlConfigs.secret.privateKey or samlConfigs.secret.publicCert + logger.error "You must specify both cert and key files." + privateKey = false + privateCert = false + Accounts.saml.settings.generateUsername = samlConfigs.generateUsername + Accounts.saml.settings.providers.push + provider: samlConfigs.clientConfig.provider + entryPoint: samlConfigs.entryPoint + idpSLORedirectURL: samlConfigs.idpSLORedirectURL + issuer: samlConfigs.issuer + cert: samlConfigs.secret.cert + privateCert: privateCert + privateKey: privateKey + RocketChat.settings.get /^SAML_.+/, (key, value) -> updateServices() Meteor.startup -> - if RocketChat.settings.get(/^(SAML_Custom)_[a-z]+$/i)?.length is 0 - Meteor.call 'addSamlService', 'Default' + Meteor.call 'addSamlService', 'Default' diff --git a/packages/meteor-accounts-saml/saml_server.js b/packages/meteor-accounts-saml/saml_server.js index e5d6359200d1f9abb6b77cb628449b595cc2c610..7213171015092f4541a53ee54b7c4f82cf4d7391 100644 --- a/packages/meteor-accounts-saml/saml_server.js +++ b/packages/meteor-accounts-saml/saml_server.js @@ -15,16 +15,28 @@ var fiber = Npm.require('fibers'); var connect = Npm.require('connect'); RoutePolicy.declare('/_saml/', 'network'); +/** + * Fetch SAML provider configs for given 'provider'. + */ +function getSamlProviderConfig(provider) { + if (! provider) { + throw new Meteor.Error('no-saml-provider', + 'SAML internal error', + { method: 'getSamlProviderConfig' }); + } + var samlProvider = function(element) { + return (element.provider === provider); + }; + return Accounts.saml.settings.providers.filter(samlProvider)[0]; +} + Meteor.methods({ samlLogout: function(provider) { // Make sure the user is logged in before initiate SAML SLO if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'samlLogout' }); } - var samlProvider = function(element) { - return (element.provider === provider); - }; - var providerConfig = Accounts.saml.settings.providers.filter(samlProvider)[0]; + var providerConfig = getSamlProviderConfig(provider); if (Accounts.saml.settings.debug) { console.log('Logout request from ' + JSON.stringify(providerConfig)); @@ -185,7 +197,8 @@ var samlUrlToObject = function(url) { return null; } - var splitPath = url.split('/'); + var splitUrl = url.split('?'); + var splitPath = splitUrl[0].split('/'); // Any non-saml request will continue down the default // middlewares. @@ -320,12 +333,22 @@ var middleware = function(req, res, next) { var credentialToken = profile.inResponseToId || profile.InResponseTo || samlObject.credentialToken; if (!credentialToken) { - throw new Error('Unable to determine credentialToken'); + // No credentialToken in IdP-initiated SSO + var saml_idp_credentialToken = Random.id(); + Accounts.saml._loginResultForCredentialToken[saml_idp_credentialToken] = { + profile: profile + }; + var url = Meteor.absoluteUrl('home') + '?saml_idp_credentialToken='+saml_idp_credentialToken; + res.writeHead(302, { + 'Location': url + }); + res.end(); + } else { + Accounts.saml._loginResultForCredentialToken[credentialToken] = { + profile: profile + }; + closePopup(res); } - Accounts.saml._loginResultForCredentialToken[credentialToken] = { - profile: profile - }; - closePopup(res); }); break; default: diff --git a/packages/meteor-accounts-saml/saml_utils.js b/packages/meteor-accounts-saml/saml_utils.js index 90ab63d4382c52d1377e6080bcebd9085954ed8e..09e299a0acd174588da8da97b382ef887cb289ad 100644 --- a/packages/meteor-accounts-saml/saml_utils.js +++ b/packages/meteor-accounts-saml/saml_utils.js @@ -166,15 +166,6 @@ SAML.prototype.requestToUrl = function(request, operation, callback) { target += '?'; } - var samlRequest = { - SAMLRequest: base64 - }; - - if (self.options.privateCert) { - samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; - samlRequest.Signature = self.signRequest(querystring.stringify(samlRequest)); - } - // TBD. We should really include a proper RelayState here var relayState; if (operation === 'logout') { @@ -183,7 +174,18 @@ SAML.prototype.requestToUrl = function(request, operation, callback) { } else { relayState = self.options.provider; } - target += querystring.stringify(samlRequest) + '&RelayState=' + relayState; + + var samlRequest = { + SAMLRequest: base64, + RelayState: relayState + }; + + if (self.options.privateCert) { + samlRequest.SigAlg = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + samlRequest.Signature = self.signRequest(querystring.stringify(samlRequest)); + } + + target += querystring.stringify(samlRequest); if (Meteor.settings.debug) { console.log('requestToUrl: ' + target); diff --git a/packages/rocketchat-action-links/client/stylesheets/actionLinks.less b/packages/rocketchat-action-links/client/stylesheets/actionLinks.less index 6a0a00aad2d7243d72168108bc43cb1a1a7b8be3..4d6eea113a9a2eb99848b4cd727c9870ce2b06b9 100644 --- a/packages/rocketchat-action-links/client/stylesheets/actionLinks.less +++ b/packages/rocketchat-action-links/client/stylesheets/actionLinks.less @@ -4,24 +4,23 @@ margin-top: 4px; margin-bottom: 4px; text-align: center; + li { cursor: pointer; position: relative; padding-right: 2px; - color: @primary-action-color; - list-style: none; display: inline; .action-link { - background-color: @primary-action-color; padding: 5px; border-radius: 7px; - color: @tertiary-font-color; margin: 0 2px; } } - li:last-child:after { content: none; } + li:last-child::after { + content: none; + } } } diff --git a/packages/rocketchat-action-links/loadStylesheets.js b/packages/rocketchat-action-links/loadStylesheets.js deleted file mode 100644 index e580080b6dc727d07b2db1104e1898e1d56e85ff..0000000000000000000000000000000000000000 --- a/packages/rocketchat-action-links/loadStylesheets.js +++ /dev/null @@ -1,3 +0,0 @@ -RocketChat.theme.addPackageAsset(function() { - return Assets.getText('client/stylesheets/actionLinks.less'); -}); diff --git a/packages/rocketchat-action-links/package.js b/packages/rocketchat-action-links/package.js index 3039842b48b2e9bd7c6af58cbafebb2251a56c93..d8f12c9c181433ef6da0b4302bfb08678c20b41a 100644 --- a/packages/rocketchat-action-links/package.js +++ b/packages/rocketchat-action-links/package.js @@ -11,10 +11,10 @@ Package.onUse(function(api) { api.use('rocketchat:lib'); api.use('rocketchat:theme'); api.use('rocketchat:ui'); + api.use('less'); api.addFiles('client/init.js', 'client'); - api.addAssets('client/stylesheets/actionLinks.less', 'server'); - api.addFiles('loadStylesheets.js', 'server'); + api.addFiles('client/stylesheets/actionLinks.less', 'client'); api.addFiles('server/registerActionLinkFuncts.js', ['server', 'client']); api.addFiles('server/actionLinkHandler.js', ['server', 'client']); diff --git a/packages/rocketchat-api/package.js b/packages/rocketchat-api/package.js index b40fbeb8710776b1ba0ea5ee9a24166e6d28d1f4..023e3b6a3c75e2a9df8c8fddf7b6dfd07f8c9330 100644 --- a/packages/rocketchat-api/package.js +++ b/packages/rocketchat-api/package.js @@ -7,16 +7,31 @@ Package.describe({ Package.onUse(function(api) { api.use([ - 'coffeescript', 'underscore', 'ecmascript', 'rocketchat:lib', 'nimble:restivus' ]); - api.addFiles('server/api.coffee', 'server'); - api.addFiles('server/routes.coffee', 'server'); + api.addFiles('server/api.js', 'server'); api.addFiles('server/settings.js', 'server'); + + //Register v1 helpers + api.addFiles('server/v1/helpers/getPaginationItems.js', 'server'); + api.addFiles('server/v1/helpers/getUserFromParams.js', 'server'); + api.addFiles('server/v1/helpers/parseJsonQuery.js', 'server'); + + api.addFiles('server/default/info.js', 'server'); + + //Add v1 routes + api.addFiles('server/v1/channels.js', 'server'); + api.addFiles('server/v1/chat.js', 'server'); + api.addFiles('server/v1/groups.js', 'server'); + api.addFiles('server/v1/im.js', 'server'); + api.addFiles('server/v1/integrations.js', 'server'); + api.addFiles('server/v1/misc.js', 'server'); + api.addFiles('server/v1/users.js', 'server'); + api.addFiles('server/v1/settings.js', 'server'); }); Npm.depends({ diff --git a/packages/rocketchat-api/server/api.coffee b/packages/rocketchat-api/server/api.coffee deleted file mode 100644 index 2b600dba87d5a9e309f239e79aeb748fc8a4b8f7..0000000000000000000000000000000000000000 --- a/packages/rocketchat-api/server/api.coffee +++ /dev/null @@ -1,61 +0,0 @@ -class API extends Restivus - constructor: -> - @authMethods = [] - super - - addAuthMethod: (method) -> - @authMethods.push method - - success: (result={}) -> - if _.isObject(result) - result.success = true - - return {} = - statusCode: 200 - body: result - - failure: (result) -> - if _.isObject(result) - result.success = false - else - result = - success: false - error: result - - return {} = - statusCode: 400 - body: result - - unauthorized: (msg) -> - return {} = - statusCode: 403 - body: - success: false - error: msg or 'unauthorized' - - -RocketChat.API = {} - - -RocketChat.API.v1 = new API - version: 'v1' - useDefaultAuth: true - prettyJson: true - enableCors: false - auth: - token: 'services.resume.loginTokens.hashedToken' - user: -> - if @bodyParams?.payload? - @bodyParams = JSON.parse @bodyParams.payload - - for method in RocketChat.API.v1.authMethods - result = method.apply @, arguments - if result not in [undefined, null, false] - return result - - if @request.headers['x-auth-token'] - token = Accounts._hashLoginToken @request.headers['x-auth-token'] - - return {} = - userId: @request.headers['x-user-id'] - token: token diff --git a/packages/rocketchat-api/server/api.js b/packages/rocketchat-api/server/api.js new file mode 100644 index 0000000000000000000000000000000000000000..eba3aff4f77128d43cc9acf56d662051f1d169c5 --- /dev/null +++ b/packages/rocketchat-api/server/api.js @@ -0,0 +1,151 @@ +/* global Restivus */ +class API extends Restivus { + constructor(properties) { + super(properties); + this.logger = new Logger(`API ${properties.version ? properties.version : 'default'} Logger`, {}); + this.authMethods = []; + this.helperMethods = new Map(); + this.defaultFieldsToExclude = { + joinCode: 0, + $loki: 0, + meta: 0 + }; + } + + addAuthMethod(method) { + this.authMethods.push(method); + } + + success(result={}) { + if (_.isObject(result)) { + result.success = true; + } + + return { + statusCode: 200, + body: result + }; + } + + failure(result, errorType) { + if (_.isObject(result)) { + result.success = false; + } else { + result = { + success: false, + error: result + }; + + if (errorType) { + result.errorType = errorType; + } + } + + return { + statusCode: 400, + body: result + }; + } + + + unauthorized(msg) { + return { + statusCode: 403, + body: { + success: false, + error: msg ? msg : 'unauthorized' + } + }; + } + + addRoute(route, options, endpoints) { + //Note: required if the developer didn't provide options + if (typeof endpoints === 'undefined') { + endpoints = options; + options = {}; + } + + //Note: This is required due to Restivus calling `addRoute` in the constructor of itself + if (this.helperMethods) { + Object.keys(endpoints).forEach((method) => { + if (typeof endpoints[method] === 'function') { + endpoints[method] = { action: endpoints[method] }; + } + + //Add a try/catch for each much + const originalAction = endpoints[method].action; + endpoints[method].action = function() { + let result; + try { + result = originalAction.apply(this); + } catch (e) { + this.logger.debug(`${method} ${route} threw an error:`, e); + return RocketChat.API.v1.failure(e.message, e.error); + } + + return result ? result : RocketChat.API.v1.success(); + }; + + for (const [name, helperMethod] of this.helperMethods) { + endpoints[method][name] = helperMethod; + } + + //Allow the endpoints to make usage of the logger which respects the user's settings + endpoints[method].logger = this.logger; + endpoints[method].method = method.toUpperCase(); + }); + } + + super.addRoute(route, options, endpoints); + } +} + +RocketChat.API = {}; + +const getUserAuth = function _getUserAuth() { + const invalidResults = [undefined, null, false]; + return { + token: 'services.resume.loginTokens.hashedToken', + user: function() { + if (this.bodyParams && this.bodyParams.payload) { + this.bodyParams = JSON.parse(this.bodyParams.payload); + } + + for (let i = 0; i < RocketChat.API.v1.authMethods.length; i++) { + const method = RocketChat.API.v1.authMethods[i]; + + if (typeof method === 'function') { + const result = method.apply(this, arguments); + if (!invalidResults.includes(result)) { + return result; + } + } + } + + let token; + if (this.request.headers['x-auth-token']) { + token = Accounts._hashLoginToken(this.request.headers['x-auth-token']); + } + + return { + userId: this.request.headers['x-user-id'], + token + }; + } + }; +}; + +RocketChat.API.v1 = new API({ + version: 'v1', + useDefaultAuth: true, + prettyJson: true, + enableCors: false, + auth: getUserAuth() +}); + +RocketChat.API.default = new API({ + useDefaultAuth: true, + prettyJson: true, + enableCors: false, + auth: getUserAuth() +}); diff --git a/packages/rocketchat-api/server/default/info.js b/packages/rocketchat-api/server/default/info.js new file mode 100644 index 0000000000000000000000000000000000000000..adf7a094db0d341d1d6523d2a9435eb366a5d82d --- /dev/null +++ b/packages/rocketchat-api/server/default/info.js @@ -0,0 +1,5 @@ +RocketChat.API.default.addRoute('info', { authRequired: false }, { + get: function() { + return RocketChat.Info; + } +}); diff --git a/packages/rocketchat-api/server/routes.coffee b/packages/rocketchat-api/server/routes.coffee deleted file mode 100644 index 10b34d05b93b67868e9eccfed09996e423b1c6fd..0000000000000000000000000000000000000000 --- a/packages/rocketchat-api/server/routes.coffee +++ /dev/null @@ -1,293 +0,0 @@ -RocketChat.API.v1.addRoute 'info', authRequired: false, - get: -> RocketChat.Info - - -RocketChat.API.v1.addRoute 'me', authRequired: true, - get: -> - return _.pick @user, [ - '_id' - 'name' - 'emails' - 'status' - 'statusConnection' - 'username' - 'utcOffset' - 'active' - 'language' - ] - - -# Send Channel Message -RocketChat.API.v1.addRoute 'chat.messageExamples', authRequired: true, - get: -> - return RocketChat.API.v1.success - body: [ - token: Random.id(24) - channel_id: Random.id() - channel_name: 'general' - timestamp: new Date - user_id: Random.id() - user_name: 'rocket.cat' - text: 'Sample text 1' - trigger_word: 'Sample' - , - token: Random.id(24) - channel_id: Random.id() - channel_name: 'general' - timestamp: new Date - user_id: Random.id() - user_name: 'rocket.cat' - text: 'Sample text 2' - trigger_word: 'Sample' - , - token: Random.id(24) - channel_id: Random.id() - channel_name: 'general' - timestamp: new Date - user_id: Random.id() - user_name: 'rocket.cat' - text: 'Sample text 3' - trigger_word: 'Sample' - ] - - -# Send Channel Message -RocketChat.API.v1.addRoute 'chat.postMessage', authRequired: true, - post: -> - try - messageReturn = processWebhookMessage @bodyParams, @user - - if not messageReturn? - return RocketChat.API.v1.failure 'unknown-error' - - return RocketChat.API.v1.success - ts: Date.now() - channel: messageReturn.channel - message: messageReturn.message - catch e - return RocketChat.API.v1.failure e.error - -# Set Channel Topic -RocketChat.API.v1.addRoute 'channels.setTopic', authRequired: true, - post: -> - if not @bodyParams.channel? - return RocketChat.API.v1.failure 'Body param "channel" is required' - - if not @bodyParams.topic? - return RocketChat.API.v1.failure 'Body param "topic" is required' - - unless RocketChat.authz.hasPermission(@userId, 'edit-room', @bodyParams.channel) - return RocketChat.API.v1.unauthorized() - - if not RocketChat.saveRoomTopic(@bodyParams.channel, @bodyParams.topic, @user) - return RocketChat.API.v1.failure 'invalid_channel' - - return RocketChat.API.v1.success - topic: @bodyParams.topic - - -# Create Channel -RocketChat.API.v1.addRoute 'channels.create', authRequired: true, - post: -> - if not @bodyParams.name? - return RocketChat.API.v1.failure 'Body param "name" is required' - - if not RocketChat.authz.hasPermission(@userId, 'create-c') - return RocketChat.API.v1.unauthorized() - - id = undefined - try - Meteor.runAsUser this.userId, => - id = Meteor.call 'createChannel', @bodyParams.name, [] - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success - channel: RocketChat.models.Rooms.findOneById(id.rid) - -# List Private Groups a user has access to -RocketChat.API.v1.addRoute 'groups.list', authRequired: true, - get: -> - roomIds = _.pluck RocketChat.models.Subscriptions.findByTypeAndUserId('p', @userId).fetch(), 'rid' - return { groups: RocketChat.models.Rooms.findByIds(roomIds).fetch() } - -# Add All Users to Channel -RocketChat.API.v1.addRoute 'channel.addall', authRequired: true, - post: -> - - id = undefined - try - Meteor.runAsUser this.userId, => - id = Meteor.call 'addAllUserToRoom', @bodyParams.roomId, [] - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success - channel: RocketChat.models.Rooms.findOneById(@bodyParams.roomId) - -# List all users -RocketChat.API.v1.addRoute 'users.list', authRequired: true, - get: -> - if RocketChat.authz.hasRole(@userId, 'admin') is false - return RocketChat.API.v1.unauthorized() - - return { users: RocketChat.models.Users.find().fetch() } - -# Create user -RocketChat.API.v1.addRoute 'users.create', authRequired: true, - post: -> - try - check @bodyParams, - email: String - name: String - password: String - username: String - role: Match.Maybe(String) - joinDefaultChannels: Match.Maybe(Boolean) - requirePasswordChange: Match.Maybe(Boolean) - sendWelcomeEmail: Match.Maybe(Boolean) - verified: Match.Maybe(Boolean) - customFields: Match.Maybe(Object) - - # check username availability first (to not create an user without a username) - try - nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$' - catch - nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$' - - if not nameValidation.test @bodyParams.username - return RocketChat.API.v1.failure 'Invalid username' - - unless RocketChat.checkUsernameAvailability @bodyParams.username - return RocketChat.API.v1.failure 'Username not available' - - userData = {} - - newUserId = RocketChat.saveUser(@userId, @bodyParams) - - if @bodyParams.customFields? - RocketChat.saveCustomFields(newUserId, @bodyParams.customFields) - - user = RocketChat.models.Users.findOneById(newUserId) - - if typeof @bodyParams.joinDefaultChannels is 'undefined' or @bodyParams.joinDefaultChannels - RocketChat.addUserToDefaultChannels(user) - - return RocketChat.API.v1.success - user: user - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - -# Update user -RocketChat.API.v1.addRoute 'user.update', authRequired: true, - post: -> - try - check @bodyParams, - userId: String - data: - email: Match.Maybe(String) - name: Match.Maybe(String) - password: Match.Maybe(String) - username: Match.Maybe(String) - role: Match.Maybe(String) - joinDefaultChannels: Match.Maybe(Boolean) - requirePasswordChange: Match.Maybe(Boolean) - sendWelcomeEmail: Match.Maybe(Boolean) - verified: Match.Maybe(Boolean) - customFields: Match.Maybe(Object) - - userData = _.extend({ _id: @bodyParams.userId }, @bodyParams.data) - - RocketChat.saveUser(@userId, userData) - - if @bodyParams.data.customFields? - RocketChat.saveCustomFields(@bodyParams.userId, @bodyParams.data.customFields) - - return RocketChat.API.v1.success - user: RocketChat.models.Users.findOneById(@bodyParams.userId) - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - -# Get User Information -RocketChat.API.v1.addRoute 'user.info', authRequired: true, - post: -> - if RocketChat.authz.hasRole(@userId, 'admin') is false - return RocketChat.API.v1.unauthorized() - - return { user: RocketChat.models.Users.findOneByUsername @bodyParams.name } - -# Get User Presence -RocketChat.API.v1.addRoute 'user.getpresence', authRequired: true, - post: -> - return { user: RocketChat.models.Users.findOne( { username: @bodyParams.name} , {fields: {status: 1}} ) } - -# Delete User -RocketChat.API.v1.addRoute 'users.delete', authRequired: true, - post: -> - if not @bodyParams.userId? - return RocketChat.API.v1.failure 'Body param "userId" is required' - - if not RocketChat.authz.hasPermission(@userId, 'delete-user') - return RocketChat.API.v1.unauthorized() - - id = undefined - try - Meteor.runAsUser this.userId, => - id = Meteor.call 'deleteUser', @bodyParams.userId, [] - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success - -# Set user's avatar -RocketChat.API.v1.addRoute 'users.setAvatar', authRequired: true, - post: -> - try - Busboy = Npm.require('busboy') - - busboy = new Busboy headers: @request.headers - - user = Meteor.users.findOne(@userId) - - Meteor.wrapAsync((callback) => - busboy.on 'file', Meteor.bindEnvironment (fieldname, file, filename, encoding, mimetype) => - if fieldname isnt 'image' - return callback(new Meteor.Error 'invalid-field') - - imageData = [] - file.on 'data', Meteor.bindEnvironment (data) -> - imageData.push data - - file.on 'end', Meteor.bindEnvironment () => - RocketChat.setUserAvatar(user, Buffer.concat(imageData), mimetype, 'rest') - callback() - - @request.pipe busboy - )() - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success() - -# Create Private Group -RocketChat.API.v1.addRoute 'groups.create', authRequired: true, - post: -> - if not @bodyParams.name? - return RocketChat.API.v1.failure 'Body param "name" is required' - - if not RocketChat.authz.hasPermission(@userId, 'create-p') - return RocketChat.API.v1.unauthorized() - - id = undefined - try - if not @bodyParams.members? - Meteor.runAsUser this.userId, => - id = Meteor.call 'createPrivateGroup', @bodyParams.name, [] - else - Meteor.runAsUser this.userId, => - id = Meteor.call 'createPrivateGroup', @bodyParams.name, @bodyParams.members, [] - catch e - return RocketChat.API.v1.failure e.name + ': ' + e.message - - return RocketChat.API.v1.success - group: RocketChat.models.Rooms.findOneById(id.rid) diff --git a/packages/rocketchat-api/server/settings.js b/packages/rocketchat-api/server/settings.js index b80df858c217b12e1c80b41038860e9e6d9b0e3f..238f4d0d11d7c965e02f15f5598a4f04e6cc4c26 100644 --- a/packages/rocketchat-api/server/settings.js +++ b/packages/rocketchat-api/server/settings.js @@ -1,29 +1,7 @@ -// settings endpoints -RocketChat.API.v1.addRoute('settings/:_id', { authRequired: true }, { - get() { - if (!RocketChat.authz.hasPermission(this.userId, 'view-privileged-setting')) { - return RocketChat.API.v1.unauthorized(); - } - - return RocketChat.API.v1.success(_.pick(RocketChat.models.Settings.findOneNotHiddenById(this.urlParams._id), '_id', 'value')); - }, - post() { - if (!RocketChat.authz.hasPermission(this.userId, 'edit-privileged-setting')) { - return RocketChat.API.v1.unauthorized(); - } - - try { - check(this.bodyParams, { - value: Match.Any - }); - - if (RocketChat.models.Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value)) { - return RocketChat.API.v1.success(); - } - - return RocketChat.API.v1.failure(); - } catch (e) { - return RocketChat.API.v1.failure(e.message); - } - } +RocketChat.settings.addGroup('General', function() { + this.section('REST API', function() { + this.add('API_Upper_Count_Limit', 100, { type: 'int', public: false }); + this.add('API_Default_Count', 50, { type: 'int', public: false }); + this.add('API_Allow_Infinite_Count', true, { type: 'boolean', public: false }); + }); }); diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js new file mode 100644 index 0000000000000000000000000000000000000000..1e091fc7b82db404c59ce1d09f94681432442644 --- /dev/null +++ b/packages/rocketchat-api/server/v1/channels.js @@ -0,0 +1,587 @@ +//Returns the channel IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property +function findChannelById({ roomId, checkedArchived = true }) { + if (!roomId || !roomId.trim()) { + throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" is required'); + } + + const room = RocketChat.models.Rooms.findOneById(roomId, { fields: RocketChat.API.v1.defaultFieldsToExclude }); + + if (!room || room.t !== 'c') { + throw new Meteor.Error('error-room-not-found', `No channel found by the id of: ${roomId}`); + } + + if (checkedArchived && room.archived) { + throw new Meteor.Error('error-room-aquived', `The channel, ${room.name}, is archived`); + } + + return room; +} + +RocketChat.API.v1.addRoute('channels.addAll', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addAllUserToRoom', findResult._id); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.addModerator', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addRoomModerator', findResult._id, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.addOwner', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addRoomOwner', findResult._id, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.archive', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('archiveRoom', findResult._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.cleanHistory', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (!this.bodyParams.latest) { + return RocketChat.API.v1.failure('Body parameter "latest" is required.'); + } + + if (!this.bodyParams.oldest) { + return RocketChat.API.v1.failure('Body parameter "oldest" is required.'); + } + + const latest = new Date(this.bodyParams.latest); + const oldest = new Date(this.bodyParams.oldest); + + let inclusive = false; + if (typeof this.bodyParams.inclusive !== 'undefined') { + inclusive = this.bodyParams.inclusive; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('cleanChannelHistory', { roomId: findResult._id, latest, oldest, inclusive }); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.close', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + + const sub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId); + + if (!sub) { + return RocketChat.API.v1.failure(`The user/callee is not in the channel "${findResult.name}.`); + } + + if (!sub.open) { + return RocketChat.API.v1.failure(`The channel, ${findResult.name}, is already closed to the sender`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('hideRoom', findResult._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.create', { authRequired: true }, { + post: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'create-p')) { + return RocketChat.API.v1.unauthorized(); + } + + if (!this.bodyParams.name) { + return RocketChat.API.v1.failure('Body param "name" is required'); + } + + if (this.bodyParams.members && !_.isArray(this.bodyParams.members)) { + return RocketChat.API.v1.failure('Body param "members" must be an array if provided'); + } + + let readOnly = false; + if (typeof this.bodyParams.readOnly !== 'undefined') { + readOnly = this.bodyParams.readOnly; + } + + let id; + Meteor.runAsUser(this.userId, () => { + id = Meteor.call('createChannel', this.bodyParams.name, this.bodyParams.members ? this.bodyParams.members : [], readOnly); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(id.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.delete', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + + //The find method returns either with the group or the failur + + Meteor.runAsUser(this.userId, () => { + Meteor.call('eraseRoom', findResult._id); + }); + + return RocketChat.API.v1.success({ + channel: findResult + }); + } +}); + +RocketChat.API.v1.addRoute('channels.getIntegrations', { authRequired: true }, { + get: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'manage-integrations')) { + return RocketChat.API.v1.unauthorized(); + } + + const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false }); + + let includeAllPublicChannels = true; + if (typeof this.queryParams.includeAllPublicChannels !== 'undefined') { + includeAllPublicChannels = this.queryParams.includeAllPublicChannels === 'true'; + } + + let ourQuery = { + channel: `#${findResult.name}` + }; + + if (includeAllPublicChannels) { + ourQuery.channel = { + $in: [ourQuery.channel, 'all_public_channels'] + }; + } + + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + ourQuery = Object.assign({}, query, ourQuery); + + const integrations = RocketChat.models.Integrations.find(ourQuery, { + sort: sort ? sort : { _createdAt: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }).fetch(); + + return RocketChat.API.v1.success({ + integrations, + count: integrations.length, + offset, + total: RocketChat.models.Integrations.find(ourQuery).count() + }); + } +}); + +RocketChat.API.v1.addRoute('channels.history', { authRequired: true }, { + get: function() { + const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false }); + + let latestDate = new Date(); + if (this.queryParams.latest) { + latestDate = new Date(this.queryParams.latest); + } + + let oldestDate = undefined; + if (this.queryParams.oldest) { + oldestDate = new Date(this.queryParams.oldest); + } + + let inclusive = false; + if (this.queryParams.inclusive) { + inclusive = this.queryParams.inclusive; + } + + let count = 20; + if (this.queryParams.count) { + count = parseInt(this.queryParams.count); + } + + let unreads = false; + if (this.queryParams.unreads) { + unreads = this.queryParams.unreads; + } + + let result; + Meteor.runAsUser(this.userId, () => { + result = Meteor.call('getChannelHistory', { rid: findResult._id, latest: latestDate, oldest: oldestDate, inclusive, count, unreads }); + }); + + return RocketChat.API.v1.success({ + messages: result && result.messages ? result.messages : [] + }); + } +}); + +RocketChat.API.v1.addRoute('channels.info', { authRequired: true }, { + get: function() { + const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.invite', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addUserToRoom', { rid: findResult._id, username: user.username }); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.join', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('joinRoom', findResult._id, this.bodyParams.joinCode); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.kick', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('removeUserFromRoom', { rid: findResult._id, username: user.username }); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.leave', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('leaveRoom', findResult._id); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.list', { authRequired: true }, { + get: { + //This is like this only to provide an example of how we routes can be defined :X + action: function() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + const ourQuery = Object.assign({}, query, { t: 'c' }); + + const rooms = RocketChat.models.Rooms.find(ourQuery, { + sort: sort ? sort : { name: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }).fetch(); + + return RocketChat.API.v1.success({ + channels: rooms, + count: rooms.length, + offset, + total: RocketChat.models.Rooms.find(ourQuery).count() + }); + } + } +}); + +RocketChat.API.v1.addRoute('channels.list.joined', { authRequired: true }, { + get: function() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields } = this.parseJsonQuery(); + let rooms = _.pluck(RocketChat.models.Subscriptions.findByTypeAndUserId('c', this.userId).fetch(), '_room'); + const totalCount = rooms.length; + + rooms = RocketChat.models.Rooms.processQueryOptionsOnResult(rooms, { + sort: sort ? sort : { name: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }); + + return RocketChat.API.v1.success({ + channels: rooms, + offset, + count: rooms.length, + total: totalCount + }); + } +}); + +RocketChat.API.v1.addRoute('channels.open', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + + const sub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId); + + if (!sub) { + return RocketChat.API.v1.failure(`The user/callee is not in the channel "${findResult.name}".`); + } + + if (sub.open) { + return RocketChat.API.v1.failure(`The channel, ${findResult.name}, is already open to the sender`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('openRoom', findResult._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.removeModerator', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('removeRoomModerator', findResult._id, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.removeOwner', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('removeRoomOwner', findResult._id, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('channels.rename', { authRequired: true }, { + post: function() { + if (!this.bodyParams.name || !this.bodyParams.name.trim()) { + return RocketChat.API.v1.failure('The bodyParam "name" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (findResult.name === this.bodyParams.name) { + return RocketChat.API.v1.failure('The channel name is the same as what it would be renamed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'roomName', this.bodyParams.name); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.setDescription', { authRequired: true }, { + post: function() { + if (!this.bodyParams.description || !this.bodyParams.description.trim()) { + return RocketChat.API.v1.failure('The bodyParam "description" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (findResult.description === this.bodyParams.description) { + return RocketChat.API.v1.failure('The channel description is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'roomDescription', this.bodyParams.description); + }); + + return RocketChat.API.v1.success({ + description: this.bodyParams.description + }); + } +}); + +RocketChat.API.v1.addRoute('channels.setJoinCode', { authRequired: true }, { + post: function() { + if (!this.bodyParams.joinCode || !this.bodyParams.joinCode.trim()) { + return RocketChat.API.v1.failure('The bodyParam "joinCode" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'joinCode', this.bodyParams.joinCode); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.setPurpose', { authRequired: true }, { + post: function() { + if (!this.bodyParams.purpose || !this.bodyParams.purpose.trim()) { + return RocketChat.API.v1.failure('The bodyParam "purpose" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (findResult.description === this.bodyParams.purpose) { + return RocketChat.API.v1.failure('The channel purpose (description) is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'roomDescription', this.bodyParams.purpose); + }); + + return RocketChat.API.v1.success({ + purpose: this.bodyParams.purpose + }); + } +}); + +RocketChat.API.v1.addRoute('channels.setReadOnly', { authRequired: true }, { + post: function() { + if (typeof this.bodyParams.readOnly === 'undefined') { + return RocketChat.API.v1.failure('The bodyParam "readOnly" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (findResult.ro === this.bodyParams.readOnly) { + return RocketChat.API.v1.failure('The channel read only setting is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'readOnly', this.bodyParams.readOnly); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.setTopic', { authRequired: true }, { + post: function() { + if (!this.bodyParams.topic || !this.bodyParams.topic.trim()) { + return RocketChat.API.v1.failure('The bodyParam "topic" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (findResult.topic === this.bodyParams.topic) { + return RocketChat.API.v1.failure('The channel topic is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'roomTopic', this.bodyParams.topic); + }); + + return RocketChat.API.v1.success({ + topic: this.bodyParams.topic + }); + } +}); + +RocketChat.API.v1.addRoute('channels.setType', { authRequired: true }, { + post: function() { + if (!this.bodyParams.type || !this.bodyParams.type.trim()) { + return RocketChat.API.v1.failure('The bodyParam "type" is required'); + } + + const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + + if (findResult.t === this.bodyParams.type) { + return RocketChat.API.v1.failure('The channel type is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'roomType', this.bodyParams.type); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('channels.unarchive', { authRequired: true }, { + post: function() { + const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + + if (!findResult.archived) { + return RocketChat.API.v1.failure(`The channel, ${findResult.name}, is not archived`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('unarchiveRoom', findResult._id); + }); + + return RocketChat.API.v1.success(); + } +}); diff --git a/packages/rocketchat-api/server/v1/chat.js b/packages/rocketchat-api/server/v1/chat.js new file mode 100644 index 0000000000000000000000000000000000000000..90569eda69461326cba0e62fd609da77a8d65ea7 --- /dev/null +++ b/packages/rocketchat-api/server/v1/chat.js @@ -0,0 +1,76 @@ +/* global processWebhookMessage */ +RocketChat.API.v1.addRoute('chat.delete', { authRequired: true }, { + post: function() { + check(this.bodyParams, Match.ObjectIncluding({ + msgId: String, + roomId: String, + asUser: Match.Maybe(Boolean) + })); + + const msg = RocketChat.models.Messages.findOneById(this.bodyParams.msgId, { fields: { u: 1, rid: 1 }}); + + if (!msg) { + return RocketChat.API.v1.failure(`No message found with the id of "${this.bodyParams.msgId}".`); + } + + if (this.bodyParams.roomId !== msg.rid) { + return RocketChat.API.v1.failure('The room id provided does not match where the message is from.'); + } + + Meteor.runAsUser(this.bodyParams.asUser ? msg.u._id : this.userId, () => { + Meteor.call('deleteMessage', { _id: msg._id }); + }); + + return RocketChat.API.v1.success({ + _id: msg._id, + ts: Date.now() + }); + } +}); + +RocketChat.API.v1.addRoute('chat.postMessage', { authRequired: true }, { + post: function() { + const messageReturn = processWebhookMessage(this.bodyParams, this.user)[0]; + + if (!messageReturn) { + return RocketChat.API.v1.failure('unknown-error'); + } + + return RocketChat.API.v1.success({ + ts: Date.now(), + channel: messageReturn.channel, + message: messageReturn.message + }); + } +}); + +RocketChat.API.v1.addRoute('chat.update', { authRequired: true }, { + post: function() { + check(this.bodyParams, Match.ObjectIncluding({ + roomId: String, + msgId: String, + text: String //Using text to be consistant with chat.postMessage + })); + + const msg = RocketChat.models.Messages.findOneById(this.bodyParams.msgId); + + //Ensure the message exists + if (!msg) { + return RocketChat.API.v1.failure(`No message found with the id of "${this.bodyParams.msgId}".`); + } + + if (this.bodyParams.roomId !== msg.rid) { + return RocketChat.API.v1.failure('The room id provided does not match where the message is from.'); + } + + //Permission checks are already done in the updateMessage method, so no need to duplicate them + Meteor.runAsUser(this.userId, () => { + Meteor.call('updateMessage', { _id: msg._id, msg: this.bodyParams.text, rid: msg.rid }); + + }); + + return RocketChat.API.v1.success({ + message: RocketChat.models.Messages.findOneById(msg._id) + }); + } +}); diff --git a/packages/rocketchat-api/server/v1/groups.js b/packages/rocketchat-api/server/v1/groups.js new file mode 100644 index 0000000000000000000000000000000000000000..abef31491b29c1d4b3e384d9371372a71b93ca90 --- /dev/null +++ b/packages/rocketchat-api/server/v1/groups.js @@ -0,0 +1,540 @@ +//Returns the private group subscription IF found otherwise it will reutrn the failure of why it didn't. Check the `statusCode` property +function findPrivateGroupById({ roomId, userId, checkedArchived = true }) { + if (!roomId || !roomId.trim()) { + return RocketChat.API.v1.failure('Body param "roomId" is required'); + } + + const roomSub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, userId); + + if (!roomSub || roomSub.t !== 'p') { + return RocketChat.API.v1.failure(`No private group found by the id of: ${roomId}`); + } + + if (checkedArchived && roomSub.archived) { + return RocketChat.API.v1.failure(`The private group, ${roomSub.name}, is already archived`); + } + + return roomSub; +} + +RocketChat.API.v1.addRoute('groups.addModerator', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + if (findResult.statusCode) { + return findResult; + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addRoomModerator', findResult.rid, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('groups.addOwner', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + if (findResult.statusCode) { + return findResult; + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addRoomOwner', findResult.rid, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +//Archives a private group only if it wasn't +RocketChat.API.v1.addRoute('groups.archive', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('archiveRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('groups.close', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId, checkedArchived: false }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + if (!findResult.open) { + return RocketChat.API.v1.failure(`The private group with an id "${this.bodyParams.roomId}" is already closed to the sender`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('hideRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); + +//Create Private Group +RocketChat.API.v1.addRoute('groups.create', { authRequired: true }, { + post: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'create-p')) { + return RocketChat.API.v1.unauthorized(); + } + + if (!this.bodyParams.name) { + return RocketChat.API.v1.failure('Body param "name" is required'); + } + + if (this.bodyParams.members && !_.isArray(this.bodyParams.members)) { + return RocketChat.API.v1.failure('Body param "members" must be an array if provided'); + } + + let id; + Meteor.runAsUser(this.userId, () => { + id = Meteor.call('createPrivateGroup', this.bodyParams.name, this.bodyParams.members ? this.bodyParams.members : []); + }); + + return RocketChat.API.v1.success({ + group: RocketChat.models.Rooms.findOneById(id.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('groups.delete', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId, checkedArchived: false }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('eraseRoom', findResult.rid); + }); + + return RocketChat.API.v1.success({ + group: RocketChat.models.Rooms.processQueryOptionsOnResult([findResult._room], { fields: RocketChat.API.v1.defaultFieldsToExclude })[0] + }); + } +}); + +RocketChat.API.v1.addRoute('groups.getIntegrations', { authRequired: true }, { + get: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'manage-integrations')) { + return RocketChat.API.v1.unauthorized(); + } + + const findResult = findPrivateGroupById({ roomId: this.queryParams.roomId, userId: this.userId, checkedArchived: false }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + let includeAllPrivateGroups = true; + if (typeof this.queryParams.includeAllPrivateGroups !== 'undefined') { + includeAllPrivateGroups = this.queryParams.includeAllPrivateGroups === 'true'; + } + + const channelsToSearch = [`#${findResult.name}`]; + if (includeAllPrivateGroups) { + channelsToSearch.push('all_private_groups'); + } + + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + const ourQuery = Object.assign({}, query, { channel: { $in: channelsToSearch } }); + const integrations = RocketChat.models.Integrations.find(ourQuery, { + sort: sort ? sort : { _createdAt: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }).fetch(); + + return RocketChat.API.v1.success({ + integrations, + count: integrations.length, + offset, + total: RocketChat.models.Integrations.find(ourQuery).count() + }); + } +}); + +RocketChat.API.v1.addRoute('groups.history', { authRequired: true }, { + get: function() { + const findResult = findPrivateGroupById({ roomId: this.queryParams.roomId, userId: this.userId, checkedArchived: false }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + let latestDate = new Date(); + if (this.queryParams.latest) { + latestDate = new Date(this.queryParams.latest); + } + + let oldestDate = undefined; + if (this.queryParams.oldest) { + oldestDate = new Date(this.queryParams.oldest); + } + + let inclusive = false; + if (this.queryParams.inclusive) { + inclusive = this.queryParams.inclusive; + } + + let count = 20; + if (this.queryParams.count) { + count = parseInt(this.queryParams.count); + } + + let unreads = false; + if (this.queryParams.unreads) { + unreads = this.queryParams.unreads; + } + + let result; + Meteor.runAsUser(this.userId, () => { + result = Meteor.call('getChannelHistory', { rid: findResult.rid, latest: latestDate, oldest: oldestDate, inclusive, count, unreads }); + }); + + return RocketChat.API.v1.success({ + messages: result && result.messages ? result.messages : [] + }); + } +}); + +RocketChat.API.v1.addRoute('groups.info', { authRequired: true }, { + get: function() { + const findResult = findPrivateGroupById({ roomId: this.queryParams.roomId, userId: this.userId, checkedArchived: false }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + return RocketChat.API.v1.success({ + group: RocketChat.models.Rooms.findOneById(findResult.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('groups.invite', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('addUserToRoom', { rid: findResult.rid, username: user.username }); + }); + + return RocketChat.API.v1.success({ + group: RocketChat.models.Rooms.findOneById(findResult.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('groups.kick', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('removeUserFromRoom', { rid: findResult.rid, username: user.username }); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('groups.leave', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('leaveRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); + +//List Private Groups a user has access to +RocketChat.API.v1.addRoute('groups.list', { authRequired: true }, { + get: function() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields } = this.parseJsonQuery(); + let rooms = _.pluck(RocketChat.models.Subscriptions.findByTypeAndUserId('p', this.userId).fetch(), '_room'); + const totalCount = rooms.length; + + rooms = RocketChat.models.Rooms.processQueryOptionsOnResult(rooms, { + sort: sort ? sort : { name: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }); + + return RocketChat.API.v1.success({ + groups: rooms, + offset, + count: rooms.length, + total: totalCount + }); + } +}); + +RocketChat.API.v1.addRoute('groups.open', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId, checkedArchived: false }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + if (findResult.open) { + return RocketChat.API.v1.failure(`The private group, ${this.bodyParams.name}, is already open for the sender`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('openRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('groups.removeModerator', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + if (findResult.statusCode) { + return findResult; + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('removeRoomModerator', findResult.rid, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('groups.removeOwner', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + if (findResult.statusCode) { + return findResult; + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('removeRoomOwner', findResult.rid, user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('groups.rename', { authRequired: true }, { + post: function() { + if (!this.bodyParams.name || !this.bodyParams.name.trim()) { + return RocketChat.API.v1.failure('The bodyParam "name" is required'); + } + + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult.rid, 'roomName', this.bodyParams.name); + }); + + return RocketChat.API.v1.success({ + channel: RocketChat.models.Rooms.findOneById(findResult.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('groups.setDescription', { authRequired: true }, { + post: function() { + if (!this.bodyParams.description || !this.bodyParams.description.trim()) { + return RocketChat.API.v1.failure('The bodyParam "description" is required'); + } + + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult.rid, 'roomDescription', this.bodyParams.description); + }); + + return RocketChat.API.v1.success({ + description: this.bodyParams.description + }); + } +}); + +RocketChat.API.v1.addRoute('groups.setPurpose', { authRequired: true }, { + post: function() { + if (!this.bodyParams.purpose || !this.bodyParams.purpose.trim()) { + return RocketChat.API.v1.failure('The bodyParam "purpose" is required'); + } + + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult.rid, 'roomDescription', this.bodyParams.purpose); + }); + + return RocketChat.API.v1.success({ + purpose: this.bodyParams.purpose + }); + } +}); + +RocketChat.API.v1.addRoute('groups.setReadOnly', { authRequired: true }, { + post: function() { + if (typeof this.bodyParams.readOnly === 'undefined') { + return RocketChat.API.v1.failure('The bodyParam "readOnly" is required'); + } + + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + if (findResult.statusCode) { + return findResult; + } + + if (findResult.ro === this.bodyParams.readOnly) { + return RocketChat.API.v1.failure('The private group read only setting is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'readOnly', this.bodyParams.readOnly); + }); + + return RocketChat.API.v1.success({ + group: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('groups.setTopic', { authRequired: true }, { + post: function() { + if (!this.bodyParams.topic || !this.bodyParams.topic.trim()) { + return RocketChat.API.v1.failure('The bodyParam "topic" is required'); + } + + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult.rid, 'roomTopic', this.bodyParams.topic); + }); + + return RocketChat.API.v1.success({ + topic: this.bodyParams.topic + }); + } +}); + +RocketChat.API.v1.addRoute('groups.setType', { authRequired: true }, { + post: function() { + if (!this.bodyParams.type || !this.bodyParams.type.trim()) { + return RocketChat.API.v1.failure('The bodyParam "type" is required'); + } + + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + if (findResult.statusCode) { + return findResult; + } + + if (findResult.t === this.bodyParams.type) { + return RocketChat.API.v1.failure('The private group type is the same as what it would be changed to.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult._id, 'roomType', this.bodyParams.type); + }); + + return RocketChat.API.v1.success({ + group: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) + }); + } +}); + +RocketChat.API.v1.addRoute('groups.unarchive', { authRequired: true }, { + post: function() { + const findResult = findPrivateGroupById({ roomId: this.bodyParams.roomId, userId: this.userId }); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('unarchiveRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); diff --git a/packages/rocketchat-api/server/v1/helpers/getPaginationItems.js b/packages/rocketchat-api/server/v1/helpers/getPaginationItems.js new file mode 100644 index 0000000000000000000000000000000000000000..dd1732df6c73657826654e4aee212be4cd2ab869 --- /dev/null +++ b/packages/rocketchat-api/server/v1/helpers/getPaginationItems.js @@ -0,0 +1,30 @@ +// If the count query param is higher than the "API_Upper_Count_Limit" setting, then we limit that +// If the count query param isn't defined, then we set it to the "API_Default_Count" setting +// If the count is zero, then that means unlimited and is only allowed if the setting "API_Allow_Infinite_Count" is true + +RocketChat.API.v1.helperMethods.set('getPaginationItems', function _getPaginationItems() { + const hardUpperLimit = RocketChat.settings.get('API_Upper_Count_Limit') <= 0 ? 100 : RocketChat.settings.get('API_Upper_Count_Limit'); + const defaultCount = RocketChat.settings.get('API_Default_Count') <= 0 ? 50 : RocketChat.settings.get('API_Default_Count'); + const offset = this.queryParams.offset ? parseInt(this.queryParams.offset) : 0; + let count = defaultCount; + + // Ensure count is an appropiate amount + if (typeof this.queryParams.count !== 'undefined') { + count = parseInt(this.queryParams.count); + } else { + count = defaultCount; + } + + if (count > hardUpperLimit) { + count = hardUpperLimit; + } + + if (count === 0 && !RocketChat.settings.get('API_Allow_Infinite_Count')) { + count = defaultCount; + } + + return { + offset, + count + }; +}); diff --git a/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js b/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js new file mode 100644 index 0000000000000000000000000000000000000000..8774549a7e15bf04ac0397e0cc6bdf37359da0d3 --- /dev/null +++ b/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js @@ -0,0 +1,35 @@ +//Convience method, almost need to turn it into a middleware of sorts +RocketChat.API.v1.helperMethods.set('getUserFromParams', function _getUserFromParams() { + const doesntExist = { _doesntExist: true }; + let user; + + switch (this.method) { + case 'POST': + case 'PUT': + if (this.bodyParams.userId && this.bodyParams.userId.trim()) { + user = RocketChat.models.Users.findOneById(this.bodyParams.userId) || doesntExist; + } else if (this.bodyParams.username && this.bodyParams.username.trim()) { + user = RocketChat.models.Users.findOneByUsername(this.bodyParams.username) || doesntExist; + } else if (this.bodyParams.user && this.bodyParams.user.trim()) { + user = RocketChat.models.Users.findOneByUsername(this.bodyParams.user) || doesntExist; + } + break; + default: + if (this.queryParams.userId && this.queryParams.userId.trim()) { + user = RocketChat.models.Users.findOneById(this.queryParams.userId) || doesntExist; + } else if (this.queryParams.username && this.queryParams.username.trim()) { + user = RocketChat.models.Users.findOneByUsername(this.queryParams.username) || doesntExist; + } else if (this.queryParams.user && this.queryParams.user.trim()) { + user = RocketChat.models.Users.findOneByUsername(this.queryParams.user) || doesntExist; + } + break; + } + + if (!user) { + throw new Meteor.Error('error-user-param-not-provided', 'The required "userId" or "username" param was not provided'); + } else if (user._doesntExist) { + throw new Meteor.Error('error-invalid-user', 'The required "userId" or "username" param provided does not match any users'); + } + + return user; +}); diff --git a/packages/rocketchat-api/server/v1/helpers/parseJsonQuery.js b/packages/rocketchat-api/server/v1/helpers/parseJsonQuery.js new file mode 100644 index 0000000000000000000000000000000000000000..073ebf4e83f9aa6a255e82a25f22092e9f51eb71 --- /dev/null +++ b/packages/rocketchat-api/server/v1/helpers/parseJsonQuery.js @@ -0,0 +1,37 @@ +RocketChat.API.v1.helperMethods.set('parseJsonQuery', function _parseJsonQuery() { + let sort; + if (this.queryParams.sort) { + try { + sort = JSON.parse(this.queryParams.sort); + } catch (e) { + this.logger.warn(`Invalid sort parameter provided "${this.queryParams.sort}":`, e); + throw new Meteor.Error('error-invalid-sort', `Invalid sort parameter provided: "${this.queryParams.sort}"`, { helperMethod: 'parseJsonQuery' }); + } + } + + let fields; + if (this.queryParams.fields) { + try { + fields = JSON.parse(this.queryParams.fields); + } catch (e) { + this.logger.warn(`Invalid fields parameter provided "${this.queryParams.fields}":`, e); + throw new Meteor.Error('error-invalid-fields', `Invalid fields parameter provided: "${this.queryParams.fields}"`, { helperMethod: 'parseJsonQuery' }); + } + } + + let query; + if (this.queryParams.query) { + try { + query = JSON.parse(this.queryParams.query); + } catch (e) { + this.logger.warn(`Invalid query parameter provided "${this.queryParams.query}":`, e); + throw new Meteor.Error('error-invalid-query', `Invalid query parameter provided: "${this.queryParams.query}"`, { helperMethod: 'parseJsonQuery' }); + } + } + + return { + sort, + fields, + query + }; +}); diff --git a/packages/rocketchat-api/server/v1/im.js b/packages/rocketchat-api/server/v1/im.js new file mode 100644 index 0000000000000000000000000000000000000000..78f2762cab65927d86455ebe6d230ccbd6e17db7 --- /dev/null +++ b/packages/rocketchat-api/server/v1/im.js @@ -0,0 +1,174 @@ +function findDirectMessageRoomById(roomId, userId) { + if (!roomId || !roomId.trim()) { + return RocketChat.API.v1.failure('Body param "roomId" is required'); + } + + const roomSub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, userId); + + if (!roomSub || roomSub.t !== 'd') { + return RocketChat.API.v1.failure(`No direct message room found by the id of: ${roomId}`); + } + + return roomSub; +} + +RocketChat.API.v1.addRoute('im.close', { authRequired: true }, { + post: function() { + const findResult = findDirectMessageRoomById(this.bodyParams.roomId, this.userId); + + //The find method returns either with the dm or the failure + if (findResult.statusCode) { + return findResult; + } + + if (!findResult.open) { + return RocketChat.API.v1.failure(`The direct message room, ${this.bodyParams.name}, is already closed to the sender`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('hideRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('im.history', { authRequired: true }, { + get: function() { + const findResult = findDirectMessageRoomById(this.queryParams.roomId, this.userId); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + let latestDate = new Date(); + if (this.queryParams.latest) { + latestDate = new Date(this.queryParams.latest); + } + + let oldestDate = undefined; + if (this.queryParams.oldest) { + oldestDate = new Date(this.queryParams.oldest); + } + + let inclusive = false; + if (this.queryParams.inclusive) { + inclusive = this.queryParams.inclusive; + } + + let count = 20; + if (this.queryParams.count) { + count = parseInt(this.queryParams.count); + } + + let unreads = false; + if (this.queryParams.unreads) { + unreads = this.queryParams.unreads; + } + + let result; + Meteor.runAsUser(this.userId, () => { + result = Meteor.call('getChannelHistory', { rid: findResult.rid, latest: latestDate, oldest: oldestDate, inclusive, count, unreads }); + }); + + return RocketChat.API.v1.success({ + messages: result && result.messages ? result.messages : [] + }); + } +}); + + +RocketChat.API.v1.addRoute('im.list', { authRequired: true }, { + get: function() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields } = this.parseJsonQuery(); + let rooms = _.pluck(RocketChat.models.Subscriptions.findByTypeAndUserId('d', this.userId).fetch(), '_room'); + const totalCount = rooms.length; + + rooms = RocketChat.models.Rooms.processQueryOptionsOnResult(rooms, { + sort: sort ? sort : { name: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }); + + return RocketChat.API.v1.success({ + ims: rooms, + offset, + count: rooms.length, + total: totalCount + }); + } +}); + +RocketChat.API.v1.addRoute('im.list.everyone', { authRequired: true }, { + get: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'view-room-administration')) { + return RocketChat.API.v1.unauthorized(); + } + + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + const ourQuery = Object.assign({}, query, { t: 'd' }); + + const rooms = RocketChat.models.Rooms.find(ourQuery, { + sort: sort ? sort : { name: 1 }, + skip: offset, + limit: count, + fields: Object.assign({}, fields, RocketChat.API.v1.defaultFieldsToExclude) + }).fetch(); + + return RocketChat.API.v1.success({ + ims: rooms, + offset, + count: rooms.length, + total: RocketChat.models.Rooms.find(ourQuery).count() + }); + } +}); + +RocketChat.API.v1.addRoute('im.open', { authRequired: true }, { + post: function() { + const findResult = findDirectMessageRoomById(this.bodyParams.roomId, this.userId); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + if (findResult.open) { + return RocketChat.API.v1.failure(`The direct message room, ${this.bodyParams.name}, is already open for the sender`); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('openRoom', findResult.rid); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('im.setTopic', { authRequired: true }, { + post: function() { + if (!this.bodyParams.topic || !this.bodyParams.topic.trim()) { + return RocketChat.API.v1.failure('The bodyParam "topic" is required'); + } + + const findResult = findDirectMessageRoomById(this.bodyParams.roomId, this.userId); + + //The find method returns either with the group or the failure + if (findResult.statusCode) { + return findResult; + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('saveRoomSettings', findResult.rid, 'roomTopic', this.bodyParams.topic); + }); + + return RocketChat.API.v1.success({ + topic: this.bodyParams.topic + }); + } +}); diff --git a/packages/rocketchat-api/server/v1/integrations.js b/packages/rocketchat-api/server/v1/integrations.js new file mode 100644 index 0000000000000000000000000000000000000000..9294dbb54135c1aa0799affe94d3dadce4930b77 --- /dev/null +++ b/packages/rocketchat-api/server/v1/integrations.js @@ -0,0 +1,98 @@ +RocketChat.API.v1.addRoute('integrations.create', { authRequired: true }, { + post: function() { + check(this.bodyParams, Match.ObjectIncluding({ + type: String, + name: String, + enabled: Boolean, + username: String, + urls: [String], + channel: Match.Maybe(String), + triggerWords: Match.Maybe([String]), + alias: Match.Maybe(String), + avatar: Match.Maybe(String), + emoji: Match.Maybe(String), + token: Match.Maybe(String), + scriptEnabled: Boolean, + script: Match.Maybe(String) + })); + + let integration; + + switch (this.bodyParams.type) { + case 'webhook-outgoing': + Meteor.runAsUser(this.userId, () => { + integration = Meteor.call('addOutgoingIntegration', this.bodyParams); + }); + break; + default: + return RocketChat.API.v1.failure('Invalid integration type.'); + } + + return RocketChat.API.v1.success({ integration }); + } +}); + +RocketChat.API.v1.addRoute('integrations.list', { authRequired: true }, { + get: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'manage-integrations')) { + return RocketChat.API.v1.unauthorized(); + } + + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + const ourQuery = Object.assign({}, query); + const integrations = RocketChat.models.Integrations.find(ourQuery, { + sort: sort ? sort : { ts: -1 }, + skip: offset, + limit: count, + fields + }).fetch(); + + return RocketChat.API.v1.success({ + integrations: integrations, + offset, + items: integrations.length, + total: RocketChat.models.Integrations.find(ourQuery).count() + }); + } +}); + +RocketChat.API.v1.addRoute('integrations.remove', { authRequired: true }, { + post: function() { + check(this.bodyParams, Match.ObjectIncluding({ + type: String, + target_url: Match.Maybe(String), + integrationId: Match.Maybe(String) + })); + + if (!this.bodyParams.target_url && !this.bodyParams.integrationId) { + return RocketChat.API.v1.failure('An integrationId or target_url needs to be provided.'); + } + + switch (this.bodyParams.type) { + case 'webhook-outgoing': + let integration; + + if (this.bodyParams.target_url) { + integration = RocketChat.models.Integrations.findOne({ urls: this.bodyParams.target_url }); + } else if (this.bodyParams.integrationId) { + integration = RocketChat.models.Integrations.findOne({ _id: this.bodyParams.integrationId }); + } + + if (!integration) { + return RocketChat.API.v1.failure('No integration found.'); + } + + Meteor.runAsUser(this.userId, () => { + Meteor.call('deleteOutgoingIntegration', integration._id); + }); + + return RocketChat.API.v1.success({ + integration: integration + }); + default: + return RocketChat.API.v1.failure('Invalid integration type.'); + } + } +}); diff --git a/packages/rocketchat-api/server/v1/misc.js b/packages/rocketchat-api/server/v1/misc.js new file mode 100644 index 0000000000000000000000000000000000000000..6a2a58027694e6240a9e7352cef74a940bf09aad --- /dev/null +++ b/packages/rocketchat-api/server/v1/misc.js @@ -0,0 +1,23 @@ +RocketChat.API.v1.addRoute('info', { authRequired: false }, { + get: function() { + return RocketChat.API.v1.success({ + info: RocketChat.Info + }); + } +}); + +RocketChat.API.v1.addRoute('me', { authRequired: true }, { + get: function() { + return RocketChat.API.v1.success(_.pick(this.user, [ + '_id', + 'name', + 'emails', + 'status', + 'statusConnection', + 'username', + 'utcOffset', + 'active', + 'language' + ])); + } +}); diff --git a/packages/rocketchat-api/server/v1/settings.js b/packages/rocketchat-api/server/v1/settings.js new file mode 100644 index 0000000000000000000000000000000000000000..3f6af353d7a3bddb9197f74900b1c4d832f1cd5b --- /dev/null +++ b/packages/rocketchat-api/server/v1/settings.js @@ -0,0 +1,56 @@ +// settings endpoints +RocketChat.API.v1.addRoute('settings', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + let ourQuery = { + hidden: { $ne: true } + }; + + if (!RocketChat.authz.hasPermission(this.userId, 'view-privileged-setting')) { + ourQuery.public = true; + } + + ourQuery = Object.assign({}, query, ourQuery); + + const settings = RocketChat.models.Settings.find(ourQuery, { + sort: sort ? sort : { _id: 1 }, + skip: offset, + limit: count, + fields: Object.assign({ _id: 1, value: 1 }, fields) + }).fetch(); + + return RocketChat.API.v1.success({ + settings, + count: settings.length, + offset, + total: RocketChat.models.Settings.find(ourQuery).count() + }); + } +}); + +RocketChat.API.v1.addRoute('settings/:_id', { authRequired: true }, { + get() { + if (!RocketChat.authz.hasPermission(this.userId, 'view-privileged-setting')) { + return RocketChat.API.v1.unauthorized(); + } + + return RocketChat.API.v1.success(_.pick(RocketChat.models.Settings.findOneNotHiddenById(this.urlParams._id), '_id', 'value')); + }, + post() { + if (!RocketChat.authz.hasPermission(this.userId, 'edit-privileged-setting')) { + return RocketChat.API.v1.unauthorized(); + } + + check(this.bodyParams, { + value: Match.Any + }); + + if (RocketChat.models.Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value)) { + return RocketChat.API.v1.success(); + } + + return RocketChat.API.v1.failure(); + } +}); diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js new file mode 100644 index 0000000000000000000000000000000000000000..151effe1d7a1b77f5a242e603d2ca9f22151a7ad --- /dev/null +++ b/packages/rocketchat-api/server/v1/users.js @@ -0,0 +1,213 @@ +RocketChat.API.v1.addRoute('users.create', { authRequired: true }, { + post: function() { + check(this.bodyParams, { + email: String, + name: String, + password: String, + username: String, + active: Match.Maybe(Boolean), + roles: Match.Maybe(Array), + joinDefaultChannels: Match.Maybe(Boolean), + requirePasswordChange: Match.Maybe(Boolean), + sendWelcomeEmail: Match.Maybe(Boolean), + verified: Match.Maybe(Boolean), + customFields: Match.Maybe(Object) + }); + + //New change made by pull request #5152 + if (typeof this.bodyParams.joinDefaultChannels === 'undefined') { + this.bodyParams.joinDefaultChannels = true; + } + + const newUserId = RocketChat.saveUser(this.userId, this.bodyParams); + + if (this.bodyParams.customFields) { + RocketChat.saveCustomFields(newUserId, this.bodyParams.customFields); + } + + if (typeof this.bodyParams.active !== 'undefined') { + Meteor.runAsUser(this.userId, () => { + Meteor.call('setUserActiveStatus', newUserId, this.bodyParams.active); + }); + } + + return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(newUserId, { fields: RocketChat.API.v1.defaultFieldsToExclude }) }); + } +}); + +RocketChat.API.v1.addRoute('users.delete', { authRequired: true }, { + post: function() { + if (!RocketChat.authz.hasPermission(this.userId, 'delete-user')) { + return RocketChat.API.v1.unauthorized(); + } + + const user = this.getUserFromParams(); + + Meteor.runAsUser(this.userId, () => { + Meteor.call('deleteUser', user._id); + }); + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('users.getPresence', { authRequired: true }, { + get: function() { + //BLAHHHHHHHHHH :'( + if ((this.queryParams.userId && this.userId !== this.queryParams.userId) || (this.queryParams.username && this.user.username !== this.queryParams.username) || (this.queryParams.user && this.user.username !== this.queryParams.user)) { + const user = this.getUserFromParams(); + + return RocketChat.API.v1.success({ + presence: user.status + }); + } + + const user = RocketChat.models.Users.findOneById(this.userId); + return RocketChat.API.v1.success({ + presence: user.status, + connectionStatus: user.statusConnection, + lastLogin: user.lastLogin + }); + } +}); + +RocketChat.API.v1.addRoute('users.info', { authRequired: true }, { + get: function() { + const user = this.getUserFromParams(); + + let result; + Meteor.runAsUser(this.userId, () => { + result = Meteor.call('getFullUserData', { filter: user.username, limit: 1 }); + }); + + if (!result || result.length !== 1) { + return RocketChat.API.v1.failure(`Failed to get the user data for the userId of "${user._id}".`); + } + + return RocketChat.API.v1.success({ + user: result[0] + }); + } +}); + +RocketChat.API.v1.addRoute('users.list', { authRequired: true }, { + get: function() { + const { offset, count } = this.getPaginationItems(); + const { sort, fields, query } = this.parseJsonQuery(); + + let fieldsToKeepFromRegularUsers; + if (!RocketChat.authz.hasPermission(this.userId, 'view-full-other-user-info')) { + fieldsToKeepFromRegularUsers = { + avatarOrigin: 0, + emails: 0, + phone: 0, + statusConnection: 0, + createdAt: 0, + lastLogin: 0, + services: 0, + requirePasswordChange: 0, + requirePasswordChangeReason: 0, + roles: 0, + statusDefault: 0, + _updatedAt: 0, + customFields: 0 + }; + } + + const ourQuery = Object.assign({}, query); + const ourFields = Object.assign({}, fields, fieldsToKeepFromRegularUsers, RocketChat.API.v1.defaultFieldsToExclude); + + const users = RocketChat.models.Users.find(ourQuery, { + sort: sort ? sort : { username: 1 }, + skip: offset, + limit: count, + fields: ourFields + }).fetch(); + + return RocketChat.API.v1.success({ + users, + count: users.length, + offset, + total: RocketChat.models.Users.find(ourQuery).count() + }); + } +}); + +//TODO: Make this route work with support for usernames +RocketChat.API.v1.addRoute('users.setAvatar', { authRequired: true }, { + post: function() { + check(this.bodyParams, { avatarUrl: Match.Maybe(String), userId: Match.Maybe(String) }); + + if (typeof this.bodyParams.userId !== 'undefined' && this.userId !== this.bodyParams.userId && !RocketChat.authz.hasPermission(this.userId, 'edit-other-user-info')) { + return RocketChat.API.v1.unauthorized(); + } + + const user = Meteor.users.findOne(this.bodyParams.userId ? this.bodyParams.userId : this.userId); + + if (this.bodyParams.avatarUrl) { + RocketChat.setUserAvatar(user, this.bodyParams.avatarUrl, '', 'url'); + } else { + const Busboy = Npm.require('busboy'); + const busboy = new Busboy({ headers: this.request.headers }); + + Meteor.wrapAsync((callback) => { + busboy.on('file', Meteor.bindEnvironment((fieldname, file, filename, encoding, mimetype) => { + if (fieldname !== 'image') { + return callback(new Meteor.Error('invalid-field')); + } + + const imageData = []; + file.on('data', Meteor.bindEnvironment((data) => { + imageData.push(data); + })); + + file.on('end', Meteor.bindEnvironment(() => { + RocketChat.setUserAvatar(user, Buffer.concat(imageData), mimetype, 'rest'); + callback(); + })); + + this.request.pipe(busboy); + })); + })(); + } + + return RocketChat.API.v1.success(); + } +}); + +RocketChat.API.v1.addRoute('users.update', { authRequired: true }, { + post: function() { + check(this.bodyParams, { + userId: String, + data: Match.ObjectIncluding({ + email: Match.Maybe(String), + name: Match.Maybe(String), + password: Match.Maybe(String), + username: Match.Maybe(String), + active: Match.Maybe(Boolean), + roles: Match.Maybe(Array), + joinDefaultChannels: Match.Maybe(Boolean), + requirePasswordChange: Match.Maybe(Boolean), + sendWelcomeEmail: Match.Maybe(Boolean), + verified: Match.Maybe(Boolean), + customFields: Match.Maybe(Object) + }) + }); + + const userData = _.extend({ _id: this.bodyParams.userId }, this.bodyParams.data); + + RocketChat.saveUser(this.userId, userData); + + if (this.bodyParams.data.customFields) { + RocketChat.saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields); + } + + if (typeof this.bodyParams.data.active !== 'undefined') { + Meteor.runAsUser(this.userId, () => { + Meteor.call('setUserActiveStatus', this.bodyParams.userId, this.bodyParams.data.active); + }); + } + + return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.bodyParams.userId, { fields: RocketChat.API.v1.defaultFieldsToExclude }) }); + } +}); diff --git a/packages/rocketchat-assets/package.js b/packages/rocketchat-assets/package.js index c39b215d6d2c249a81bede0272e640b711876cce..ff71e218bea3d92cdf26caecbd1171e33915711a 100644 --- a/packages/rocketchat-assets/package.js +++ b/packages/rocketchat-assets/package.js @@ -20,6 +20,5 @@ Package.onUse(function(api) { }); Npm.depends({ - 'image-size': '0.4.0', - 'mime-types': '2.1.9' + 'image-size': '0.4.0' }); diff --git a/packages/rocketchat-assets/server/assets.coffee b/packages/rocketchat-assets/server/assets.coffee index f7104791838b6b0850acf7ab6dca729f47c1f2ca..e210bdb5c9597c78826565f67b0291ecf1047bb4 100644 --- a/packages/rocketchat-assets/server/assets.coffee +++ b/packages/rocketchat-assets/server/assets.coffee @@ -18,7 +18,7 @@ assets = width: undefined height: undefined 'favicon_ico': - label: 'favicon.ico' + label: 'favicon (ico)' defaultUrl: 'favicon.ico' constraints: type: 'image' @@ -26,53 +26,101 @@ assets = width: undefined height: undefined 'favicon': - label: 'favicon.svg' + label: 'favicon (svg)' defaultUrl: 'images/logo/icon.svg' constraints: type: 'image' extensions: ['svg'] width: undefined height: undefined - 'favicon_64': - label: 'favicon.png (64x64)' - defaultUrl: 'images/logo/favicon-64x64.png' + 'favicon_16': + label: 'favicon 16x16 (png)' + defaultUrl: 'images/logo/favicon-16x16.png' constraints: type: 'image' extensions: ['png'] - width: 64 - height: 64 - 'favicon_96': - label: 'favicon.png (96x96)' - defaultUrl: 'images/logo/favicon-96x96.png' + width: 16 + height: 16 + 'favicon_32': + label: 'favicon 32x32 (png)' + defaultUrl: 'images/logo/favicon-32x32.png' constraints: type: 'image' extensions: ['png'] - width: 96 - height: 96 - 'favicon_128': - label: 'favicon.png (128x128)' - defaultUrl: 'images/logo/favicon-128x128.png' - constraints: - type: 'image' - extensions: ['png'] - width: 128 - height: 128 + width: 32 + height: 32 'favicon_192': - label: 'favicon.png (192x192)' + label: 'android-chrome 192x192 (png)' defaultUrl: 'images/logo/android-chrome-192x192.png' constraints: type: 'image' extensions: ['png'] width: 192 height: 192 - 'favicon_256': - label: 'favicon.png (256x256)' - defaultUrl: 'images/logo/favicon-256x256.png' + 'favicon_512': + label: 'android-chrome 512x512 (png)' + defaultUrl: 'images/logo/512x512.png' + constraints: + type: 'image' + extensions: ['png'] + width: 512 + height: 512 + 'touchicon_180': + label: 'apple-touch-icon 180x180 (png)' + defaultUrl: 'images/logo/apple-touch-icon.png' + constraints: + type: 'image' + extensions: ['png'] + width: 180 + height: 180 + 'touchicon_180_pre': + label: 'apple-touch-icon-precomposed 180x180 (png)' + defaultUrl: 'images/logo/apple-touch-icon-precomposed.png' + constraints: + type: 'image' + extensions: ['png'] + width: 180 + height: 180 + 'tile_144': + label: 'mstile 144x144 (png)' + defaultUrl: 'images/logo/mstile-144x144.png' + constraints: + type: 'image' + extensions: ['png'] + width: 144 + height: 144 + 'tile_150': + label: 'mstile 150x150 (png)' + defaultUrl: 'images/logo/mstile-150x150.png' constraints: type: 'image' extensions: ['png'] - width: 256 - height: 256 + width: 150 + height: 150 + 'tile_310_square': + label: 'mstile 310x310 (png)' + defaultUrl: 'images/logo/mstile-310x310.png' + constraints: + type: 'image' + extensions: ['png'] + width: 310 + height: 310 + 'tile_310_wide': + label: 'mstile 310x150 (png)' + defaultUrl: 'images/logo/mstile-310x150.png' + constraints: + type: 'image' + extensions: ['png'] + width: 310 + height: 150 + 'safari_pinned': + label: 'safari pinneed tab (svg)' + defaultUrl: 'images/logo/safari-pinned-tab.svg' + constraints: + type: 'image' + extensions: ['svg'] + width: undefined + height: undefined RocketChat.Assets = new class diff --git a/packages/rocketchat-authorization/client/hasPermission.coffee b/packages/rocketchat-authorization/client/hasPermission.coffee index 14fcd42ec36badc55816a0da71798be55c3095d2..c9861bebc2e91d43b5d614de2d8e51c0f9476a7c 100644 --- a/packages/rocketchat-authorization/client/hasPermission.coffee +++ b/packages/rocketchat-authorization/client/hasPermission.coffee @@ -1,7 +1,7 @@ atLeastOne = (permissions, scope) -> return _.some permissions, (permissionId) -> permission = ChatPermissions.findOne permissionId - return _.some permission.roles, (roleName) -> + return permission and _.some permission.roles, (roleName) -> role = RocketChat.models.Roles.findOne roleName roleScope = role?.scope return RocketChat.models[roleScope]?.isUserInRole?(Meteor.userId(), roleName, scope) diff --git a/packages/rocketchat-authorization/client/requiresPermission.html b/packages/rocketchat-authorization/client/requiresPermission.html new file mode 100644 index 0000000000000000000000000000000000000000..db71b5643888d1b2f0891412ca7a85be42041b21 --- /dev/null +++ b/packages/rocketchat-authorization/client/requiresPermission.html @@ -0,0 +1,11 @@ +<template name="requiresPermission"> + {{#if hasPermission this}} + {{> Template.contentBlock}} + {{else}} + {{#if Template.elseBlock}} + {{> Template.elseBlock}} + {{else}} + {{_ "Not_found_or_not_allowed"}} + {{/if}} + {{/if}} +</template> diff --git a/packages/rocketchat-authorization/client/stylesheets/load.coffee b/packages/rocketchat-authorization/client/stylesheets/load.coffee deleted file mode 100644 index 1e284cb4903eed0dc1dda7fd5c1aad79e537f3b6..0000000000000000000000000000000000000000 --- a/packages/rocketchat-authorization/client/stylesheets/load.coffee +++ /dev/null @@ -1 +0,0 @@ -RocketChat.theme.addPackageAsset -> Assets.getText 'client/stylesheets/permissions.less' diff --git a/packages/rocketchat-authorization/client/stylesheets/permissions.less b/packages/rocketchat-authorization/client/stylesheets/permissions.less index 756999f86a30d262860fa3a1bf2aac18269bb287..c970dbf8d1f2056a4eb8fcaf59e082ab8d8929c0 100644 --- a/packages/rocketchat-authorization/client/stylesheets/permissions.less +++ b/packages/rocketchat-authorization/client/stylesheets/permissions.less @@ -4,27 +4,29 @@ font-size: 16px; margin-top: 1em !important; margin-bottom: 1em !important; - border-bottom: 1px solid @tertiary-background-color; + border-bottom-width: 1px; } .permission-grid { th { white-space: normal; - text-align: center; - position: relative; - padding-top: 20px; + text-align: center; + position: relative; + padding-top: 20px; } + td { text-align: center; width: 10%; } + .icon-edit { font-size: 80%; position: absolute; - padding-left: 2px; - top: 0; - left: 50%; - transform: translateX(-50%); + padding-left: 2px; + top: 0; + left: 50%; + transform: translateX(-50%); } } diff --git a/packages/rocketchat-authorization/client/views/permissions.html b/packages/rocketchat-authorization/client/views/permissions.html index e9162bb589725ea969ced9cfe7102d4109b69438..3d53eb803e4227320ab43ed49ecedfb9b9529d92 100644 --- a/packages/rocketchat-authorization/client/views/permissions.html +++ b/packages/rocketchat-authorization/client/views/permissions.html @@ -2,12 +2,12 @@ <div class="permissions-manager"> {{#if hasPermission}} <a href="{{pathFor "admin-permissions-new"}}" class="button primary new-role">{{_ "New_role"}}</a> - <table border="1" class="permission-grid"> - <thead> + <table border="1" class="permission-grid secondary-background-color"> + <thead class="content-background-color"> <tr> - <th> </th> + <th class="border-component-color"> </th> {{#each role}} - <th title="{{description}}"> + <th class="border-component-color" title="{{description}}"> <a href="{{pathFor "admin-permissions-edit" name=_id}}"> {{_id}} <i class="icon-edit"></i> @@ -18,10 +18,10 @@ </thead> <tbody> {{#each permission}} - <tr> - <td>{{_id}}</td> + <tr class="admin-table-row"> + <td class="border-component-color">{{_id}}</td> {{#each role}} - <td> + <td class="border-component-color"> <input type="checkbox" name="perm[{{_id}}][{{../_id}}]" class="role-permission" value="1" checked="{{granted ../roles}}" data-role="{{_id}}" data-permission="{{../_id}}"> </td> {{/each}} diff --git a/packages/rocketchat-authorization/client/views/permissionsRole.html b/packages/rocketchat-authorization/client/views/permissionsRole.html index dd602f8794168d250cdd1caeb677cfda3950eede..4aaa9f69243162626bdbf1f78d6f8db612cd38b5 100644 --- a/packages/rocketchat-authorization/client/views/permissionsRole.html +++ b/packages/rocketchat-authorization/client/views/permissionsRole.html @@ -30,7 +30,7 @@ </form> {{/with}} {{#if editing}} - <h2>{{_ "Users_in_role"}}</h2> + <h2 class="border-tertiary-background-color">{{_ "Users_in_role"}}</h2> {{#if $eq role.scope 'Subscriptions'}} <form id="form-search-room" class="inline"> <label>{{_ "Choose_a_room"}}</label> diff --git a/packages/rocketchat-authorization/package.js b/packages/rocketchat-authorization/package.js index 8dac681dc7df62a3a84d488afbb1dc3794bdcbdc..6838b9aeed6b99a15365929074d2418a576fbebc 100644 --- a/packages/rocketchat-authorization/package.js +++ b/packages/rocketchat-authorization/package.js @@ -11,7 +11,8 @@ Package.onUse(function(api) { 'ecmascript', 'coffeescript', 'underscore', - 'rocketchat:lib' + 'rocketchat:lib', + 'less' ]); api.use('mongo', ['client', 'server']); @@ -31,6 +32,7 @@ Package.onUse(function(api) { api.addFiles('client/startup.coffee', ['client']); api.addFiles('client/hasPermission.coffee', ['client']); api.addFiles('client/hasRole.coffee', ['client']); + api.addFiles('client/requiresPermission.html', ['client']); api.addFiles('client/route.coffee', ['client']); @@ -41,8 +43,7 @@ Package.onUse(function(api) { api.addFiles('client/views/permissionsRole.coffee', ['client']); // stylesheets - api.addAssets('client/stylesheets/permissions.less', 'server'); - api.addFiles('client/stylesheets/load.coffee', 'server'); + api.addFiles('client/stylesheets/permissions.less', 'client'); api.addFiles('server/models/Permissions.coffee', ['server']); api.addFiles('server/models/Roles.coffee', ['server']); diff --git a/packages/rocketchat-authorization/server/functions/addUserRoles.coffee b/packages/rocketchat-authorization/server/functions/addUserRoles.coffee index 647c25d68269ad8695e18755c716f0dbd883d241..626b1769f235c781766001d716e95faa069b8707 100644 --- a/packages/rocketchat-authorization/server/functions/addUserRoles.coffee +++ b/packages/rocketchat-authorization/server/functions/addUserRoles.coffee @@ -2,7 +2,7 @@ RocketChat.authz.addUserRoles = (userId, roleNames, scope) -> if not userId or not roleNames return false - user = RocketChat.models.Users.findOneById(userId) + user = RocketChat.models.Users.db.findOneById(userId) if not user throw new Meteor.Error 'error-invalid-user', 'Invalid user', { function: 'RocketChat.authz.addUserRoles' } diff --git a/packages/rocketchat-authorization/server/functions/canAccessRoom.js b/packages/rocketchat-authorization/server/functions/canAccessRoom.js index e010678f67a80daf25a4d476dc0d229646760f64..5e41811e28f8a01c27db33af1e19bedca1174e38 100644 --- a/packages/rocketchat-authorization/server/functions/canAccessRoom.js +++ b/packages/rocketchat-authorization/server/functions/canAccessRoom.js @@ -1,7 +1,10 @@ /* globals RocketChat */ RocketChat.authz.roomAccessValidators = [ function(room, user) { - return room.usernames.indexOf(user.username) !== -1; + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); + if (subscription) { + return subscription._room; + } }, function(room, user) { if (room.t === 'c') { diff --git a/packages/rocketchat-authorization/server/models/Permissions.coffee b/packages/rocketchat-authorization/server/models/Permissions.coffee index 096917c69bb69e613c28aec2c1d0dfbee9756f57..3da9a835bef94db1bd6581141111317dbee7c133 100644 --- a/packages/rocketchat-authorization/server/models/Permissions.coffee +++ b/packages/rocketchat-authorization/server/models/Permissions.coffee @@ -22,4 +22,5 @@ class ModelPermissions extends RocketChat.models._Base @update({ _id: permission }, { $pull: { roles: role } }) -RocketChat.models.Permissions = new ModelPermissions('permissions') +RocketChat.models.Permissions = new ModelPermissions('permissions', true) +RocketChat.models.Permissions.cache.load() diff --git a/packages/rocketchat-authorization/server/publications/permissions.js b/packages/rocketchat-authorization/server/publications/permissions.js index e1e3072e13b4e6218522d0a16dc6f1b3cb226cdd..5180f9e852ca154861014db9853e4db58bd0c2bd 100644 --- a/packages/rocketchat-authorization/server/publications/permissions.js +++ b/packages/rocketchat-authorization/server/publications/permissions.js @@ -2,19 +2,22 @@ Meteor.methods({ 'permissions/get'(updatedAt) { this.unblock(); + const records = RocketChat.models.Permissions.find().fetch(); + if (updatedAt instanceof Date) { - return RocketChat.models.Permissions.dinamicFindChangesAfter('find', updatedAt); + return { + update: records.filter((record) => { + return record._updatedAt > updatedAt; + }), + remove: RocketChat.models.Permissions.trashFindDeletedAfter(updatedAt, {}, {fields: {_id: 1, _deletedAt: 1}}).fetch() + }; } - return RocketChat.models.Permissions.find().fetch(); + return records; } }); -RocketChat.models.Permissions.on('change', (type, ...args) => { - const records = RocketChat.models.Permissions.getChangedRecords(type, args[0]); - - for (const record of records) { - RocketChat.Notifications.notifyAll('permissions-changed', type, record); - } +RocketChat.models.Permissions.on('changed', (type, permission) => { + RocketChat.Notifications.notifyAllInThisInstance('permissions-changed', type, permission); }); diff --git a/packages/rocketchat-authorization/server/startup.coffee b/packages/rocketchat-authorization/server/startup.coffee index a6beed9bbd3a71cbc62d889be3b9e97a3d2f8e3e..1037dcaf5e8c76bcf980d6c2b35056e2fcf4fb97 100644 --- a/packages/rocketchat-authorization/server/startup.coffee +++ b/packages/rocketchat-authorization/server/startup.coffee @@ -13,10 +13,11 @@ Meteor.startup -> { _id: 'ban-user', roles : ['admin', 'owner', 'moderator'] } { _id: 'bulk-create-c', roles : ['admin'] } { _id: 'bulk-register-user', roles : ['admin'] } - { _id: 'create-c', roles : ['admin', 'user'] } - { _id: 'create-d', roles : ['admin', 'user'] } - { _id: 'create-p', roles : ['admin', 'user'] } + { _id: 'create-c', roles : ['admin', 'user', 'bot'] } + { _id: 'create-d', roles : ['admin', 'user', 'bot'] } + { _id: 'create-p', roles : ['admin', 'user', 'bot'] } { _id: 'create-user', roles : ['admin'] } + { _id: 'clean-channel-history', roles : ['admin'] } # special permission to bulk delete a channel's mesages { _id: 'delete-c', roles : ['admin'] } { _id: 'delete-d', roles : ['admin'] } { _id: 'delete-message', roles : ['admin', 'owner', 'moderator'] } diff --git a/packages/rocketchat-autolinker/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-autolinker/.npm/package/npm-shrinkwrap.json index 8bb6ab9d7a171af420d02223b5c0ac1fd886c8ed..469351e76fd8e7a6dc8a1fcb7e20db31ef742583 100644 --- a/packages/rocketchat-autolinker/.npm/package/npm-shrinkwrap.json +++ b/packages/rocketchat-autolinker/.npm/package/npm-shrinkwrap.json @@ -1,9 +1,9 @@ { "dependencies": { "autolinker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-1.3.2.tgz", - "from": "autolinker@1.3.2" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-1.4.0.tgz", + "from": "autolinker@1.4.0" } } } diff --git a/packages/rocketchat-autolinker/package.js b/packages/rocketchat-autolinker/package.js index eca73cbc98c88588c6cc9917a6083730909da9cf..5ce89297d45c8a36dda337bf8ccb6772aa854238 100644 --- a/packages/rocketchat-autolinker/package.js +++ b/packages/rocketchat-autolinker/package.js @@ -6,7 +6,7 @@ Package.describe({ }); Npm.depends({ - autolinker: '1.3.2' + autolinker: '1.4.0' }); Package.onUse(function(api) { diff --git a/packages/rocketchat-bot-helpers/server/index.js b/packages/rocketchat-bot-helpers/server/index.js index 9e514f880c6d8389f1ff7565ce120e22c6b19042..98fe5e9951f5759da1194ddb35fcb6ee05c24657 100644 --- a/packages/rocketchat-bot-helpers/server/index.js +++ b/packages/rocketchat-bot-helpers/server/index.js @@ -76,7 +76,7 @@ class BotHelpers { // "public" properties accessed by getters // allUsers / onlineUsers return whichever properties are enabled by settings get allUsers() { - if (!this.userFields.length) { + if (!Object.keys(this.userFields).length) { this.requestError(); return false; } else { @@ -84,7 +84,7 @@ class BotHelpers { } } get onlineUsers() { - if (!this.userFields.length) { + if (!Object.keys(this.userFields).length) { this.requestError(); return false; } else { diff --git a/packages/rocketchat-cas/cas_server.js b/packages/rocketchat-cas/cas_server.js index 4a37df7c2e1dd4d087570827997c2445c4fc86ab..555fa638307cff0222244a3e1a89c0ea9cd5daba 100644 --- a/packages/rocketchat-cas/cas_server.js +++ b/packages/rocketchat-cas/cas_server.js @@ -236,11 +236,6 @@ Accounts.registerLoginHandler(function(options) { logger.debug('Created new user for \'' + result.username + '\' with id: ' + user._id); //logger.debug(JSON.stringify(user, undefined, 4)); - logger.debug('Joining user to default channels'); - Meteor.runAsUser(user._id, function() { - Meteor.call('joinDefaultChannels'); - }); - logger.debug('Joining user to attribute channels: ' + int_attrs.rooms); if (int_attrs.rooms) { _.each(int_attrs.rooms.split(','), function(room_name) { diff --git a/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html b/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html index f6cf39d656df845c0cebcedb346975d1c78b12b1..50c148705a456dc2b5314a567cc6430a142fa962 100644 --- a/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html +++ b/packages/rocketchat-channel-settings-mail-messages/client/views/mailMessagesInstructions.html @@ -37,13 +37,13 @@ </div> </fieldset> </form> - <div class="error error-missing-to alert alert-danger" style="display: none"> + <div class="error error-missing-to alert error-color error-background error-border" style="display: none"> {{_ "Mail_Message_Missing_to"}} </div> - <div class="error error-invalid-emails alert alert-danger" style="display: none"> + <div class="error error-invalid-emails alert error-color error-background error-border" style="display: none"> {{_ "Mail_Message_Invalid_emails" erroredEmails}} </div> - <div class="error error-select alert alert-danger" style="display: none"> + <div class="error error-select alert error-color error-background error-border" style="display: none"> {{{_ "Mail_Message_No_messages_selected_select_all"}}} </div> <p style="margin-top: 30px"> diff --git a/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less b/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less index bbe3c25fc8ba328ab7b2c9cc475c68cd2485c475..5bfe3351fd9b6144f3a26dbfc72cbd8fe02b2107 100644 --- a/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less +++ b/packages/rocketchat-channel-settings/client/stylesheets/channel-settings.less @@ -6,29 +6,93 @@ } } - form { - label { - display: block; - font-weight: bold; - margin-bottom: 5px; + label { + display: block; + font-weight: bold; + margin-bottom: 5px; + font-size: 14px; + } + + .current-setting { + font-size: 14px; + width: calc(~"100% - 38px"); + display: inline-block; + vertical-align: middle; + min-height: 20px; + cursor: pointer; + margin-top: 3px; + + &[data-edit="false"] { + cursor: inherit; + user-select: initial; } + } - div span { - font-size: 14px; - i.icon-pencil { - font-size: 12px; - margin-left: 3px; - } + .editing { + padding-right: 80px; + font-size: 14px; + margin: -2px 0 -1px -9px; + } + + .buttons { + position: absolute; + top: -1px; + bottom: 0; + right: 10px; + border-radius: 0 4px 4px 0; + + .button { + padding: 8px; } } + .button.edit { + padding: 8px; + font-size: 12px; + vertical-align: middle; + display: inline-block; + visibility: hidden; + } + .submit { margin-top: 30px; text-align: center; } - [data-edit] { - cursor: pointer; + .boolean { + font-size: 0; + + > label { + width: calc(~"100% - 45px"); + display: inline-block; + vertical-align: middle; + } + + .setting-block { + width: 40px; + display: inline-block; + vertical-align: middle; + margin-left: -5px; + } + } + + .setting-block { + position: relative; + font-size: 0; + + .loading-animation { + top: 30px; + } + + &:hover { + .button.edit { + visibility: visible; + } + } + } + + nav { + text-align: right; } } } diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.coffee b/packages/rocketchat-channel-settings/client/views/channelSettings.coffee index b4670ec92ecc4c392ed3354077a8baaba13ef7e5..17bc6334bf42f0d24ff3f2fd096bdabd160229db 100644 --- a/packages/rocketchat-channel-settings/client/views/channelSettings.coffee +++ b/packages/rocketchat-channel-settings/client/views/channelSettings.coffee @@ -25,6 +25,9 @@ Template.channelSettings.helpers editing: (field) -> return Template.instance().editing.get() is field + isDisabled: (field, room) -> + return Template.instance().settings[field].processing.get() or !RocketChat.authz.hasAllPermission('edit-room', room._id) + channelSettings: -> return RocketChat.ChannelSettings.getOptions() @@ -34,8 +37,10 @@ Template.channelSettings.helpers canDeleteRoom: -> roomType = ChatRoom.findOne(@rid, { fields: { t: 1 }})?.t return roomType? and RocketChat.authz.hasAtLeastOnePermission("delete-#{roomType}", @rid) + readOnly: -> return ChatRoom.findOne(@rid, { fields: { ro: 1 }})?.ro + readOnlyDescription: -> readOnly = ChatRoom.findOne(@rid, { fields: { ro: 1 }})?.ro if readOnly is true @@ -79,8 +84,16 @@ Template.channelSettings.events 'click [data-edit]': (e, t) -> e.preventDefault() - t.editing.set($(e.currentTarget).data('edit')) - setTimeout (-> t.$('input.editing').focus().select()), 100 + if $(e.currentTarget).data('edit') + t.editing.set($(e.currentTarget).data('edit')) + setTimeout (-> t.$('input.editing').focus().select()), 100 + + 'change [type="radio"]': (e, t) -> + t.editing.set($(e.currentTarget).attr('name')) + + 'change [type="checkbox"]': (e, t) -> + t.editing.set($(e.currentTarget).attr('name')) + t.saveSetting() 'click .cancel': (e, t) -> e.preventDefault() @@ -138,48 +151,86 @@ Template.channelSettings.onCreated -> toastr.success TAPi18n.__ 'Room_description_changed_successfully' t: - type: 'select' - label: 'Type' - options: - c: 'Channel' - p: 'Private_Group' - canView: (room) => room.t in ['c', 'p'] + type: 'boolean' + label: 'Private' + isToggle: true + processing: new ReactiveVar(false) + canView: (room) -> + if not room.t in ['c', 'p'] + return false + else if room.t is 'p' and not RocketChat.authz.hasAllPermission('create-c') + return false + else if room.t is 'c' and not RocketChat.authz.hasAllPermission('create-p') + return false + return true canEdit: (room) => RocketChat.authz.hasAllPermission('edit-room', room._id) save: (value, room) -> - if value not in ['c', 'p'] - return toastr.error t('error-invalid-room-type', value) - + @processing.set(true) + value = if value then 'p' else 'c' RocketChat.callbacks.run 'roomTypeChanged', room - Meteor.call 'saveRoomSettings', room._id, 'roomType', value, (err, result) -> + Meteor.call 'saveRoomSettings', room._id, 'roomType', value, (err, result) => return handleError err if err + @processing.set(false) toastr.success TAPi18n.__ 'Room_type_changed_successfully' ro: type: 'boolean' label: 'Read_only' + isToggle: true + processing: new ReactiveVar(false) canView: (room) => room.t isnt 'd' canEdit: (room) => RocketChat.authz.hasAllPermission('set-readonly', room._id) save: (value, room) -> - Meteor.call 'saveRoomSettings', room._id, 'readOnly', value, (err, result) -> + @processing.set(true) + Meteor.call 'saveRoomSettings', room._id, 'readOnly', value, (err, result) => return handleError err if err + @processing.set(false) toastr.success TAPi18n.__ 'Read_only_changed_successfully' + reactWhenReadOnly: + type: 'boolean' + label: 'React_when_read_only' + canView: (room) => room.t isnt 'd' and room.ro + canEdit: (room) => RocketChat.authz.hasAllPermission('set-react-when-readonly', room._id) + save: (value, room) -> + Meteor.call 'saveRoomSettings', room._id, 'reactWhenReadOnly', value, (err, result) -> + return handleError err if err + toastr.success TAPi18n.__ 'React_when_read_only_changed_successfully' + archived: type: 'boolean' label: 'Room_archivation_state_true' + isToggle: true, + processing: new ReactiveVar(false) canView: (room) => room.t isnt 'd' canEdit: (room) => RocketChat.authz.hasAtLeastOnePermission(['archive-room', 'unarchive-room'], room._id) - save: (value, room) -> - if value is true - Meteor.call 'archiveRoom', room._id, (err, results) -> - return handleError err if err - toastr.success TAPi18n.__ 'Room_archived' - RocketChat.callbacks.run 'archiveRoom', room - else - Meteor.call 'unarchiveRoom', room._id, (err, results) -> - return handleError err if err - toastr.success TAPi18n.__ 'Room_unarchived' - RocketChat.callbacks.run 'unarchiveRoom', room + save: (value, room) => + swal { + title: t('Are_you_sure') + type: 'warning' + showCancelButton: true + confirmButtonColor: '#DD6B55' + confirmButtonText: if value then t('Yes_archive_it') else t('Yes_unarchive_it') + cancelButtonText: t('Cancel') + closeOnConfirm: false + html: false + }, (confirmed) => + swal.disableButtons() + if (confirmed) + action = if value then 'archiveRoom' else 'unarchiveRoom' + Meteor.call action, room._id, (err, results) => + if err + swal.enableButtons() + handleError err + swal + title: if value then t('Room_archived') else t('Room_has_been_archived') + text: if value then t('Room_has_been_archived') else t('Room_has_been_unarchived') + type: 'success' + timer: 2000 + showConfirmButton: false + RocketChat.callbacks.run action, room + else + $(".channel-settings form [name='archived']").prop('checked', room.archived) joinCode: type: 'text' @@ -200,7 +251,7 @@ Template.channelSettings.onCreated -> if @settings[field].type is 'select' value = @$(".channel-settings form [name=#{field}]:checked").val() else if @settings[field].type is 'boolean' - value = @$(".channel-settings form [name=#{field}]:checked").val() is 'true' + value = @$(".channel-settings form [name=#{field}]").is(":checked") else value = @$(".channel-settings form [name=#{field}]").val() diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.html b/packages/rocketchat-channel-settings/client/views/channelSettings.html index b253a257b16d24e6ffeb40c4beb438028fbd5748..562590be0c78605fdd7f4eed3920e782267466ad 100644 --- a/packages/rocketchat-channel-settings/client/views/channelSettings.html +++ b/packages/rocketchat-channel-settings/client/views/channelSettings.html @@ -11,63 +11,68 @@ {{#if $value.canView room}} {{#let value=(valueOf room $key)}} - <li> + <li class="{{$value.type}}"> <label>{{_ $value.label}}</label> - <div> + <div class="setting-block"> {{#if $eq $value.type 'text'}} {{#if editing $key}} - <input type="text" name="{{$key}}" value="{{value}}" class="editing" /> + {{#if $value.canEdit room}} + <input type="text" name="{{$key}}" value="{{value}}" class="content-background-color editing" /> + {{/if}} {{else}} - <span class='current-setting'>{{value}}</span> + <span class='current-setting' data-edit="{{#if $value.canEdit room}}{{$key}}{{else}}false{{/if}}">{{value}}</span> {{/if}} {{/if}} {{#if $eq $value.type 'markdown'}} {{#if editing $key}} - <input type="text" name="{{$key}}" value="{{unscape value}}" class="editing" /> + {{#if $value.canEdit room}} + <input type="text" name="{{$key}}" value="{{unscape value}}" class="content-background-color editing" /> + {{/if}} {{else}} - <span class='current-setting'>{{{RocketChatMarkdown value}}}</span> + <span class='current-setting' data-edit="{{#if $value.canEdit room}}{{$key}}{{else}}false{{/if}}">{{{RocketChatMarkdown value}}}</span> {{/if}} {{/if}} {{#if $eq $value.type 'select'}} - {{#if editing $key}} - {{#each toArray $value.options}} - <label> - <input type="radio" name="{{../$key}}" value="{{$key}}" checked="{{$eq value $key}}" class="editing" /> - {{_ $value}} - </label> - {{/each}} - {{else}} - <span class='current-setting'>{{_ (valueOf $value.options value)}}</span> - {{/if}} + {{#each toArray $value.options}} + <div class="input radio"> + <input type="radio" id="{{$key}}" name="{{../$key}}" value="{{$key}}" checked="{{$eq value $key}}" disabled="{{isDisabled $key room}}" /> + <label for="{{$key}}">{{_ $value}}</label> + </div> + {{/each}} {{/if}} {{#if $eq $value.type 'boolean'}} - {{#if editing $key}} - <label> - <input type="radio" name="{{$key}}" value="true" checked="{{$eq value true}}" class="editing" /> - {{_ 'True'}} - </label> - <label> - <input type="radio" name="{{$key}}" value="false" checked="{{$neq value true}}" class="editing" /> - {{_ 'False'}} - </label> - {{else}} - {{#if value}} - <span class='current-setting'>{{_ 'True'}}</span> - {{else}} - <span class='current-setting'>{{_ 'False'}}</span> + <div class="input checkbox toggle"> + <input type="checkbox" id="{{$key}}" name="{{$key}}" value="{{value}}" checked="{{$eq value true}}" disabled="{{isDisabled $key room}}" /> + <label for="{{$key}}"></label> + </div> + {{#if $value.canEdit room}} + {{#if $value.processing.get}} + {{> loading}} {{/if}} {{/if}} {{/if}} - {{#if editing $key}} - <button type="button" class="button cancel">{{_ "Cancel"}}</button> - <button type="button" class="button primary save">{{_ "Save"}}</button> - {{else}} - <span>{{#if $value.canEdit room}} <i class="icon-pencil" data-edit="{{$key}}"></i>{{/if}}</span> - {{/if}} + {{#unless $value.isToggle}} + {{#if $value.canEdit room}} + {{#if editing $key}} + <div class="buttons secondary-background-color"> + <button type="button" class="button cancel"> + <i class="icon-cancel"></i> + </button> + <button type="button" class="button primary save"> + <i class="icon-ok success-color"></i> + </button> + </div> + {{else}} + <button type="button" class="button edit"> + <i class="icon-pencil" data-edit="{{$key}}"></i> + </button> + {{/if}} + {{/if}} + {{/unless}} </div> </li> {{/let}} @@ -81,9 +86,9 @@ </ul> </form> {{#if canDeleteRoom}} - <nav> - <button class='button danger delete'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button> - </nav> + <nav> + <button class="button danger delete" title="{{_ 'Delete'}}"><i class="icon-trash"></i></button> + </nav> {{/if}} </div> </div> diff --git a/packages/rocketchat-channel-settings/package.js b/packages/rocketchat-channel-settings/package.js index dfc77ee32bfc53d6e2968db43a2e029fc0ccb259..3f42e734af3ffc157c307ce48e1ad588259e2ce5 100644 --- a/packages/rocketchat-channel-settings/package.js +++ b/packages/rocketchat-channel-settings/package.js @@ -27,6 +27,7 @@ Package.onUse(function(api) { ], 'client'); api.addFiles([ + 'server/functions/saveReactWhenReadOnly.js', 'server/functions/saveRoomType.coffee', 'server/functions/saveRoomTopic.coffee', 'server/functions/saveRoomName.coffee', diff --git a/packages/rocketchat-channel-settings/server/functions/saveReactWhenReadOnly.js b/packages/rocketchat-channel-settings/server/functions/saveReactWhenReadOnly.js new file mode 100644 index 0000000000000000000000000000000000000000..61cf8128df04d81cfe6bce4b7762c7c897aec66d --- /dev/null +++ b/packages/rocketchat-channel-settings/server/functions/saveReactWhenReadOnly.js @@ -0,0 +1,7 @@ +RocketChat.saveReactWhenReadOnly = function(rid, allowReact) { + if (!Match.test(rid, String)) { + throw new Meteor.Error('invalid-room', 'Invalid room', { function: 'RocketChat.saveReactWhenReadOnly' }); + } + + return RocketChat.models.Rooms.setAllowReactingWhenReadOnlyById(rid, allowReact); +}; diff --git a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee index c2aadcadd3a68a6257ed1f4c0374b72db7f2398b..f51a06949452d3394301bc73bf014097ead5cb84 100644 --- a/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee +++ b/packages/rocketchat-channel-settings/server/methods/saveRoomSettings.coffee @@ -6,7 +6,7 @@ Meteor.methods unless Match.test rid, String throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'saveRoomSettings' } - if setting not in ['roomName', 'roomTopic', 'roomDescription', 'roomType', 'readOnly', 'systemMessages', 'default', 'joinCode'] + if setting not in ['roomName', 'roomTopic', 'roomDescription', 'roomType', 'readOnly', 'reactWhenReadOnly', 'systemMessages', 'default', 'joinCode'] throw new Meteor.Error 'error-invalid-settings', 'Invalid settings provided', { method: 'saveRoomSettings' } unless RocketChat.authz.hasPermission(Meteor.userId(), 'edit-room', rid) @@ -17,6 +17,12 @@ Meteor.methods room = RocketChat.models.Rooms.findOneById rid if room? + if setting is 'roomType' and value isnt room.t and value is 'c' and not RocketChat.authz.hasPermission(@userId, 'create-c') + throw new Meteor.Error 'error-action-not-allowed', 'Changing a private group to a public channel is not allowed', { method: 'saveRoomSettings', action: 'Change_Room_Type' } + + if setting is 'roomType' and value isnt room.t and value is 'p' and not RocketChat.authz.hasPermission(@userId, 'create-p') + throw new Meteor.Error 'error-action-not-allowed', 'Changing a public channel to a private room is not allowed', { method: 'saveRoomSettings', action: 'Change_Room_Type' } + switch setting when 'roomName' name = RocketChat.saveRoomName rid, value, Meteor.user() @@ -32,6 +38,9 @@ Meteor.methods when 'readOnly' if value isnt room.ro RocketChat.saveRoomReadOnly rid, value, Meteor.user() + when 'reactWhenReadOnly' + if value isnt room.reactWhenReadOnly + RocketChat.saveReactWhenReadOnly rid, value, Meteor.user() when 'systemMessages' if value isnt room.sysMes RocketChat.saveRoomSystemMessages rid, value, Meteor.user() diff --git a/packages/rocketchat-channel-settings/server/models/Rooms.coffee b/packages/rocketchat-channel-settings/server/models/Rooms.coffee index f4328cebf9d531d8132b71446b6fe43694e830ad..d277959fe754678795f4261a174c083a90a4a0b4 100644 --- a/packages/rocketchat-channel-settings/server/models/Rooms.coffee +++ b/packages/rocketchat-channel-settings/server/models/Rooms.coffee @@ -19,9 +19,11 @@ RocketChat.models.Rooms.setReadOnlyById = (_id, readOnly) -> if readOnly # we want to mute all users without the post-readonly permission - usernames = @findOne(query, { fields: { usernames: 1 }}) - users = RocketChat.models.Users.findUsersByUsernames usernames?.usernames, {fields: {username: 1}} - users.forEach (user) -> + RocketChat.models.Subscriptions.findByRoomId(_id).forEach (subscription) -> + if not subscription._user? + return + + user = subscription._user if RocketChat.authz.hasPermission(user._id, 'post-readonly') is false # create a new array if necessary update.$set.muted = [] if !update.$set.muted @@ -32,6 +34,16 @@ RocketChat.models.Rooms.setReadOnlyById = (_id, readOnly) -> return @update query, update +RocketChat.models.Rooms.setAllowReactingWhenReadOnlyById = (_id, allowReacting) -> + query = + _id: _id + + update = + $set: + reactWhenReadOnly: allowReacting + + return @update query, update + RocketChat.models.Rooms.setSystemMessagesById = (_id, systemMessages) -> query = _id: _id diff --git a/packages/rocketchat-channel-settings/server/startup.js b/packages/rocketchat-channel-settings/server/startup.js index a881ce9496a4439481bdfafd7ed081760b92bba0..b63c0fabf67fcf666427f6492de7558a3faf3175 100644 --- a/packages/rocketchat-channel-settings/server/startup.js +++ b/packages/rocketchat-channel-settings/server/startup.js @@ -1,4 +1,5 @@ Meteor.startup(function() { RocketChat.models.Permissions.upsert('post-readonly', {$set: { roles: ['admin', 'owner', 'moderator'] } }); RocketChat.models.Permissions.upsert('set-readonly', {$set: { roles: ['admin', 'owner'] } }); + RocketChat.models.Permissions.upsert('set-react-when-readonly', {$set: { roles: ['admin', 'owner'] }}); }); diff --git a/packages/rocketchat-chatops/client/views/codemirror.html b/packages/rocketchat-chatops/client/views/codemirror.html index e185ab09607e1c814ab5769449534498f2cf28ef..6333d8416805ffd7063f8b4675834783e4be01e4 100644 --- a/packages/rocketchat-chatops/client/views/codemirror.html +++ b/packages/rocketchat-chatops/client/views/codemirror.html @@ -4,7 +4,7 @@ <form class="search-form" role="form"> <div class="input-line search"> <input type="text" id="room-search" class="search" placeholder="{{tQuickSearch}}" autocomplete="off" /> - <i class="icon-search"></i> + <i class="icon-search secondary-font-color"></i> </div> </form> </div> diff --git a/packages/rocketchat-crowd/server/crowd.js b/packages/rocketchat-crowd/server/crowd.js index d30fe4307d016b1f43fd47387f48947dd51c1978..65c6f5427260f1482f9258a86737ffa0d97758f1 100644 --- a/packages/rocketchat-crowd/server/crowd.js +++ b/packages/rocketchat-crowd/server/crowd.js @@ -170,10 +170,6 @@ const CROWD = class CROWD { }); } - Meteor.runAsUser(crowdUser._id, function() { - Meteor.call('joinDefaultChannels'); - }); - return { userId: crowdUser._id }; diff --git a/packages/rocketchat-custom-oauth/custom_oauth_client.coffee b/packages/rocketchat-custom-oauth/custom_oauth_client.coffee deleted file mode 100644 index f0d7338cfc74ee98e75c60dbbc3e253581a3ed6a..0000000000000000000000000000000000000000 --- a/packages/rocketchat-custom-oauth/custom_oauth_client.coffee +++ /dev/null @@ -1,79 +0,0 @@ -# Request custom OAuth credentials for the user -# @param options {optional} -# @param credentialRequestCompleteCallback {Function} Callback function to call on -# completion. Takes one argument, credentialToken on success, or Error on -# error. -class CustomOAuth - constructor: (@name, options) -> - if not Match.test @name, String - return throw new Meteor.Error 'CustomOAuth: Name is required and must be String' - - @configure options - - Accounts.oauth.registerService @name - - @configureLogin() - - configure: (options) -> - if not Match.test options, Object - return throw new Meteor.Error 'CustomOAuth: Options is required and must be Object' - - if not Match.test options.serverURL, String - return throw new Meteor.Error 'CustomOAuth: Options.serverURL is required and must be String' - - if not Match.test options.authorizePath, String - options.authorizePath = '/oauth/authorize' - - if not Match.test options.scope, String - options.scope = 'openid' - - @serverURL = options.serverURL - @authorizePath = options.authorizePath - @scope = options.scope - - if not /^https?:\/\/.+/.test @authorizePath - @authorizePath = @serverURL + @authorizePath - - configureLogin: -> - self = @ - loginWithService = "loginWith" + s.capitalize(@name) - - Meteor[loginWithService] = (options, callback) -> - # support a callback without options - if not callback and typeof options is "function" - callback = options - options = null - - credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback) - self.requestCredential(options, credentialRequestCompleteCallback) - - requestCredential: (options, credentialRequestCompleteCallback) -> - # support both (options, callback) and (callback). - if not credentialRequestCompleteCallback and typeof options is 'function' - credentialRequestCompleteCallback = options - options = {} - - config = ServiceConfiguration.configurations.findOne service: @name - if not config - credentialRequestCompleteCallback? new ServiceConfiguration.ConfigError() - return - - credentialToken = Random.secret() - loginStyle = OAuth._loginStyle @name, config, options - - loginUrl = @authorizePath + - '?client_id=' + config.clientId + - '&redirect_uri=' + OAuth._redirectUri(@name, config) + - '&response_type=code' + - '&state=' + OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl) + - '&scope=' + @scope - - OAuth.launchLogin - loginService: @name - loginStyle: loginStyle - loginUrl: loginUrl - credentialRequestCompleteCallback: credentialRequestCompleteCallback - credentialToken: credentialToken - popupOptions: - width: 900 - height: 450 diff --git a/packages/rocketchat-custom-oauth/custom_oauth_client.js b/packages/rocketchat-custom-oauth/custom_oauth_client.js new file mode 100644 index 0000000000000000000000000000000000000000..b3c88e9955d8af19761ce25f466c57536136e97f --- /dev/null +++ b/packages/rocketchat-custom-oauth/custom_oauth_client.js @@ -0,0 +1,100 @@ +/*globals OAuth*/ +// Request custom OAuth credentials for the user +// @param options {optional} +// @param credentialRequestCompleteCallback {Function} Callback function to call on +// completion. Takes one argument, credentialToken on success, or Error on +// error. + +export class CustomOAuth { + constructor(name, options) { + this.name = name; + if (!Match.test(this.name, String)) { + throw new Meteor.Error('CustomOAuth: Name is required and must be String'); + } + + this.configure(options); + + Accounts.oauth.registerService(this.name); + + this.configureLogin(); + } + + configure(options) { + if (!Match.test(options, Object)) { + throw new Meteor.Error('CustomOAuth: Options is required and must be Object'); + } + + if (!Match.test(options.serverURL, String)) { + throw new Meteor.Error('CustomOAuth: Options.serverURL is required and must be String'); + } + + if (!Match.test(options.authorizePath, String)) { + options.authorizePath = '/oauth/authorize'; + } + + if (!Match.test(options.scope, String)) { + options.scope = 'openid'; + } + + this.serverURL = options.serverURL; + this.authorizePath = options.authorizePath; + this.scope = options.scope; + + if (!/^https?:\/\/.+/.test(this.authorizePath)) { + this.authorizePath = this.serverURL + this.authorizePath; + } + } + + configureLogin() { + const loginWithService = 'loginWith' + s.capitalize(this.name); + + Meteor[loginWithService] = (options, callback) => { + // support a callback without options + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + + const credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + this.requestCredential(options, credentialRequestCompleteCallback); + }; + } + + requestCredential(options, credentialRequestCompleteCallback) { + // support both (options, callback) and (callback). + if (!credentialRequestCompleteCallback && typeof options === 'function') { + credentialRequestCompleteCallback = options; + options = {}; + } + + const config = ServiceConfiguration.configurations.findOne({service: this.name}); + if (!config) { + if (credentialRequestCompleteCallback) { + credentialRequestCompleteCallback(new ServiceConfiguration.ConfigError()); + } + return; + } + + const credentialToken = Random.secret(); + const loginStyle = OAuth._loginStyle(this.name, config, options); + + const loginUrl = this.authorizePath + + '?client_id=' + config.clientId + + '&redirect_uri=' + OAuth._redirectUri(this.name, config) + + '&response_type=code' + + '&state=' + OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl) + + '&scope=' + this.scope; + + OAuth.launchLogin({ + loginService: this.name, + loginStyle: loginStyle, + loginUrl: loginUrl, + credentialRequestCompleteCallback: credentialRequestCompleteCallback, + credentialToken: credentialToken, + popupOptions: { + width: 900, + height: 450 + } + }); + } +} diff --git a/packages/rocketchat-custom-oauth/custom_oauth_server.coffee b/packages/rocketchat-custom-oauth/custom_oauth_server.coffee deleted file mode 100644 index e556ff0b72e73af17c7311a5b546f2d5c905e258..0000000000000000000000000000000000000000 --- a/packages/rocketchat-custom-oauth/custom_oauth_server.coffee +++ /dev/null @@ -1,162 +0,0 @@ -Services = {} - -class CustomOAuth - constructor: (@name, options) -> - if not Match.test @name, String - return throw new Meteor.Error 'CustomOAuth: Name is required and must be String' - - if Services[@name]? - Services[@name].configure options - return - - Services[@name] = @ - - @configure options - - @userAgent = "Meteor" - if Meteor.release - @userAgent += '/' + Meteor.release - - Accounts.oauth.registerService @name - @registerService() - - configure: (options) -> - if not Match.test options, Object - return throw new Meteor.Error 'CustomOAuth: Options is required and must be Object' - - if not Match.test options.serverURL, String - return throw new Meteor.Error 'CustomOAuth: Options.serverURL is required and must be String' - - if not Match.test options.tokenPath, String - options.tokenPath = '/oauth/token' - - if not Match.test options.identityPath, String - options.identityPath = '/me' - - @serverURL = options.serverURL - @tokenPath = options.tokenPath - @identityPath = options.identityPath - @tokenSentVia = options.tokenSentVia - - if not /^https?:\/\/.+/.test @tokenPath - @tokenPath = @serverURL + @tokenPath - - if not /^https?:\/\/.+/.test @identityPath - @identityPath = @serverURL + @identityPath - - if Match.test options.addAutopublishFields, Object - Accounts.addAutopublishFields options.addAutopublishFields - - getAccessToken: (query) -> - config = ServiceConfiguration.configurations.findOne service: @name - if not config? - throw new ServiceConfiguration.ConfigError() - - response = undefined - try - response = HTTP.post @tokenPath, - auth: config.clientId + ':' + OAuth.openSecret(config.secret) - headers: - Accept: 'application/json' - 'User-Agent': @userAgent - params: - code: query.code - client_id: config.clientId - client_secret: OAuth.openSecret(config.secret) - redirect_uri: OAuth._redirectUri(@name, config) - grant_type: 'authorization_code' - state: query.state - - catch err - error = new Error("Failed to complete OAuth handshake with #{@name} at #{@tokenPath}. " + err.message) - throw _.extend error, {response: err.response} - - if response.data.error #if the http response was a json object with an error attribute - throw new Error("Failed to complete OAuth handshake with #{@name} at #{@tokenPath}. " + response.data.error) - else - return response.data.access_token - - getIdentity: (accessToken) -> - params = {} - headers = - 'User-Agent': @userAgent # http://doc.gitlab.com/ce/api/users.html#Current-user - - if @tokenSentVia is 'header' - headers['Authorization'] = 'Bearer ' + accessToken - else - params['access_token'] = accessToken - - try - response = HTTP.get @identityPath, - headers: headers - params: params - - if response.data - return response.data - else - return JSON.parse response.content - - catch err - error = new Error("Failed to fetch identity from #{@name} at #{@identityPath}. " + err.message) - throw _.extend error, {response: err.response} - - registerService: -> - self = @ - OAuth.registerService @name, 2, null, (query) -> - accessToken = self.getAccessToken query - # console.log 'at:', accessToken - - identity = self.getIdentity accessToken - - # Fix for Reddit - if identity?.result - identity = identity.result - - # Fix WordPress-like identities having 'ID' instead of 'id' - if identity?.ID and not identity.id - identity.id = identity.ID - - # Fix Auth0-like identities having 'user_id' instead of 'id' - if identity?.user_id and not identity.id - identity.id = identity.user_id - - if identity?.CharacterID and not identity.id - identity.id = identity.CharacterID - - # Fix Dataporten having 'user.userid' instead of 'id' - if identity?.user?.userid and not identity.id - identity.id = identity.user.userid - identity.email = identity.user.email - - # Fix general 'phid' instead of 'id' from phabricator - if identity?.phid and not identity.id - identity.id = identity.phid - - # Fix Keycloak-like identities having 'sub' instead of 'id' - if identity?.sub and not identity.id - identity.id = identity.sub - - # Fix general 'userid' instead of 'id' from provider - if identity?.userid and not identity.id - identity.id = identity.userid - - # console.log 'id:', JSON.stringify identity, null, ' ' - - serviceData = - _OAuthCustom: true - accessToken: accessToken - - _.extend serviceData, identity - - data = - serviceData: serviceData - options: - profile: - name: identity.name or identity.username or identity.nickname or identity.CharacterName or identity.userName or identity.preferred_username or identity.user?.name - - # console.log data - - return data - - retrieveCredential: (credentialToken, credentialSecret) -> - return OAuth.retrieveCredential credentialToken, credentialSecret diff --git a/packages/rocketchat-custom-oauth/custom_oauth_server.js b/packages/rocketchat-custom-oauth/custom_oauth_server.js new file mode 100644 index 0000000000000000000000000000000000000000..84abcb7df8b2f82a7d4aa592cf025379f97be942 --- /dev/null +++ b/packages/rocketchat-custom-oauth/custom_oauth_server.js @@ -0,0 +1,298 @@ +/*globals OAuth*/ + +const logger = new Logger('CustomOAuth'); + +const Services = {}; +const BeforeUpdateOrCreateUserFromExternalService = []; + +export class CustomOAuth { + constructor(name, options) { + logger.debug('Init CustomOAuth', name, options); + + this.name = name; + if (!Match.test(this.name, String)) { + throw new Meteor.Error('CustomOAuth: Name is required and must be String'); + } + + if (Services[this.name]) { + Services[this.name].configure(options); + return; + } + + Services[this.name] = this; + + this.configure(options); + + this.userAgent = 'Meteor'; + if (Meteor.release) { + this.userAgent += '/' + Meteor.release; + } + + Accounts.oauth.registerService(this.name); + this.registerService(); + this.addHookToProcessUser(); + } + + configure(options) { + if (!Match.test(options, Object)) { + throw new Meteor.Error('CustomOAuth: Options is required and must be Object'); + } + + if (!Match.test(options.serverURL, String)) { + throw new Meteor.Error('CustomOAuth: Options.serverURL is required and must be String'); + } + + if (!Match.test(options.tokenPath, String)) { + options.tokenPath = '/oauth/token'; + } + + if (!Match.test(options.identityPath, String)) { + options.identityPath = '/me'; + } + + this.serverURL = options.serverURL; + this.tokenPath = options.tokenPath; + this.identityPath = options.identityPath; + this.tokenSentVia = options.tokenSentVia; + this.usernameField = (options.usernameField || '').trim(); + this.mergeUsers = options.mergeUsers; + + if (!/^https?:\/\/.+/.test(this.tokenPath)) { + this.tokenPath = this.serverURL + this.tokenPath; + } + + if (!/^https?:\/\/.+/.test(this.identityPath)) { + this.identityPath = this.serverURL + this.identityPath; + } + + if (Match.test(options.addAutopublishFields, Object)) { + Accounts.addAutopublishFields(options.addAutopublishFields); + } + } + + getAccessToken(query) { + const config = ServiceConfiguration.configurations.findOne({service: this.name}); + if (!config) { + throw new ServiceConfiguration.ConfigError(); + } + + let response = undefined; + try { + response = HTTP.post(this.tokenPath, { + auth: config.clientId + ':' + OAuth.openSecret(config.secret), + headers: { + Accept: 'application/json', + 'User-Agent': this.userAgent + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri(this.name, config), + grant_type: 'authorization_code', + state: query.state + } + }); + } catch (err) { + const error = new Error(`Failed to complete OAuth handshake with ${this.name} at ${this.tokenPath}. ${err.message}`); + throw _.extend(error, {response: err.response}); + } + + if (response.data.error) { //if the http response was a json object with an error attribute + throw new Error(`Failed to complete OAuth handshake with ${this.name} at ${this.tokenPath}. ${response.data.error}`); + } else { + return response.data.access_token; + } + } + + getIdentity(accessToken) { + const params = {}; + const headers = { + 'User-Agent': this.userAgent // http://doc.gitlab.com/ce/api/users.html#Current-user + }; + + if (this.tokenSentVia === 'header') { + headers['Authorization'] = 'Bearer ' + accessToken; + } else { + params['access_token'] = accessToken; + } + + try { + const response = HTTP.get(this.identityPath, { + headers: headers, + params: params + }); + + let data; + + if (response.data) { + data = response.data; + } else { + data = JSON.parse(response.content); + } + + logger.debug('Identity response', JSON.stringify(data, null, 2)); + + return data; + } catch (err) { + const error = new Error(`Failed to fetch identity from ${this.name} at ${this.identityPath}. ${err.message}`); + throw _.extend(error, {response: err.response}); + } + } + + registerService() { + const self = this; + OAuth.registerService(this.name, 2, null, (query) => { + const accessToken = self.getAccessToken(query); + // console.log 'at:', accessToken + + let identity = self.getIdentity(accessToken); + + if (identity) { + // Fix for Reddit + if (identity.result) { + identity = identity.result; + } + + // Fix WordPress-like identities having 'ID' instead of 'id' + if (identity.ID && !identity.id) { + identity.id = identity.ID; + } + + // Fix Auth0-like identities having 'user_id' instead of 'id' + if (identity.user_id && !identity.id) { + identity.id = identity.user_id; + } + + if (identity.CharacterID && !identity.id) { + identity.id = identity.CharacterID; + } + + // Fix Dataporten having 'user.userid' instead of 'id' + if (identity.user && identity.user.userid && !identity.id) { + identity.id = identity.user.userid; + identity.email = identity.user.email; + } + + // Fix general 'phid' instead of 'id' from phabricator + if (identity.phid && !identity.id) { + identity.id = identity.phid; + } + + // Fix Keycloak-like identities having 'sub' instead of 'id' + if (identity.sub && !identity.id) { + identity.id = identity.sub; + } + + // Fix general 'userid' instead of 'id' from provider + if (identity.userid && !identity.id) { + identity.id = identity.userid; + } + } + + // console.log 'id:', JSON.stringify identity, null, ' ' + + const serviceData = { + _OAuthCustom: true, + accessToken: accessToken + }; + + _.extend(serviceData, identity); + + const data = { + serviceData: serviceData, + options: { + profile: { + name: identity.name || identity.username || identity.nickname || identity.CharacterName || identity.userName || identity.preferred_username || (identity.user && identity.user.name) + } + } + }; + + // console.log data + + return data; + }); + } + + retrieveCredential(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); + } + + getUsername(data) { + let username = ''; + + if (this.usernameField.indexOf('#{') > -1) { + username = this.usernameField.replace(/#{(.+?)}/g, function(match, field) { + if (!data[field]) { + throw new Meteor.Error('field_not_found', `Username template item "${field}" not found in data`, data); + } + return data[field]; + }); + } else { + username = data[this.usernameField]; + if (!username) { + throw new Meteor.Error('field_not_found', `Username field "${this.usernameField}" not found in data`, data); + } + } + + return username; + } + + addHookToProcessUser() { + BeforeUpdateOrCreateUserFromExternalService.push((serviceName, serviceData/*, options*/) => { + if (serviceName !== this.name) { + return; + } + + if (this.usernameField) { + const username = this.getUsername(serviceData); + + const user = RocketChat.models.Users.findOneByUsername(username); + if (!user) { + return; + } + + // User already created or merged + if (user.services && user.services[serviceName] && user.services[serviceName].id === serviceData.id) { + return; + } + + if (this.mergeUsers !== true) { + throw new Meteor.Error('CustomOAuth', `User with username ${user.username} already exists`); + } + + const serviceIdKey = `services.${serviceName}.id`; + const update = { + $set: { + [serviceIdKey]: serviceData.id + } + }; + + RocketChat.models.Users.update({_id: user._id}, update); + } + }); + + Accounts.validateNewUser((user) => { + if (!user.services || !user.services[this.name] || !user.services[this.name].id) { + return true; + } + + if (this.usernameField) { + user.username = this.getUsername(user.services[this.name]); + } + + return true; + }); + + } +} + + +const updateOrCreateUserFromExternalService = Accounts.updateOrCreateUserFromExternalService; +Accounts.updateOrCreateUserFromExternalService = function(/*serviceName, serviceData, options*/) { + for (const hook of BeforeUpdateOrCreateUserFromExternalService) { + hook.apply(this, arguments); + } + + return updateOrCreateUserFromExternalService.apply(this, arguments); +}; diff --git a/packages/rocketchat-custom-oauth/package.js b/packages/rocketchat-custom-oauth/package.js index bf9f3219d4704e15852c0a139b5cef8e38461c73..e4a397f479e8ecba55c29f2c2f53bf527a2ee848 100644 --- a/packages/rocketchat-custom-oauth/package.js +++ b/packages/rocketchat-custom-oauth/package.js @@ -5,12 +5,12 @@ Package.describe({ }); Package.onUse(function(api) { + api.use('modules'); api.use('check'); api.use('oauth'); api.use('oauth2'); api.use('underscore'); api.use('ecmascript'); - api.use('coffeescript'); api.use('accounts-oauth'); api.use('service-configuration'); api.use('underscorestring:underscore.string'); @@ -20,9 +20,9 @@ Package.onUse(function(api) { api.use('http', 'server'); - api.addFiles('custom_oauth_client.coffee', 'client'); + api.mainModule('custom_oauth_client.js', 'client'); - api.addFiles('custom_oauth_server.coffee', 'server'); + api.mainModule('custom_oauth_server.js', 'server'); api.export('CustomOAuth'); }); diff --git a/packages/rocketchat-emoji-custom/admin/adminEmoji.html b/packages/rocketchat-emoji-custom/admin/adminEmoji.html index 1af79f80f4709b7fdfb77997da877ed1ab54ad1b..32c7844b6baa817465e0bc48d844e71bf6bd4f9d 100644 --- a/packages/rocketchat-emoji-custom/admin/adminEmoji.html +++ b/packages/rocketchat-emoji-custom/admin/adminEmoji.html @@ -1,6 +1,6 @@ <template name="adminEmoji"> <section class="page-container page-list"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Custom_Emoji"}}</span> @@ -13,7 +13,7 @@ <form class="search-form" role="form"> <div class="input-line search"> <input type="text" id="emoji-filter" placeholder="{{_ "Search"}}" dir="auto"> - <i class="icon-search"></i> + <i class="icon-search secondary-font-color"></i> {{#unless isReady}}<i class="icon-spin"></i>{{/unless}} </div> </form> @@ -21,24 +21,24 @@ {{{_ "Showing_results" customemoji.length}}} </div> <div class="list"> - <table> + <table class="secondary-background-color"> <thead> - <tr> - <th> </th> - <th width="51%">{{_ "Name"}}</th> - <th width="49%">{{_ "Aliases"}}</th> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color"> </th> + <th class="content-background-color border-component-color" width="51%">{{_ "Name"}}</th> + <th class="content-background-color border-component-color" width="49%">{{_ "Aliases"}}</th> </tr> </thead> <tbody> {{#each customemoji}} - <tr class="emoji-info row-link"> - <td> + <tr class="emoji-info row-link admin-table-row"> + <td class="border-component-color"> <div class="emojiAdminPreview-image"> {{> emojiPreview name=name extension=extension}} </div> </td> - <td>{{name}}</td> - <td>{{aliases}}</td> + <td class="border-component-color">{{name}}</td> + <td class="border-component-color">{{aliases}}</td> </tr> {{/each}} </tbody> @@ -50,7 +50,7 @@ {{/unless}} </div> </section> - <section class="flex-tab"> + <section class="flex-tab secondary-background-color"> {{> Template.dynamic template=flexTemplate data=flexData}} </section> </template> diff --git a/packages/rocketchat-emoji-custom/admin/emojiEdit.html b/packages/rocketchat-emoji-custom/admin/emojiEdit.html index b16d900d02351c311bd3bccc89ce9b13e3d0a5b6..76b938cdf5b192617e94f9104e5c86c4e41dd866 100644 --- a/packages/rocketchat-emoji-custom/admin/emojiEdit.html +++ b/packages/rocketchat-emoji-custom/admin/emojiEdit.html @@ -11,11 +11,11 @@ {{/if}} <div class="input-line"> <label for="name">{{_ "Name"}}</label> - <input type="text" id="name" autocomplete="off" value="{{emoji.name}}"> + <input type="text" id="name" autocomplete="off" value="{{emoji.name}}" class="content-background-color"> </div> <div class="input-line"> <label for="aliases">{{_ "Aliases"}}</label> - <input type="text" id="aliases" autocomplete="off" value="{{emoji.aliases}}"> + <input type="text" id="aliases" autocomplete="off" value="{{emoji.aliases}}" class="content-background-color"> </div> <div class="input-line"> <label for="image">{{_ "Image"}}</label> diff --git a/packages/rocketchat-emoji-custom/assets/stylesheets/emojiCustomAdmin.less b/packages/rocketchat-emoji-custom/assets/stylesheets/emojiCustomAdmin.less index 1a6dfc8d2f11dbd68cf7d59ccd1d4a1a7c01c322..1eb732ff8e50537b7dab08ae24c4072292a5ae58 100644 --- a/packages/rocketchat-emoji-custom/assets/stylesheets/emojiCustomAdmin.less +++ b/packages/rocketchat-emoji-custom/assets/stylesheets/emojiCustomAdmin.less @@ -4,6 +4,7 @@ overflow: hidden; position: relative; border-radius: 4px; + .emojiAdminPreview-image { height: 100%; width: 100%; @@ -22,17 +23,21 @@ z-index: 15; overflow-y: auto; overflow-x: hidden; + .thumb { width: 100%; height: 350px; padding: 20px; } + nav { padding: 0 20px; } + .info { white-space: normal; padding: 0 20px; + h3 { -webkit-user-select: text; -moz-user-select: text; @@ -45,7 +50,8 @@ width: 100%; overflow: hidden; white-space: nowrap; - i:after { + + i::after { content: " "; display: inline-block; width: 8px; @@ -53,19 +59,8 @@ border-radius: 4px; vertical-align: middle; } - i.status-offline { - &:after {} - } - i.status-online { - &:after {} - } - i.status-away { - &:after {} - } - i.status-busy { - &:after {} - } } + p { -webkit-user-select: text; -moz-user-select: text; @@ -76,22 +71,27 @@ font-weight: 300; } } + .edit-form { - padding: 20px 20px 0 20px; + padding: 20px 20px 0; white-space: normal; + h3 { font-size: 24px; margin-bottom: 8px; line-height: 22px; } + p { line-height: 18px; font-size: 12px; font-weight: 300; } + > .input-line { margin-top: 20px; } + nav { padding: 0; @@ -110,7 +110,8 @@ } } } + .room-info-content > div { - margin: 0 0 20px 0; + margin: 0 0 20px; } } diff --git a/packages/rocketchat-emoji-emojione/emojiPicker.js b/packages/rocketchat-emoji-emojione/emojiPicker.js index db9771e476d80a5cbc5f61afd7c2f216eb529138..6a7a2dab3c327a950caecf7275b363ef5b935fdb 100644 --- a/packages/rocketchat-emoji-emojione/emojiPicker.js +++ b/packages/rocketchat-emoji-emojione/emojiPicker.js @@ -1523,13 +1523,6 @@ emojisByCategory = { 'flag_ss', 'flag_tc', 'flag_mf' - ], - 'modifier': [ - 'tone1', - 'tone2', - 'tone3', - 'tone4', - 'tone5' ] }; diff --git a/packages/rocketchat-emoji/emojiPicker.html b/packages/rocketchat-emoji/emojiPicker.html index 3603525d5e38b8a8d475838dfcbbfe44cf613530..8ac2e5e66b256dcfc558e9d14de2404651382297 100644 --- a/packages/rocketchat-emoji/emojiPicker.html +++ b/packages/rocketchat-emoji/emojiPicker.html @@ -1,32 +1,31 @@ <template name="emojiPicker"> - <div class="emoji-picker"> + <div class="emoji-picker secondary-background-color"> + <div class="emoji-top"> + <form class="emoji-filter input-line search search-form"> + <input type="text" class="search content-background-color" autocomplete="off"> + <i class="icon-search secondary-font-color"></i> + </form> + <div class="change-tone"> + <a href="#change-tone"><span class="current-tone {{currentTone}}"></span></a> + <ul class="tone-selector secondary-background-color"> + <li><a href="#tone" class="tone" data-tone="0"><span class="tone-0"></span></a></li> + <li><a href="#tone" class="tone" data-tone="1"><span class="tone-1"></span></a></li> + <li><a href="#tone" class="tone" data-tone="2"><span class="tone-2"></span></a></li> + <li><a href="#tone" class="tone" data-tone="3"><span class="tone-3"></span></a></li> + <li><a href="#tone" class="tone" data-tone="4"><span class="tone-4"></span></a></li> + <li><a href="#tone" class="tone" data-tone="5"><span class="tone-5"></span></a></li> + </ul> + </div> + </div> <div class="filter"> - <ul> + <ul class="filter-list"> {{#each category}} - <li class="{{activeCategory .}}" title="{{categoryName .}}"> - <a href="#{{.}}" class="category-link"><i class="icon-{{.}}"></i></a> + <li class="filter-item border-secondary-background-color {{activeCategory .}}" title="{{categoryName .}}"> + <a href="#{{.}}" class="category-link color-info-font-color"><i class="category-icon icon-{{.}}"></i></a> </li> {{/each}} - <li class="change-tone"> - <a href="#change-tone"><span class="current-tone {{currentTone}}"></span></a> - <ul class="tone-selector"> - <li><a href="#tone" class="tone" data-tone="0"><span class="tone-0"></span></a></li> - <li><a href="#tone" class="tone" data-tone="1"><span class="tone-1"></span></a></li> - <li><a href="#tone" class="tone" data-tone="2"><span class="tone-2"></span></a></li> - <li><a href="#tone" class="tone" data-tone="3"><span class="tone-3"></span></a></li> - <li><a href="#tone" class="tone" data-tone="4"><span class="tone-4"></span></a></li> - <li><a href="#tone" class="tone" data-tone="5"><span class="tone-5"></span></a></li> - </ul> - </li> </ul> </div> - <form class="emoji-filter input-line search search-form"> - <input type="text" class="search" placeholder="{{_ 'Search Emoji'}}" autocomplete="off"> - <i class="icon-right-open-small"></i> - </form> - <h2 class="current-category-header"> - {{ currentCategory }} - </h2> <div class="emojis"> {{#each category}} <ul class="{{.}} emoji-list {{isVisible .}}"> diff --git a/packages/rocketchat-emoji/emojiPicker.js b/packages/rocketchat-emoji/emojiPicker.js index f09437bdaeb2af6667133ed8371900ddcf322ae4..d960b86639eb8a89575940ef3827f356557e8fec 100644 --- a/packages/rocketchat-emoji/emojiPicker.js +++ b/packages/rocketchat-emoji/emojiPicker.js @@ -178,6 +178,8 @@ Template.emojiPicker.events({ event.stopPropagation(); event.preventDefault(); + instance.$('.emoji-filter .search').val('').change(); + instance.currentCategory.set(event.currentTarget.hash.substr(1)); return false; @@ -247,7 +249,7 @@ Template.emojiPicker.events({ event.preventDefault(); } }, - 'keyup .emoji-filter .search'(event, instance) { + 'keyup .emoji-filter .search, change .emoji-filter .search'(event, instance) { const value = event.target.value.trim(); const cst = instance.currentSearchTerm; if (value === cst.get()) { diff --git a/packages/rocketchat-emoji/emojiPicker.less b/packages/rocketchat-emoji/emojiPicker.less index 8db8c68f993a99aab410205167b5130bee398ef5..d4a06e938886572691846510ceee3ee8fbe2d1c7 100644 --- a/packages/rocketchat-emoji/emojiPicker.less +++ b/packages/rocketchat-emoji/emojiPicker.less @@ -1,29 +1,30 @@ +@import "lesshat.less"; + .emoji-picker-icon { cursor: pointer; - font-size: 18px; - color: @info-font-color; - &:before { - .transition(transform 0.2s ease); + &::before { + transition: transform 0.2s ease; } + &:hover { - &:before { + &::before { .transform(scale(1.2)); } } } + .emoji-picker { width: 100%; max-width: 365px; - background-color: @secondary-background-color; border-radius: 5px; - box-shadow: 0px 1px 1px 0 rgba(0,0,0,0.2), 0 2px 10px 0 rgba(0,0,0,.16); + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); position: absolute; z-index: 200; - - .transition(transform 0.2s ease, visibility 0.2s ease, opacity 0.2s ease); - + transition: transform 0.2s ease, visibility 0.2s ease, opacity 0.2s ease; .transform(translateY(30px)); opacity: 0; visibility: hidden; @@ -36,142 +37,51 @@ } .filter { - text-align: center; - box-shadow: 0px 2px 2px -1px rgba(0,0,0,0.2); - - > ul { - display: table; - width: 100%; - - > li { - display: table-cell; - margin: 0 2px; - padding: 6px 0; - - border-bottom: 2px solid @secondary-background-color; - - .category-link { - i { - color: @info-font-color; - font-size: 20px; - } - } + box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.2); + } - &:hover { - border-bottom: 2px solid @info-font-color; - } + .filter-list { + display: flex; + width: 100%; + padding: 0 5px; + } - &.active { - border-bottom: 2px solid @primary-background-color; - } + .filter-item { + padding: 6px 0; + border-style: solid; + border-width: 0 0 2px; + display: flex; + justify-content: center; + flex-grow: 1; - &.change-tone { - opacity: 1; - border-bottom: 2px solid transparent; - - > a { - position: relative; - z-index: 10; - } - - .current-tone { - display: inline-block; - width: 20px; - height: 20px; - border-radius: 10px; - } - - .tone-selector { - position: absolute; - background-color: @secondary-background-color; - border-radius: 4px; - box-shadow: 0px 1px 1px 0 rgba(0,0,0,0.2), 0 2px 10px 0 rgba(0,0,0,.16); - - padding: 4px 2px; - margin-left: -4px; - z-index: 8; - - .transition(transform 0.2s ease, visibility 0.2s ease, opacity 0.2s ease); - - .transform(translateY(-20px)); - opacity: 0; - visibility: hidden; - - &.show { - .transform(translateY(0px)); - opacity: 1; - display: block; - visibility: visible; - } - - li { - display: block; - padding: 0 4px; - } - - span { - display: inline-block; - width: 20px; - height: 20px; - border-radius: 10px; - - .transition(transform 0.2s ease); - } - } - - .tone-0 { - background-color: #ffcf11; - } - - .tone-1 { - background-color: #fae3c3; - } - - .tone-2 { - background-color: #e2cfa1; - } - - .tone-3 { - background-color: #dba373; - } - - .tone-4 { - background-color: #a88054; - } - - .tone-5 { - background-color: #5f4e43; - } - } - } + .category-icon { + font-size: 20px; } } .current-category-header { - padding: 3px 6px 2px 6px; + padding: 3px 5px; } .emojis { height: 160px; overflow-y: auto; - padding: 3px 0px 0px 2px; + padding: 3px 0 0 2px; - .custom-scroll(transparent, #DDD); .emoji-list { display: none; li { display: inline-block; margin: 2px; - padding: 4px 2px 2px 2px; + padding: 4px 2px 2px; border-radius: 4px; cursor: pointer; - - .transition(transform 0.2s ease); + transition: transform 0.2s ease; &:hover { .transform(scale(1.2)); - background-color: #DDD; + background-color: #dddddd; } } @@ -181,3 +91,92 @@ } } } + +.emoji-top { + display: flex; + align-items: center; + padding: 5px; + + .emoji-filter { + width: 90%; + margin-bottom: 0; + } + + .change-tone { + width: 10%; + display: flex; + justify-content: center; + position: relative; + + a { + position: relative; + z-index: 10; + } + + .current-tone { + display: block; + width: 20px; + height: 20px; + border-radius: 10px; + } + + .tone-selector { + position: absolute; + border-radius: 4px; + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); + padding: 4px 2px; + top: 25px; + z-index: 1; + transition: transform 0.2s ease, visibility 0.2s ease, opacity 0.2s ease; + .transform(translateY(-20px)); + opacity: 0; + visibility: hidden; + + &.show { + .transform(translateY(0px)); + opacity: 1; + display: block; + visibility: visible; + } + + li { + display: block; + padding: 0 4px; + } + + span { + display: inline-block; + width: 20px; + height: 20px; + border-radius: 10px; + transition: transform 0.2s ease; + } + } + + .tone-0 { + background-color: #ffcf11; + } + + .tone-1 { + background-color: #fae3c3; + } + + .tone-2 { + background-color: #e2cfa1; + } + + .tone-3 { + background-color: #dba373; + } + + .tone-4 { + background-color: #a88054; + } + + .tone-5 { + background-color: #5f4e43; + } + } +} diff --git a/packages/rocketchat-emoji/lesshat.less b/packages/rocketchat-emoji/lesshat.less new file mode 100644 index 0000000000000000000000000000000000000000..7c9f0ae86a267eb3660d2381a0bba2620f0488f4 --- /dev/null +++ b/packages/rocketchat-emoji/lesshat.less @@ -0,0 +1,17 @@ +// lesshat - The best mixin library in the world +// +// version: v4.1.0 (2016-07-19) + +.calc(...) { + @process: ~`(function(a){function c(c,t){var r=");\n",s=e.split(","),l=s[0]+":"+c+"("+(s[1].trim()||0)+r;"start"==t?a="0;\n"+l:a+=l}a=a||8121991;var t="@{state}",e=a;if(8121991==a)return a;switch(t){case"1":c("-webkit-calc","start"),c("-moz-calc"),c("calc");break;case"2":c("-webkit-calc","start"),c("-moz-calc");break;case"3":c("-webkit-calc","start"),c("calc");break;case"4":c("-webkit-calc","start");break;case"5":c("-moz-calc","start"),c("calc");break;case"6":c("-moz-calc","start");break;case"7":c("calc","start")}return a=a.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + @state: 1; -lh-property: @process; +} + +.transform(...) { + @process: ~`(function(e){e=e||"none";var r={translate:"px",rotate:"deg",rotate3d:"deg",skew:"deg"};/^\w*\(?[a-z0-9.]*\)?/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,""));for(var t in r)e.indexOf(t)>=0&&(e=e.replace(new RegExp(t+"[\\w]?\\([a-z0-9, %]*\\)"),function(e){var n=/(\d+\.?\d*)(?!\w|%)/g;return"rotate3d"==t&&(n=/,\s*\d+$/),e.replace(n,function(e){return e+r[t]})}));return e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: @process; + -moz-transform: @process; + -ms-transform: @process; + -o-transform: @process; + transform: @process; +} diff --git a/packages/rocketchat-emoji/lib/EmojiPicker.js b/packages/rocketchat-emoji/lib/EmojiPicker.js index dc3bd9dd04f58ce09e4afe2b28eac5961c2f05f9..02ad077052a04c7b6bee87ae932a635a7ea3c054 100644 --- a/packages/rocketchat-emoji/lib/EmojiPicker.js +++ b/packages/rocketchat-emoji/lib/EmojiPicker.js @@ -53,21 +53,30 @@ RocketChat.EmojiPicker = { }, setPosition() { let sourcePos = $(this.source).offset(); - let left = (sourcePos.left - this.width + 65); + let left = sourcePos.left; let top = (sourcePos.top - this.height - 5); + let cssProperties = { + top: top, + left: left + }; - if (left < 0) { - left = 10; - } if (top < 0) { - top = 10; + cssProperties.top = 10; + } + + if (left < 35) { + cssProperties.left = 0; + } else { + let windowSize = $(window).width(); + let pickerWidth = $('.emoji-picker').width(); + + if (left + pickerWidth > windowSize) { + let emojiButtonSize = $('.reaction-message.message-action').outerWidth(); + cssProperties.left = left - pickerWidth + emojiButtonSize; + } } - return $('.emoji-picker') - .css({ - top: top + 'px', - left: left + 'px' - }); + return $('.emoji-picker').css(cssProperties); }, open(source, callback) { if (!this.initiated) { diff --git a/packages/rocketchat-emoji/loadStylesheet.js b/packages/rocketchat-emoji/loadStylesheet.js deleted file mode 100644 index 323238e1975b36e924773d34e78bb10f8eb5df43..0000000000000000000000000000000000000000 --- a/packages/rocketchat-emoji/loadStylesheet.js +++ /dev/null @@ -1,3 +0,0 @@ -RocketChat.theme.addPackageAsset(function() { - return Assets.getText('emojiPicker.less'); -}); diff --git a/packages/rocketchat-emoji/package.js b/packages/rocketchat-emoji/package.js index ed234d141dc0b67ddb894c6f1750a613010b3d82..48cb5dc9e691a8db26cc2c4302a9933281f75c55 100644 --- a/packages/rocketchat-emoji/package.js +++ b/packages/rocketchat-emoji/package.js @@ -22,9 +22,8 @@ Package.onUse(function(api) { api.addFiles('emojiPicker.html', 'client'); api.addFiles('emojiPicker.js', 'client'); + api.addFiles('emojiPicker.less', 'client'); - api.addAssets('emojiPicker.less', 'server'); - api.addFiles('loadStylesheet.js', 'server'); api.addFiles('emoji.css', 'client'); api.addFiles('lib/emojiRenderer.js', 'client'); diff --git a/packages/rocketchat-favico/favico.js b/packages/rocketchat-favico/favico.js index 711c16559ca02374785b0fd06529848aa1b7844e..1cef87bd2c1fa2e61e42d4d1214c9b0b87105724 100644 --- a/packages/rocketchat-favico/favico.js +++ b/packages/rocketchat-favico/favico.js @@ -96,12 +96,13 @@ } _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type; - _orig = link.getIcon(); + _orig = link.getIcons(); //create temp canvas _canvas = document.createElement('canvas'); //create temp image _img = document.createElement('img'); - if (_orig.hasAttribute('href')) { + var lastIcon = _orig[_orig.length - 1]; + if (lastIcon.hasAttribute('href')) { _img.setAttribute('crossOrigin', 'anonymous'); //get width/height _img.onload = function() { @@ -112,7 +113,7 @@ _context = _canvas.getContext('2d'); icon.ready(); }; - _img.setAttribute('src', _orig.getAttribute('href')); + _img.setAttribute('src', lastIcon.getAttribute('href')); } else { _img.onload = function() { _h = 32; @@ -461,37 +462,40 @@ var link = {}; /** - * Get icon from HEAD tag or create a new <link> element + * Get icons from HEAD tag or create a new <link> element */ - link.getIcon = function() { - var elm = false; + link.getIcons = function() { + var elms = []; //get link element - var getLink = function() { - var link = _doc.getElementsByTagName('head')[0].getElementsByTagName('link'); - for (var l = link.length, i = (l - 1); i >= 0; i--) { - if ((/(^|\s)icon(\s|$)/i).test(link[i].getAttribute('rel'))) { - return link[i]; + var getLinks = function() { + var icons = []; + var links = _doc.getElementsByTagName('head')[0].getElementsByTagName('link'); + for (var i = 0; i < links.length; i++) { + if ((/(^|\s)icon(\s|$)/i).test(links[i].getAttribute('rel'))) { + icons.push(links[i]); } } - return false; + return icons; }; if (_opt.element) { - elm = _opt.element; + elms = [_opt.element]; } else if (_opt.elementId) { //if img element identified by elementId - elm = _doc.getElementById(_opt.elementId); - elm.setAttribute('href', elm.getAttribute('src')); + elms = [_doc.getElementById(_opt.elementId)]; + elms[0].setAttribute('href', elms[0].getAttribute('src')); } else { //if link element - elm = getLink(); - if (elm === false) { - elm = _doc.createElement('link'); - elm.setAttribute('rel', 'icon'); - _doc.getElementsByTagName('head')[0].appendChild(elm); + elms = getLinks(); + if (elms.length === 0) { + elms = [_doc.createElement('link')]; + elms[0].setAttribute('rel', 'icon'); + _doc.getElementsByTagName('head')[0].appendChild(elms[0]); } } - elm.setAttribute('type', 'image/png'); - return elm; + elms.forEach(function(item) { + item.setAttribute('type', 'image/png'); + }); + return elms; }; link.setIcon = function(canvas) { var url = canvas.toDataURL('image/png'); @@ -512,21 +516,24 @@ if (_browser.ff || _browser.opera) { //for FF we need to "recreate" element, atach to dom and remove old <link> //var originalType = _orig.getAttribute('rel'); - var old = _orig; - _orig = _doc.createElement('link'); + var old = _orig[_orig.length - 1]; + var newIcon = _doc.createElement('link'); + _orig = [newIcon]; //_orig.setAttribute('rel', originalType); if (_browser.opera) { - _orig.setAttribute('rel', 'icon'); + newIcon.setAttribute('rel', 'icon'); } - _orig.setAttribute('rel', 'icon'); - _orig.setAttribute('type', 'image/png'); - _doc.getElementsByTagName('head')[0].appendChild(_orig); - _orig.setAttribute('href', url); + newIcon.setAttribute('rel', 'icon'); + newIcon.setAttribute('type', 'image/png'); + _doc.getElementsByTagName('head')[0].appendChild(newIcon); + newIcon.setAttribute('href', url); if (old.parentNode) { old.parentNode.removeChild(old); } } else { - _orig.setAttribute('href', url); + _orig.forEach(function(icon) { + icon.setAttribute('href', url); + }); } } }; diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js index 9ca22909f0635169661c79047e41f42028f5acc0..3fdae7c30b746062aeffee51986db1a5b84058bc 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadAmazonS3.js @@ -25,7 +25,7 @@ FileUpload.AmazonS3 = class FileUploadAmazonS3 extends FileUploadBase { Session.set('uploading', uploading); } } else { - file = _.pick(this.meta, 'type', 'size', 'name', 'identify'); + file = _.pick(this.meta, 'type', 'size', 'name', 'identify', 'description'); file._id = downloadUrl.substr(downloadUrl.lastIndexOf('/') + 1); file.url = downloadUrl; diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js b/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js index 80949d670277aea4a351bb2995a3049c5d8b9b9e..39ddae6a388345b06e9e32471b597110acf6f028 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadFileSystem.js @@ -29,7 +29,7 @@ FileUpload.FileSystem = class FileUploadFileSystem extends FileUploadBase { } }, onComplete: (fileData) => { - var file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify'); + var file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); diff --git a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js index 06a3dcf7988401504954572248fa8f6a58a16034..d78faf8d81759e6adb3df5036f2b9280ced2f988 100644 --- a/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js +++ b/packages/rocketchat-file-upload/client/lib/FileUploadGridFS.js @@ -20,7 +20,7 @@ FileUpload.GridFS = class FileUploadGridFS extends FileUploadBase { } }, onComplete: (fileData) => { - var file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify'); + var file = _.pick(fileData, '_id', 'type', 'size', 'name', 'identify', 'description'); file.url = fileData.url.replace(Meteor.absoluteUrl(), '/'); diff --git a/packages/rocketchat-file-upload/lib/FileUploadBase.js b/packages/rocketchat-file-upload/lib/FileUploadBase.js index fb79799d74e961636f3ee159e80837bd80a71acc..2e93fcfa166edced447f2231a211bf0ff884b4fc 100644 --- a/packages/rocketchat-file-upload/lib/FileUploadBase.js +++ b/packages/rocketchat-file-upload/lib/FileUploadBase.js @@ -1,6 +1,19 @@ -/* globals FileUploadBase:true */ +/* globals FileUploadBase:true, UploadFS */ /* exported FileUploadBase */ +UploadFS.config.defaultStorePermissions = new UploadFS.StorePermissions({ + insert: function(userId/*, doc*/) { + return userId; + }, + update: function(userId, doc) { + return userId === doc.userId; + }, + remove: function(userId, doc) { + return userId === doc.userId; + } +}); + + FileUploadBase = class FileUploadBase { constructor(meta, file) { this.id = Random.id(); diff --git a/packages/rocketchat-file-upload/package.js b/packages/rocketchat-file-upload/package.js index bbd3bfedbd5808c7a39871e98a7e02aa418ad7d6..55491f0e8229dea99d59955bc8eaf7e69112fc88 100644 --- a/packages/rocketchat-file-upload/package.js +++ b/packages/rocketchat-file-upload/package.js @@ -47,6 +47,5 @@ Package.onUse(function(api) { }); Npm.depends({ - 'mime-types': '2.1.11', 'filesize': '3.3.0' }); diff --git a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js index 4675716b39633ba191424ed4782f06be78ab5a06..3e7f45798d14bac533f548c4656ac6204bae389c 100644 --- a/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js +++ b/packages/rocketchat-file-upload/server/config/configFileUploadGridFS.js @@ -1,6 +1,62 @@ /* globals FileUpload, UploadFS */ -var stream = Npm.require('stream'); -var zlib = Npm.require('zlib'); +const stream = Npm.require('stream'); +const zlib = Npm.require('zlib'); +const util = Npm.require('util'); +const logger = new Logger('FileUpload'); + +function ExtractRange(options) { + if (!(this instanceof ExtractRange)) { + return new ExtractRange(options); + } + + this.start = options.start; + this.stop = options.stop; + this.bytes_read = 0; + + stream.Transform.call(this, options); +} +util.inherits(ExtractRange, stream.Transform); + + +ExtractRange.prototype._transform = function(chunk, enc, cb) { + if (this.bytes_read > this.stop) { + // done reading + this.end(); + } else if (this.bytes_read + chunk.length < this.start) { + // this chunk is still before the start byte + } else { + var start, stop; + if (this.start <= this.bytes_read) { + start = 0; + } else { + start = this.start - this.bytes_read; + } + if ((this.stop - this.bytes_read + 1) < chunk.length) { + stop = this.stop - this.bytes_read + 1; + } else { + stop = chunk.length; + } + var newchunk = chunk.slice(start, stop); + this.push(newchunk); + } + this.bytes_read += chunk.length; + cb(); +}; + + +var getByteRange = function(header) { + if (header) { + var matches = header.match(/(\d+)-(\d+)/); + if (matches) { + return { + start: parseInt(matches[1], 10), + stop: parseInt(matches[2], 10) + }; + } + } + return null; +}; + // code from: https://github.com/jalik/jalik-ufs/blob/master/ufs-server.js#L91 var readFromGridFS = function(storeName, fileId, file, headers, req, res) { @@ -26,20 +82,42 @@ var readFromGridFS = function(storeName, fileId, file, headers, req, res) { // Transform stream store.transformRead(rs, ws, fileId, file, req, headers); + var h = req.headers; + var range = getByteRange(h.range); + var out_of_range = false; + if (range) { + out_of_range = (range.start > file.size) || (range.stop <= range.start) || (range.stop > file.size); + } + // Compress data using gzip - if (accept.match(/\bgzip\b/)) { + if (accept.match(/\bgzip\b/) && range === null) { headers['Content-Encoding'] = 'gzip'; delete headers['Content-Length']; res.writeHead(200, headers); ws.pipe(zlib.createGzip()).pipe(res); - } else if (accept.match(/\bdeflate\b/)) { + } else if (accept.match(/\bdeflate\b/) && range === null) { // Compress data using deflate headers['Content-Encoding'] = 'deflate'; delete headers['Content-Length']; res.writeHead(200, headers); ws.pipe(zlib.createDeflate()).pipe(res); + } else if (range && out_of_range) { + // out of range request, return 416 + delete headers['Content-Length']; + delete headers['Content-Type']; + delete headers['Content-Disposition']; + delete headers['Last-Modified']; + headers['Content-Range'] = 'bytes */' + file.size; + res.writeHead(416, headers); + res.end(); + } else if (range) { + headers['Content-Range'] = 'bytes ' + range.start + '-' + range.stop + '/' + file.size; + delete headers['Content-Length']; + headers['Content-Length'] = range.stop - range.start + 1; + res.writeHead(206, headers); + logger.debug('File upload extracting range'); + ws.pipe(new ExtractRange({ start: range.start, stop: range.stop })).pipe(res); } else { - // Send raw data res.writeHead(200, headers); ws.pipe(res); } @@ -60,3 +138,4 @@ FileUpload.addHandler('rocketchat_uploads', { return Meteor.fileStore.delete(file._id); } }); + diff --git a/packages/rocketchat-file-upload/server/methods/sendFileMessage.js b/packages/rocketchat-file-upload/server/methods/sendFileMessage.js index c44e433ad4057c93a718cfc119eb09202bf283a1..b50028dfd286c28c923236c507441bd81feedc58 100644 --- a/packages/rocketchat-file-upload/server/methods/sendFileMessage.js +++ b/packages/rocketchat-file-upload/server/methods/sendFileMessage.js @@ -1,5 +1,5 @@ Meteor.methods({ - 'sendFileMessage'(roomId, store, file) { + 'sendFileMessage'(roomId, store, file, msgData = {}) { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'sendFileMessage' }); } @@ -10,12 +10,21 @@ Meteor.methods({ return false; } + check(msgData, { + avatar: Match.Optional(String), + emoji: Match.Optional(String), + alias: Match.Optional(String), + groupable: Match.Optional(Boolean), + msg: Match.Optional(String) + }); + RocketChat.models.Uploads.updateFileComplete(file._id, Meteor.userId(), _.omit(file, '_id')); var fileUrl = '/file-upload/' + file._id + '/' + file.name; var attachment = { title: `${TAPi18n.__('Attachment_File_Uploaded')}: ${file.name}`, + description: file.description, title_link: fileUrl, title_link_download: true }; @@ -37,7 +46,7 @@ Meteor.methods({ attachment.video_size = file.size; } - const msg = { + const msg = Object.assign({ _id: Random.id(), rid: roomId, msg: '', @@ -46,7 +55,7 @@ Meteor.methods({ }, groupable: false, attachments: [attachment] - }; + }, msgData); return Meteor.call('sendMessage', msg); } diff --git a/packages/rocketchat-highlight/highlight.coffee b/packages/rocketchat-highlight/highlight.coffee deleted file mode 100644 index c27d83616b85be45987f538eff97f2f46e73cdee..0000000000000000000000000000000000000000 --- a/packages/rocketchat-highlight/highlight.coffee +++ /dev/null @@ -1,65 +0,0 @@ -### -# Highlight is a named function that will highlight ``` messages -# @param {Object} message - The message object -### - -class Highlight - - constructor: (message) -> - - if s.trim message.html - message.tokens ?= [] - - # Count occurencies of ``` - count = (message.html.match(/```/g) || []).length - - if count - - # Check if we need to add a final ``` - if (count % 2 > 0) - message.html = message.html + "\n```" - message.msg = message.msg + "\n```" - - # Separate text in code blocks and non code blocks - msgParts = message.html.split(/^\s*(```(?:[a-zA-Z]+)?(?:(?:.|\n)*?)```)(?:\n)?$/gm) - - for part, index in msgParts - # Verify if this part is code - codeMatch = part.match(/^```(\w*[\n\ ]?)([\s\S]*?)```+?$/) - if codeMatch? - # Process highlight if this part is code - singleLine = codeMatch[0].indexOf('\n') is -1 - - if singleLine - lang = '' - code = _.unescapeHTML codeMatch[1] + codeMatch[2] - else - lang = codeMatch[1] - code = _.unescapeHTML codeMatch[2] - - if s.trim(lang) is '' - lang = '' - - if s.trim(lang) not in hljs.listLanguages() - result = hljs.highlightAuto (lang + code) - else - result = hljs.highlight s.trim(lang), code - - token = "=&=#{Random.id()}=&=" - - message.tokens.push - highlight: true - token: token - text: "<pre><code class='hljs " + result.language + "'><span class='copyonly'>```<br></span>" + result.value + "<span class='copyonly'><br>```</span></code></pre>" - - msgParts[index] = token - else - msgParts[index] = part - - # Re-mount message - message.html = msgParts.join('') - - return message - -RocketChat.callbacks.add 'renderMessage', Highlight, RocketChat.callbacks.priority.HIGH, 'highlight' -RocketChat.Highlight = true diff --git a/packages/rocketchat-i18n/i18n/ar.i18n.json b/packages/rocketchat-i18n/i18n/ar.i18n.json index a0fadb62d37808bc262ea34db17a78f65f99c458..3dbd8af238ace9d58bd7615a609ccbd816e7a2bd 100644 --- a/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -447,9 +447,7 @@ "Give_a_unique_name_for_the_custom_oauth": "تعطي اسما ÙØ±ÙŠØ¯Ø§ لأوث مخصصة", "Give_the_application_a_name_This_will_be_seen_by_your_users": "إعطاء التطبيق اسما. وسو٠يظهر هذا من قبل المستخدمين.", "Global": "عالمي", - "GoogleSiteVerification_id": "رقم تØÙ‚Ù‚ من موقع Google", "GoogleTagManager_id": "جوجل مدير العلامات معرÙ", - "Has_more": "يوجد المزيد", "Hash": "مزيج", "Header": "رأس", "Hidden": "مخÙÙŠ", @@ -707,7 +705,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "الروبوتات", "minutes": "دقيقة", - "Mobile_push": "Ø¯ÙØ¹ المØÙ…ول", "More_channels": "المزيد من القنوات", "More_direct_messages": "المزيد من الرسائل الخاصة", "More_groups": "المزيد من المجموعات الخاصة", @@ -925,8 +922,6 @@ "Script_Enabled": "السيناريو ممكن", "Search": "Ø¨ØØ«", "Search_by_username": "Ø§Ù„Ø¨ØØ« عن طريق اسم المستخدم", - "Search_Channels": "Ø¨ØØ« ÙÙŠ القنوات", - "Search_Direct_Messages": "Ø§Ù„Ø¨ØØ« ÙÙŠ الرسائل الخاصة", "Search_Messages": "Ø¨ØØ« ÙÙŠ الرسائل", "Search_Private_Groups": "Ø§Ù„Ø¨ØØ« ÙÙŠ المجموعات الخاصة", "seconds": "ثواني", diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json index d22b1ca29afeba0b824b7826b138e321b83f02e0..fc6c35c7f0df2074a06abef74b17152e73dff17d 100644 --- a/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -56,10 +56,12 @@ "Accounts_OAuth_Custom_id": "ID", "Accounts_OAuth_Custom_Identity_Path": "Ruta de la identitat", "Accounts_OAuth_Custom_Login_Style": "Estil d'entrada", + "Accounts_OAuth_Custom_Merge_Users": "Uneix usuaris", "Accounts_OAuth_Custom_Scope": "Àmbit (scope)", "Accounts_OAuth_Custom_Secret": "Secret", "Accounts_OAuth_Custom_Token_Path": "Ruta del token", "Accounts_OAuth_Custom_Token_Sent_Via": "Token enviat via", + "Accounts_OAuth_Custom_Username_Field": "Camp de nom d'usuari", "Accounts_OAuth_Facebook": "Inici de sessió amb Facebook", "Accounts_OAuth_Facebook_callback_url": "URL de retorn (callback) de Facebook", "Accounts_OAuth_Facebook_id": "App ID de Facebook", @@ -107,6 +109,8 @@ "Accounts_RegistrationForm_SecretURL_Description": "Cal proporcionar una cadena de text aleatori que s'afegirà a l'URL de registre. Exemple: https://demo.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "Requerir el nom per registrar-se", "Accounts_RequirePasswordConfirmation": "Requereix confirmació de la contrasenya", + "Accounts_SetDefaultAvatar": "Avatar per defecte", + "Accounts_SetDefaultAvatar_Description": "Prova de determinar l'avatar per defecte basant-se en el compte d'OAuth o bé Gravatar", "Accounts_ShowFormLogin": "Mostra inici de sessió basat en formulari", "Accounts_UseDefaultBlockedDomainsList": "Utilitza la llista predeterminada de dominis bloquejats", "Accounts_UseDNSDomainCheck": "Utilitza la comprovació DNS de dominis", @@ -142,6 +146,7 @@ "All_messages": "Tots els missatges", "Allow_Invalid_SelfSigned_Certs": "Permetre certificats auto-signats invà lids", "Allow_Invalid_SelfSigned_Certs_Description": "Permetre certificats SSL auto-signats i invà lids per a enllaços de validació i vistes prèvies.", + "Always_open_in_new_window": "Obre sempre en finestra nova", "Analytics_features_enabled": "Funcionalitats habilitades", "Analytics_features_messages_Description": "Monitoritza esdeveniments personalitzats relacionats amb accions que els usuaris fan als missatges.", "Analytics_features_rooms_Description": "Monitoritza esdeveniments personalitzats relacionats amb accions en un canal o grup (crear, abandonar, eliminar...).", @@ -150,7 +155,11 @@ "And_more": "I __length__ més", "Animals_and_Nature": "Animals i natura", "API": "API", + "API_Allow_Infinite_Count": "Permet obtenir tot", + "API_Allow_Infinite_Count_Description": "Les peticions a la API REST haurien de permetre retornar-ho tot en una sola petició?", "API_Analytics": "AnalÃtiques", + "API_Default_Count": "Comptador per defecte", + "API_Default_Count_Description": "El comptador per defecte per als resultats de les peticions API REST si el consumidor no n'ha especificat cap.", "API_Embed": "Incrusta (embed)", "API_Embed_Description": "Activa o no les previsualitzacions d'enllaços quan un usuari publica l'enllaç a un web.", "API_EmbedCacheExpirationDays": "Caducitat de la memòria cau de les incrustacions (en dies)", @@ -164,6 +173,8 @@ "API_GitHub_Enterprise_URL_Description": "Exemple: http://domain.com (sense la barra final)", "API_Gitlab_URL": "URL de GitLab", "API_Token": "API Token", + "API_Upper_Count_Limit": "Nombre mà xim de registres", + "API_Upper_Count_Limit_Description": "Quin és el nombre mà xim de registres que la API REST pot retornar (si no és il·limitat)?", "API_User_Limit": "LÃmit d'usuaris per afegir tots els usuaris a un canal", "API_Wordpress_URL": "URL de WordPress", "Apiai_Key": "Clau Api.ai ", @@ -214,6 +225,7 @@ "Back_to_login": "Torna a identificar-me", "Back_to_permissions": "Torna a permisos", "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "CaracterÃstica Beta. Requereix que la videoconferència estigui activa.", + "Block_User": "Bloqueja usuari", "Body": "Cos", "bold": "negreta", "bot_request": "Sol·licitud Bot", @@ -252,6 +264,7 @@ "CDN_PREFIX": "Prefix CDN", "Certificates_and_Keys": "Certificats i claus", "Changing_email": "Canvi de correu-e", + "Change_Room_Type": "Canvi de tipus de sala", "channel": "canal", "Channel": "Canal", "Channel_already_exist": "El canal '#%s' ja existeix.", @@ -325,6 +338,8 @@ "Custom_Translations_Description": "Ha de ser un objecte JSON và lid on les claus són el codi de l'idioma i contenen un diccionari de clau (key) i traduccions. Exemple:</br><code>{\n \"en\": {\n  \"key\": \"translation\"\n },\n \"ca\": {\n  \"key\": \"traducció\"\n }\n}</code> ", "Dashboard": "Tauler", "Date": "Data", + "Date_From": "De", + "Date_to": "a", "days": "dies", "DB_Migration": "Migració de base de dades", "DB_Migration_Date": "Data de migració de la BD", @@ -368,6 +383,7 @@ "Edit": "Edita", "Edit_Custom_Field": "Edita camp personalitzat", "Edit_Department": "Edita departament", + "Edit_Trigger": "Edita disparador", "edited": "editat", "Editing_room": "Edició de sala", "Editing_user": "Edició d'usuari", @@ -424,9 +440,11 @@ "error-invalid-channel-start-with-chars": "Canal no và lid. Comenceu amb @ o #", "error-invalid-custom-field": "Camp personalitzat invà lid", "error-invalid-custom-field-name": "Nom del camp personalitzat invà lid. Utilitzeu només lletres, números, guions i guions baixos.", + "error-invalid-date": "La data introduïda no és và lida.", "error-invalid-description": "Descripció invà lida", "error-invalid-domain": "Domini invà lid", "error-invalid-email": "L'adreça __email__ no és và lida", + "error-invalid-email-address": "Adreça de correu-e invà lida", "error-invalid-file-height": "Alçada de la imatge invà lida", "error-invalid-file-type": "Tipus d'arxiu no và lid", "error-direct-message-file-upload-not-allowed": "Compartició d'arxius no permesa als missatges directes", @@ -513,6 +531,8 @@ "Food_and_Drink": "Menjar i beure", "Footer": "Peu de pà gina", "For_your_security_you_must_enter_your_current_password_to_continue": "Per a la seva seguretat, ha de tornar a introduir la contrasenya per continuar", + "Force_Disable_OpLog_For_Cache": "Força la inhabilitació de OpLog per la cache", + "Force_Disable_OpLog_For_Cache_Description": "No s'utilitzarà OpLog per sincronitzar la cache tot i estar disponible", "Force_SSL": "Força SSL", "Force_SSL_Description": "*Atenció!* _Force SSL_ mai ha de ser usat amb servidor intermediari invers. Si s'utilitza un proxy invers, s'ha de fer la redirecció AL PROXY. Aquesta opció existeix per a les instal·lacions tipus Heroku, que no permeten la configuració de redireccions al proxy invers.", "Forgot_password": "No recordes la contrasenya?", @@ -530,10 +550,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Estableix un nom únic per al OAuth personalitzat", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Bateja l'aplicació. El nom escollit serà visible als usuaris.", "Global": "Global", - "GoogleSiteVerification_id": "Verificació de la identificació del lloc de Google", "GoogleTagManager_id": "ID de Google Tag Manager", "Guest_Pool": "Llista de clients", - "Has_more": "Més", "Hash": "Hash", "Header": "Encapçalament", "Hidden": "Ocult", @@ -569,6 +587,8 @@ "Iframe_Integration_send_target_origin_Description": "Només les pà gines amb l'origen proporcionat podran rebre esdeveniments o `*` per a totes. Exemple `http://localhost`", "Importer_Archived": "Arxivat", "Importer_CSV_Information": "L'importador CSV requereix un format especÃfic, si us plau llegiu la documentació sobre com estructurar l'arxiu .zip:", + "Importer_HipChatEnterprise_Information": "L'arxiu pujat ha de ser un tar.gz desencriptat. Si us plau, llegiu la documentació per a més informació:", + "Importer_HipChatEnterprise_BetaWarning": "Tingueu en compte que aquest sistema d'importació encara està en desenvolupament. Si us plau, notifiqueu-nos a GitHub els errors que es produeixin:", "Importer_done": "Importació completa!", "Importer_finishing": "Finalitza la importació.", "Importer_From_Description": "Importa les dades de __from__ a Rocket.Chat.", @@ -764,6 +784,7 @@ "Livechat_managers": "Supervisors de xat en viu", "Livechat_offline": "Xat en viu fora de lÃnia", "Livechat_online": "Xat en viu connectat", + "Livechat_open_inquiery_show_connecting": "Mostra un missatge de connectant enlloc de l'entrada de text quan el client encara no està connectat amb un agent.", "Livechat_Queue": "Cua del xat en xiu", "Livechat_room_count": "Sala de recompte del xat en viu", "Livechat_Routing_Method": "Mètode d'enrutament del xat en viu", @@ -864,13 +885,14 @@ "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Els missatges enviats al WebHook d'entrada seran publicats aquÃ.", "Meta": "Meta", "Meta_fb_app_id": "App ID de Facebook", + "Meta_custom": "Etiquetes Meta personalitzades", "Meta_google-site-verification": "Verificació del lloc web a Google (google-site-verification)", "Meta_language": "Idioma", "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "Min_length_is": "La llargada mÃnima és %s", "minutes": "minuts", - "Mobile_push": "Push mòbil ", + "Mobile": "Mòbil", "Monday": "dilluns", "Monitor_history_for_changes_on": "Monitoritza l'historial per canvis a ", "More_channels": "Més canals", @@ -901,6 +923,7 @@ "New_role": "Nou rol", "New_Room_Notification": "Nova notificació de sala", "New_videocall_request": "Nova petició de vÃdeo trucada", + "New_Trigger": "Nou disparador", "No_available_agents_to_transfer": "No hi ha agents disponibles per a transferir", "No_channel_with_name_%s_was_found": "No s'ha trobat cap canal amb el nom <strong>\"%s\"</strong>!", "No_channels_yet": "Encara no ets a cap canal.", @@ -956,6 +979,7 @@ "Open_days_of_the_week": "Dies d'obertura", "Open_Livechats": "LiveChats oberts", "Opened": "Obert", + "Opened_in_a_new_window": "Obert en una nova finestra.", "Opens_a_channel_group_or_direct_message": "Obrir un canal, grup o missatge directe", "optional": "opcional", "Use_minor_colors": "Utilitza la paleta de colors secundà ria (per defecte s'hereta de la primà ria)", @@ -1044,6 +1068,8 @@ "quote": "cita", "Quote": "Cita", "Random": "Aleatori", + "React_when_read_only": "Permetre reaccions", + "React_when_read_only_changed_successfully": "Permetre reaccions en 'només lectura'", "Reacted_with": "es fa reaccionar amb", "Reactions": "Reaccions", "Read_only": "Només lectura", @@ -1052,6 +1078,7 @@ "Read_only_group": "Grup de només lectura", "Record": "Gravar", "Redirect_URI": "URI de redireccionament (Redirect URI)", + "Refresh_oauth_services": "Refresca serveis OAuth", "Refresh_keys": "Refresca les claus", "Refresh_your_page_after_install_to_enable_screen_sharing": "Per poder compartir la pantalla refresqui la pà gina després de la instal·lació ", "Register": "Crea un compte nou", @@ -1074,6 +1101,7 @@ "Require_password_change": "Requerir el canvi de la contrasenya", "Resend_verification_email": "Reenviar el correu-e de verificació", "Reset": "Reinicialitza (reset)", + "Reset_section_settings": "Reinicialitza els ajustos de la secció", "Reset_password": "Reinicialitza la contrasenya", "Restart": "Reinicia (restart)", "Restart_the_server": "Reinicia el servidor", @@ -1090,7 +1118,10 @@ "room_changed_topic": "Tema de la sala canviat a: <em>__room_topic__</em> per <em>__user_by__</em>.", "Room_description_changed_successfully": "Descripció de la sala canviada correctament", "Room_has_been_deleted": "La sala s'ha eliminat", + "Room_has_been_archived": "La sala s'ha arxivat", + "Room_has_been_unarchived": "La sala s'ha desarxivat", "Room_Info": "Informació de la sala", + "room_is_blocked": "Aquesta sala està bloquejada", "room_is_read_only": "Aquesta sala és de només lectura", "room_name": "nom de la sala", "Room_name_changed": "Nom de la sala canviat a: <em>__room_name__</em> per <em>__user_by__</em>.", @@ -1108,8 +1139,11 @@ "SAML_Custom_Cert": "Certificat personalitzat", "SAML_Custom_Entry_point": "Punt d'entrada (Entry Point) personalitzat", "SAML_Custom_Generate_Username": "Generar nom d'usuari", + "SAML_Custom_IDP_SLO_Redirect_URL": "Redirecció URL IDP SLO", "SAML_Custom_Issuer": "Emissor (issuer) personalitzat", "SAML_Custom_Provider": "Proveïdor (provider) personalitzat", + "SAML_Custom_Public_Cert": "Contingut del certificat públic", + "SAML_Custom_Private_Key": "Contingut de la clau privada", "Sandstorm_Powerbox_Share": "Comparteix un gra de Sandstorm", "Saturday": "dissabte", "Save": "Desa", @@ -1123,8 +1157,6 @@ "Script_Enabled": "Script actiu", "Search": "Cerca", "Search_by_username": "Cerca per nom d'usuari", - "Search_Channels": "Cerca canals", - "Search_Direct_Messages": "Cerca missatges directes", "Search_Messages": "Cerca missatges", "Search_Private_Groups": "Cerca grups privats", "seconds": "segons", @@ -1167,6 +1199,7 @@ "Show_all": "Veure tots", "Show_more": "Veure més", "show_offline_users": "Mostra els usuaris desconnectats", + "Show_on_registration_page": "Mostra a la pà gina de registre", "Show_only_online": "Veure només connectats", "Show_preregistration_form": "Veure formulari de pre-registre", "Show_queue_list_to_all_agents": "Mostra la cua a tots els agents", @@ -1278,6 +1311,7 @@ "theme-color-transparent-dark": "Transparent fosc", "theme-color-transparent-light": "Transparent clar", "theme-color-transparent-lighter": "Transparent més clar", + "theme-color-transparent-lightest": "Transparent el més clar", "theme-color-content-background-color": "Color del fons del contingut", "theme-color-primary-background-color": "Color primari del fons", "theme-color-primary-font-color": "Color primari del text", @@ -1338,6 +1372,7 @@ "UI_DisplayRoles": "Mostra rols", "UI_Merge_Channels_Groups": "Uneix grups privats amb canals", "Unarchive": "Desarxiva", + "Unblock_User": "Desbloqueja usuari", "Unmute_someone_in_room": "Torna a donar veu a algú de la sala", "Unmute_user": "Dóna veu a l'usuari", "Unnamed": "Sense nom", @@ -1346,10 +1381,13 @@ "Unread_Rooms": "Sales no llegides", "Unread_Rooms_Mode": "Mode de sales no llegides", "Unstar_Message": "Esborra el destacat", + "Upload_file_description": "Descripció de l'arxiu", + "Upload_file_name": "Nom de l'arxiu", "Upload_file_question": "Pujar l'arxiu?", "Uploading_file": "Pujant l'arxiu...", "Uptime": "Temps en funcionament", "URL": "URL", + "URL_room_prefix": "Prefix de l'adreça URL de les sales", "Use_account_preference": "Utilitza la preferència del compte", "Use_Emojis": "Utilitza emojis", "Use_Global_Settings": "Usa la configuració global", @@ -1373,8 +1411,10 @@ "User_has_been_muted_in_s": "L'usuari ha sigut silenciat a %s", "User_has_been_removed_from_s": "L'usuari s'ha eliminat de %s", "User_Info": "Informació de l'usuari", + "User_is_blocked": "L'usuari està bloquejat", "User_is_no_longer_an_admin": "L'usuari ja no és administrador", "User_is_now_an_admin": "L'usuari ara és administrador", + "User_is_unblocked": "L'usuari està desbloquejat", "User_joined_channel": "S'ha unit al canal.", "User_joined_channel_female": "S'ha unit al canal.", "User_joined_channel_male": "S'ha unit al canal.", @@ -1412,10 +1452,12 @@ "UTF8_Names_Slugify": "Slugify de noms UTF8", "UTF8_Names_Validation": "Validació de noms UTF8", "UTF8_Names_Validation_Description": "RegExp que s'utilitzarà per validar noms d'usuari i de sala", + "Validate_email_address": "Valida l'adreça de correu-e", "Verification_email_sent": "Missatge de correu-e de verificació enviat", "Verified": "Verificat", "Version": "Versió", "Video_Chat_Window": "VÃdeo-xat", + "Video_Conference": "Videoconferència", "Videocall_declined": "VÃdeo trucada rebutjada.", "Videocall_enabled": "VÃdeo trucada activa", "View_All": "Veure tot", @@ -1449,6 +1491,8 @@ "will_be_able_to": "podrà ", "Would_you_like_to_return_the_inquiry": "Vols retornar la sol·licitud?", "Yes": "SÃ", + "Yes_archive_it": "SÃ, arxiva'l!", + "Yes_unarchive_it": "SÃ, desarxiva'l!", "Yes_clear_all": "SÃ, esborra!", "Yes_delete_it": "SÃ, elimina!", "Yes_hide_it": "SÃ, oculta!", diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json index ec0b5a475b6cc64b3fcdea18b32b1d8ea92cc015..95d9f6284f20f063d252b31caa90aa99f730abc8 100644 --- a/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -9,6 +9,7 @@ "@username_message": "@uživatel <message>", "__username__is_no_longer__role__defined_by__user_by_": "__username__ již nenà __role__ (odebral/a __user_by__ )", "__username__was_set__role__by__user_by_": "__username__ je nynà __role__ (nastavil/a __user_by__)", + "Accept": "PÅ™ijmout", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "PÅ™ijÃmat livechat požadavky i pokud nenà online žádný operátor", "Accept_with_no_online_agents": "PÅ™ijÃmat i bez aktivnÃch operátorů", "Access_not_authorized": "PÅ™Ãstup nenà povolen", @@ -40,6 +41,7 @@ "Accounts_Enrollment_Email_Default": "<h2>VÃtejte v <h1>[Site_Name]</h1></h2><p> PÅ™ejdÄ›te na [Site_URL] a zkuste to nejlepšà open source chat Å™eÅ¡enà na trhu!</p>", "Accounts_Enrollment_Email_Description": "Můžete použÃt [name], [fname], [lname] pro uživatelské jména, kÅ™estnà jméno a pÅ™ÃjmenÃ, <br/> pro e-mail uživatele, můžete použÃt [email].", "Accounts_Enrollment_Email_Subject_Default": "VÃtejte na stránkách [Site_Name]", + "Accounts_ForgetUserSessionOnWindowClose": "Zapomenout session uživatele pÅ™i zavÅ™enà okna", "Accounts_Iframe_api_method": "Api Metoda", "Accounts_Iframe_api_url": "Api URL", "Accounts_iframe_enabled": "Povoleno", @@ -54,10 +56,12 @@ "Accounts_OAuth_Custom_id": "ID", "Accounts_OAuth_Custom_Identity_Path": "Cesta k identitÄ›", "Accounts_OAuth_Custom_Login_Style": "Styl pÅ™ihlášenÃ", + "Accounts_OAuth_Custom_Merge_Users": "SlouÄit uživatele", "Accounts_OAuth_Custom_Scope": "Prostor", "Accounts_OAuth_Custom_Secret": "Secret", "Accounts_OAuth_Custom_Token_Path": "Cesta k tokenu", "Accounts_OAuth_Custom_Token_Sent_Via": "Token odesÃlány pÅ™es", + "Accounts_OAuth_Custom_Username_Field": "Pole uživatelské jméno", "Accounts_OAuth_Facebook": "Facebook PÅ™ihlášenÃ", "Accounts_OAuth_Facebook_callback_url": "Facebook Callback URL", "Accounts_OAuth_Facebook_id": "Facebook App Id", @@ -140,6 +144,7 @@ "All_messages": "VÅ¡echny zprávy", "Allow_Invalid_SelfSigned_Certs": "Umožnit nevalidnà Äi self-signed certifikáty", "Allow_Invalid_SelfSigned_Certs_Description": "Umožňujà použÃt neplatné/self-signed SSL certifikáty pro ověřenà odkazů a náhledů.", + "Always_open_in_new_window": "Vždy otevÃrat v novém oknÄ›", "Analytics_features_enabled": "Funkce Povoleny", "Analytics_features_messages_Description": "Sleduje vlastnà události spojené s uživatelskými akcemi u zpráv.", "Analytics_features_rooms_Description": "Sleduje vlastnà události spojené s uživatelskými akcemi na mÃstnosti nebo skupinÄ› (vytvoÅ™enÃ, odchod, smazánÃ).", @@ -151,6 +156,7 @@ "API_Analytics": "Analytika", "API_Embed": "Náhled vložených odkazů", "API_Embed_Description": "Zda zobrazit náhled stránky když uživatel poÅ¡le odkaz", + "API_EmbedCacheExpirationDays": "PoÄet dnà expirace cache embed", "API_EmbedDisabledFor": "Zakázat vložený obsah pro uživatele", "API_EmbedDisabledFor_Description": "Čárkami oddÄ›lený seznam uživatelských jmen", "API_EmbedIgnoredHosts": "Seznam zakázaných adres pro vložený obsah", @@ -210,6 +216,7 @@ "Back_to_integrations": "ZpÄ›t k integracÃm", "Back_to_login": "ZpÄ›t na pÅ™ihlaÅ¡ovacà formulář", "Back_to_permissions": "ZpÄ›t na práva", + "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta funkcionalita. Videohovory musà být povoleny.", "Body": "Obsah", "bold": "tuÄný", "bot_request": "Request bota", @@ -224,12 +231,31 @@ "busy_male": "zaneprázdnÄ›ný", "Busy_male": "ZaneprázdnÄ›ný", "by": "od", + "cache_cleared": "Cache vyÄistÄ›na", "Cancel": "ZruÅ¡it", "Cancel_message_input": "ZruÅ¡it", "Cannot_invite_users_to_direct_rooms": "Do pÅ™Ãmé konverzace nelze pozvat uživatele.", + "CAS_autoclose": "Automaticky zavÅ™Ãt pÅ™ihlaÅ¡ovacà popup", + "CAS_base_url": "SSO URL", + "CAS_base_url_Description": "Adresa vašà externà SSO služby napÅ™: https://sso.priklad.cz/sso/", + "CAS_button_color": "Barva pozadà tlaÄÃtka pÅ™ihlásit", + "CAS_button_label_color": "Barva textu login pÅ™ihlásit", + "CAS_button_label_text": "Text tlaÄÃtka pÅ™ihlásit", + "CAS_enabled": "Povoleno", + "CAS_login_url": "SSO pÅ™ihlaÅ¡ovacà URL", + "CAS_login_url_Description": "Adresa pÅ™ihlášenà vašà externà SSO služby napÅ™: https://sso.priklad.cz/sso/login", + "CAS_popup_height": "Výška pÅ™ihlaÅ¡ovacÃho popupu", + "CAS_popup_width": "Å ÃÅ™ka pÅ™ihlaÅ¡ovacÃho popupu", + "CAS_Sync_User_Data_Enabled": "Vždy synchronizovat uživatelská data", + "CAS_Sync_User_Data_Enabled_Description": "Vždy synchronizovat externà CAS data uživatele do dostupných atributů po pÅ™ihlášenÃ. Poznámka: Atributy jsou synchronizovány vždy pÅ™i vytvoÅ™enà úÄtu", + "CAS_Sync_User_Data_FieldMap": "Mapa atributů", + "CAS_Sync_User_Data_FieldMap_Description": "Použijte toto pole pro vloženà JSON mapovánà internÃch atributů (klÃÄ) na externà atributy (hodnota). Externà atributy obaleny '%' vložà hodnotu promÄ›nné.<br/>NapÅ™Ãklad, `{\"email\":\"%email%\", \"name\":\"%firstname%, %lastname%\"}`<br/><br/>V CAS 1.0 lze použÃt pouze atribut `username`. Dostupné internà atributy jsou: username, name, email, rooms; V promÄ›nné `rooms` je Äárkami oddÄ›lený seznam mÃstnostà do kterých má být uživatel po vytvoÅ™enà úÄtu pÅ™ipojen napÅ™: `{\"rooms\": \"%team%,%oddeleni%\"}` pÅ™ipojà CAS uživatele po vytvoÅ™enà do mÃstnosti jejich teamu a oddÄ›lenÃ.", + "CAS_version": "CAS verze", + "CAS_version_Description": "Zvolte verzi CAS podporovanou vašà CAS SSO službou", "CDN_PREFIX": "CDN Prefix", "Certificates_and_Keys": "Certifikáty a klÃÄe", "Changing_email": "ZmÄ›na e-mailu", + "Change_Room_Type": "ZmÄ›na typu mÃstnosti", "channel": "mÃstnost", "Channel": "MÃstnost", "Channel_already_exist": "MÃstnost '#%s' již existuje.", @@ -250,6 +276,8 @@ "Choose_messages": "Výberte zprávy", "Choose_the_alias_that_will_appear_before_the_username_in_messages": "Vyberte alias, který se objevà pÅ™ed uživatelským jménem ve zprávách.", "Choose_the_username_that_this_integration_will_post_as": "Vyberte uživatelské jméno, za které bude tato integrace posÃlat zprávy.", + "clear": "VyÄistit", + "clear_cache_now": "VyÄistit cache", "Clear_all_unreads_question": "OznaÄit vÅ¡e jako pÅ™eÄtené?", "Click_here": "KliknÄ›te zde", "Client_ID": "ID klienta", @@ -305,6 +333,7 @@ "DB_Migration": "Migrace databáze", "DB_Migration_Date": "Datum migrace databáze", "Deactivate": "Deaktivovat", + "Decline": "ZamÃtnout", "Default": "VýchozÃ", "Delete": "Smazat", "Delete_message": "Smazat zprávu", @@ -331,6 +360,7 @@ "Do_you_want_to_change_to_s_question": "Chcete zmÄ›nit na <strong>%s?</strong>", "Domain": "Doména", "Domains": "Domény", + "Download_Snippet": "Stáhnout", "Drop_to_upload_file": "PusÅ¥e soubor do okna pro jeho nahránÃ", "Dry_run": "ZkouÅ¡ka", "Dry_run_description": "OdeÅ¡le pouze jeden e-mail, na stejnou adresu jako ve formuláři. K e-mailu musà patÅ™it platný uživatel.", @@ -342,6 +372,7 @@ "Edit": "Editovat", "Edit_Custom_Field": "Upravit vlastnà pole", "Edit_Department": "Upravit oddÄ›lenÃ", + "Edit_Trigger": "Upravit trigger", "edited": "upraveno", "Editing_room": "Úprava mÃstnosti", "Editing_user": "Úprava uživatele", @@ -398,11 +429,13 @@ "error-invalid-channel-start-with-chars": "Neplatná mÃstnost. ZaÄnÄ›te s @ nebo #", "error-invalid-custom-field": "Neplatné vlastnà pole", "error-invalid-custom-field-name": "Neplatný název vlastnÃho pole. PoužÃvejte pouze pÃsmena, ÄÃslice, pomlÄky a podtržÃtka.", + "error-invalid-date": "Nevalidnà vstup", "error-invalid-description": "Neplatný popis", "error-invalid-domain": "Neplatná doména", "error-invalid-email": "Neplatný email __email__", "error-invalid-file-height": "Neplatná výška souboru", "error-invalid-file-type": "Neplatný typ souboru", + "error-direct-message-file-upload-not-allowed": "SdÃlenà souborů nenà v pÅ™Ãmé konverzaci povoleno", "error-invalid-file-width": "Neplatná Å¡ÃÅ™ka souboru", "error-invalid-from-address": "Å patná adresa FROM.", "error-invalid-integration": "Neplatná integrace", @@ -457,9 +490,12 @@ "Field_removed": "Pole odebráno", "Field_required": "Pole vyžadováno", "File_exceeds_allowed_size_of_bytes": "Soubor pÅ™ekraÄuje povolenou velikost __size__ bajtů", + "File_not_allowed_direct_messages": "SdÃlenà souborů nenà v pÅ™Ãmé konverzaci povoleno.", "File_type_is_not_accepted": "Neplatný typ souboru", "FileUpload": "Nahránà souboru", "FileUpload_Enabled": "Nahrávánà souborů povoleno", + "FileUpload_Disabled": "Nahrávánà souborů je zakázáno.", + "FileUpload_Enabled_Direct": "Nahrávánà souborů povoleno v pÅ™Ãmé konverzaci", "FileUpload_File_Empty": "Soubor je prázdný", "FileUpload_FileSystemPath": "Cesta pro nahrávané soubory", "FileUpload_MaxFileSize": "Maximálnà velikost nahrávaného souboru (v bytech)", @@ -500,10 +536,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Zadejte jedineÄný název pro vlastnà OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Pojmenujte jak se bude aplikace jmenovat pro VaÅ¡e uživatele.", "Global": "GlobálnÃ", - "GoogleSiteVerification_id": "ID Google site ověřenÃ", "GoogleTagManager_id": "ID Google tag manageru", "Guest_Pool": "Skupina hostů", - "Has_more": "VÃce...", "Hash": "Hash", "Header": "HlaviÄka", "Hidden": "Schovaný", @@ -538,6 +572,9 @@ "Iframe_Integration_send_target_origin": "OdesÃlat cÃlovou doménu", "Iframe_Integration_send_target_origin_Description": "Jen stránky z povolených domén můžou Äekat na události. `*` pro povolenà vÅ¡ech domén. Lze vložit vÃce hodnot oddÄ›lených `,`. NapÅ™Ãklad `http://localhost,https://localhost`", "Importer_Archived": "Archivováno", + "Importer_CSV_Information": "CSV import vyžaduje specifický formát. Informace o tom, jak strukturovat váš zip soubor najdete v dokumentaci:", + "Importer_HipChatEnterprise_Information": "Nahraný soubor musà být neÅ¡ifrovaný tar.gz, VÃce informacà naleznete v dokumentaci:", + "Importer_HipChatEnterprise_BetaWarning": "MÄ›jte prosÃm na pamÄ›ti, že tato funkcionalita je stále ve vývoji, prosÃm nahlaÅ¡te nám jakékoliv chyby pÅ™es github:", "Importer_done": "Import dokonÄen!", "Importer_finishing": "DokonÄuji import.", "Importer_From_Description": "Import dat __from__ do Rocket.Chat.", @@ -548,12 +585,14 @@ "Importer_importing_started": "SpouÅ¡tÃm import.", "Importer_importing_users": "Importuji uživatele.", "Importer_not_in_progress": "Importovaná služba v souÄasné dobÄ› neběžÃ.", + "Importer_not_setup": "Import pravdÄ›podobnÄ› nenà správnÄ› nastaven, protože nevrátil žádná data", "Importer_Prepare_Restart_Import": "Restartovat import", "Importer_Prepare_Start_Import": "Spustit Import", "Importer_Prepare_Uncheck_Archived_Channels": "OdÅ¡krtnout Archivováné mÃstnosti", "Importer_Prepare_Uncheck_Deleted_Users": "OdÅ¡krtnout smazané uživatele", "Importer_progress_error": "NepodaÅ™ilo se zÃskat postup importu.", "Importer_setup_error": "PÅ™i nastavovánà nástroje pro import doÅ¡lo k chybÄ›.", + "Importer_Source_File": "VýbÄ›r zdrojového souboru", "Incoming_Livechats": "PÅ™Ãchozà požadavky na livechat", "inline_code": "vlozeny_kod", "Install_Extension": "Nainstalovat rozÅ¡ÃÅ™enÃ", @@ -564,12 +603,15 @@ "Installation": "Instalace", "Installed_at": "instalováno v", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Pokyny pro VaÅ¡e návÅ¡tÄ›vnÃky k vyplnÄ›nà formulář pro odeslánà zprávy", + "Impersonate_user": "Vydávat se za uživatele", + "Impersonate_user_description": "Pokud je povoleno, integrace posÃlá za uživatele, který ji vyvolal", "Integration_added": "Integrace byla pÅ™idána", "Integration_Incoming_WebHook": "PÅ™Ãchozà WebHook Integrace", "Integration_New": "Nová integrace", "Integration_Outgoing_WebHook": "Odchozà WebHook Integrace", "Integration_updated": "Integrace byla aktualizována", "Integrations": "Integrace", + "Integrations_for_all_channels": "Zadejte <strong>all_public_channels</strong> pro poslouchánà nad vÅ¡emi otevÅ™enými mÃstnostmi, <strong>all_private_groups</strong> pro vÅ¡echny soukromé mÃstnosti a <strong>all_direct_messages</strong> pro poslouchánà pÅ™Ãmých konverzacÃ", "InternalHubot": "Internà Hubot", "InternalHubot_ScriptsToLoad": "NaÄÃst skripty", "InternalHubot_ScriptsToLoad_Description": "ProsÃm, zadejte Äárkami oddÄ›lený seznam skriptů k naÄtenà z https://github.com/github/hubot-scripts/tree/master/src/scripts", @@ -598,6 +640,19 @@ "is_typing": "pÃÅ¡e", "is_typing_female": "pÃÅ¡e", "is_typing_male": "pÃÅ¡e", + "IRC_Channel_Join": "Výstup JOIN pÅ™Ãkazu", + "IRC_Channel_Leave": "Výstup PART pÅ™Ãkazu", + "IRC_Description": "Internet Relay Chat (IRC) je nástroj pro textovou komunikaci. Uživatelé komunikujà otevÅ™enÄ› v unikátnÄ› pojmenovaných mÃstnostech. IRC také podporuje pÅ™Ãmé zprávy mezi dvÄ›ma uživateli a sdÃlenà souborů. Tento modul integruje tyto vlastnosti do Rocket.Chatu.", + "IRC_Enabled": "Pokusit se integrovat IRC. ZmÄ›na vyžaduje restart Rocket.Chatu", + "IRC_Private_Message": "Výstup PRIVMSG pÅ™Ãkazu", + "IRC_Channel_Users": "Výstup NAMES pÅ™Ãkazu", + "IRC_Channel_Users_End": "Konec výstupu NAMES pÅ™Ãkazu", + "IRC_Hostname": "IRC server ke kterému se pÅ™ipojit", + "IRC_Login_Fail": "Výstup nezdaÅ™eného pÅ™ipojenà k IRC serveru", + "IRC_Login_Success": "Výstup úspěšného pÅ™ipojenà k IRC serveru", + "IRC_Message_Cache_Size": "Limit cache pro zpracovánà odchozÃch zpráv", + "IRC_Port": "Port k pÅ™ipojenà na IRC serveru", + "IRC_Quit": "Výstup pÅ™i rozpojenà IRC session", "It_works": "Funguje to", "italics": "kurzÃva", "Jitsi_Chrome_Extension": "ID Chrome rozÅ¡ÃÅ™enÃ", @@ -636,6 +691,7 @@ "Layout_Terms_of_Service": "PodmÃnky služby", "LDAP": "LDAP", "LDAP_CA_Cert": "CA Certifikát", + "LDAP_Connect_Timeout": "Prodleva pÅ™ipojenà (ms)", "LDAP_Custom_Domain_Search": "Vyhledávánà ve vlastnà doménÄ›", "LDAP_Custom_Domain_Search_Description": "JSON, který Å™Ãdà vazby a informacà o pÅ™ipojenà a je ve tvaru: <br/> <code>{\"filter\": \"(&(objectCategory=person)(objectclass=user)(memberOf=CN=ROCKET_ACCESS,CN=Users,DC=domain,DC=com)(sAMAccountName=#{username}))\", \"scope\": \"sub\", \"userDN\": \"rocket.service@domain.com\", \"password\": \"urpass\"}</code>", "LDAP_Default_Domain": "Výchozà doména", @@ -660,6 +716,7 @@ "LDAP_Encryption_Description": "Metoda Å¡ifrovánà použÃvaná k zabezpeÄené komunikace se serverem LDAP. Jako pÅ™Ãklady lze uvést `plain` (bez Å¡ifrovánÃ),` SSL/LDAPS` (Å¡ifrovaný od zaÄátku), a `StartTLS` (Å ifrovaná komunikaci až po pÅ™ipojenÃ).", "LDAP_Host": "Hostitel", "LDAP_Host_Description": "Hostitel LDAP, napÅ™Ãklad `ldap.example.com` nebo 10.0.0.30`.", + "LDAP_Idle_Timeout": "Prodleva neÄinnosti (ms)", "LDAP_Import_Users": "Importovat LDAP uživatele", "LDAP_Import_Users_Description": "Pokud povoleno, vÅ¡ichni LDAP uživatelé budou naimportováni<br /> *POZOR* Nastavte filtr pokud chcete omezit poÄet uživatelů.", "LDAP_Login_Fallback": "Alternativnà login", @@ -682,6 +739,18 @@ "LDAP_Use_Custom_Domain_Search_Description": "NapiÅ¡te svůj vlastnà filtr pro vyhledávánà uživatelů v LDAP serveru.", "LDAP_Username_Field": "Pole Uživatelského jména", "LDAP_Username_Field_Description": "Které pole budou použity jako *Jméno* pro nové uživatele. Ponechte prázdné pro použÃtà jména z pÅ™ihlaÅ¡ovacà stránky. <br/> Můžete použÃt Å¡ablony a tagy jako napÅ™Ãklad `#{givenName}.#{sn}`.<br/>Výchozà hodnota je `sAMAccountName`.", + "LDAP_Group_Filter_Enable": "Povolit LDAP filtr uživatelské skupiny", + "LDAP_Group_Filter_Enable_Description": "Omezit pÅ™Ãstup uživatelům z LDAP skupiny<br/>Hodà se pro OpenLDAP servery kde nelze použÃt *memberOf* filtr", + "LDAP_Group_Filter_ObjectClass": "ObjectClass Skupiny", + "LDAP_Group_Filter_ObjectClass_Description": "*objectclass* která identifikuje skupiny.<br/> NapÅ™. OpenLDAP:groupOfUniqueNames", + "LDAP_Group_Filter_Group_Id_Attribute": "Atribut ID skupiny", + "LDAP_Group_Filter_Group_Id_Attribute_Description": "NapÅ™.: *OpenLDAP:*cn", + "LDAP_Group_Filter_Group_Member_Attribute": "Atribut uživatele skupiny", + "LDAP_Group_Filter_Group_Member_Attribute_Description": "NapÅ™.: *OpenLDAP:*uniqueMember", + "LDAP_Group_Filter_Group_Member_Format": "Formát uživatele skupiny", + "LDAP_Group_Filter_Group_Member_Format_Description": "NapÅ™.: *OpenLDAP:*uid=#{username},ou=users,o=Company,c=com", + "LDAP_Group_Filter_Group_Name": "Jméno skupiny", + "LDAP_Group_Filter_Group_Name_Description": "Jméno skupiny do které uživatel patÅ™Ã", "Least_Amount": "NejmenÅ¡Ãm poÄet", "Leave_Group_Warning": "Jste si jisti, že chcete opustit skupinu \"%s\"?", "Leave_Private_Warning": "Jste si jisti, že chcete opustit diskusi s \"%s\"?", @@ -701,6 +770,7 @@ "Livechat_managers": "ManažeÅ™i Livechatu", "Livechat_offline": "Livechat offline", "Livechat_online": "Livechat online", + "Livechat_open_inquiery_show_connecting": "Zobrazit informaci o ÄekajÃcÃm pÅ™ipojenà mÃsto pole zprávy pokud uživatel jeÅ¡tÄ› nebyl propojen s operátorem", "Livechat_Queue": "Livechat fronta", "Livechat_room_count": "Livechat poÄet mÃstnostÃ", "Livechat_Routing_Method": "Metoda rozÅ™azenà livechatů", @@ -761,6 +831,7 @@ "Message_AllowEditing_BlockEditInMinutesDescription": "ZadánÃm hodnoty 0 vypnete blokovánÃ.", "Message_AllowPinning": "Povolit pÅ™ipnutà zprávy", "Message_AllowPinning_Description": "Povolenà pÅ™ipnutà zpráv, k mÃstnosti.", + "Message_AllowSnippeting": "Povolit pÅ™edvolené zprávy", "Message_AllowStarring": "Povolit hvÄ›zdiÄkovánà zpráv", "Message_AllowUnrecognizedSlashCommand": "Povolit nerozpoznané lomÃtkové pÅ™Ãkazy", "Message_AlwaysSearchRegExp": "Vždy hledat pomocà regulárnÃch výrazů", @@ -775,6 +846,11 @@ "Message_editing": "Editace zprávy", "Message_GroupingPeriod": "Seskupovat zprávy v rozmezà (v sekundách)", "Message_GroupingPeriodDescription": "Zprávy budou seskupeny s pÅ™edchozà zprávou, pokud jsou obÄ› od stejného uživatele a uplynulá doba byla kratšà než specifikovaný Äas v sekundách.", + "Message_HideType_au": "Schovat zprávu o \"pÅ™idánà uživatele\"", + "Message_HideType_mute_unmute": "Schovat zprávu o \"od/ztiÅ¡enà uživatele\"", + "Message_HideType_ru": "Schovat zprávu o \"odebránà uživatele\"", + "Message_HideType_uj": "Schovat zprávu o \"pÅ™ipojenà uživatele\"", + "Message_HideType_ul": "Schovat zprávu o \"odchodu uživatele\"", "Message_KeepHistory": "Udržovat historie zpráv", "Message_MaxAll": "Maximálnà velikost mÃstnosti pro vÅ¡echny zprávy", "Message_MaxAllowedSize": "Maximálnà povolená velikost zprávy", @@ -795,13 +871,13 @@ "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Zprávy, které jsou odesÃlány do pÅ™Ãchozà WebHook integrace budou zveÅ™ejnÄ›ny zde.", "Meta": "Meta", "Meta_fb_app_id": "Facebook App Id", + "Meta_custom": "Vlastnà meta tagy", "Meta_google-site-verification": "Ověřenà stránek Google", "Meta_language": "Jazyk", "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Roboti", "Min_length_is": "Minimálnà délka je %s", "minutes": "minuty", - "Mobile_push": "Mobilnà notifikace", "Monday": "PondÄ›lÃ", "Monitor_history_for_changes_on": "Sledovat historii na zmÄ›ny:", "More_channels": "VÃce mÃstnostÃ", @@ -831,6 +907,8 @@ "New_password": "Nové heslo", "New_role": "Nová role", "New_Room_Notification": "Oznámenà o nové mÃstnosti", + "New_videocall_request": "Nový požadavek videohovoru", + "New_Trigger": "Nový trigger", "No_available_agents_to_transfer": "Žádnà operátoÅ™i k dispozici", "No_channel_with_name_%s_was_found": "Nebyla nalezena žádná mÃstnost s názvem <strong>\"%s\"</strong>!", "No_channels_yet": "ZatÃm nejste v žádné mÃstnosti.", @@ -844,6 +922,7 @@ "No_results_found": "Nebyly nalezeny žádné výsledky", "No_starred_messages": "Žádné zprávy s hvÄ›zdiÄkou", "No_such_command": "PÅ™Ãkaz `__comand__` neexistuje", + "No_snippet_messages": "Žádné pÅ™edvolby", "No_user_with_username_%s_was_found": "Nebyl nalezen žádný uživatel s uživatelským jménem <strong>\"%s\"</strong>!", "Nobody_available": "Nikdo nenà dostupný", "Node_version": "Verze Node", @@ -857,6 +936,7 @@ "Notification_Duration": "Délka zobrazenà oznámenÃ", "Notifications": "OznámenÃ", "Notify_all_in_this_room": "Oznámit vÅ¡em v této mÃstnosti", + "Notify_active_in_this_room": "Notifikovat aktivnà uživatele v mÃstnosti", "Num_Agents": "# Operátorů", "Number_of_messages": "PoÄet zpráv", "OAuth_Application": "OAuth Aplikace", @@ -884,8 +964,10 @@ "Open_days_of_the_week": "OtevÃracà doba pÅ™es týden", "Open_Livechats": "OtevÅ™ené livechaty", "Opened": "OtevÅ™ený", + "Opened_in_a_new_window": "OtevÅ™eno v novém oknÄ›", "Opens_a_channel_group_or_direct_message": "OtevÅ™e mÃstnost, skupinu nebo pÅ™Ãmou zprávu", "optional": "volitelný", + "Use_minor_colors": "PoužÃt nevýraznou barevnou baletu (ve výchozÃm stavu podÄ›dà výraznou paletu)", "or": "nebo", "Order": "Objednat", "OS_Arch": "Architektura OS", @@ -971,6 +1053,8 @@ "quote": "citovat", "Quote": "Citovat", "Random": "Náhodný", + "React_when_read_only": "Povolit reakce", + "React_when_read_only_changed_successfully": "Povolit reakce po úspěšné zmÄ›nÄ› povolenà ke ÄtenÃ", "Reacted_with": "zanechal/a reakci", "Reactions": "Reakce", "Read_only": "Pouze pro ÄtenÃ", @@ -979,6 +1063,7 @@ "Read_only_group": "Skupina pouze pro ÄtenÃ", "Record": "Záznam", "Redirect_URI": "URI PÅ™esmÄ›rovánÃ", + "Refresh_oauth_services": "Obnovit služby OAuth", "Refresh_keys": "Obnovit klÃÄe", "Refresh_your_page_after_install_to_enable_screen_sharing": "Po instalaci obnovte stránku aby sdÃlenà stránky fungovalo", "Register": "Zaregistrovat nový úÄet", @@ -1017,6 +1102,8 @@ "room_changed_topic": "<em>__user_by__</em> zmÄ›nil/a téma mÃstnosti na: <em>__room_topic__</em>", "Room_description_changed_successfully": "Popis mÃstnosti zmÄ›nÄ›n", "Room_has_been_deleted": "MÃstnost smazána", + "Room_has_been_archived": "MÃstnost byla archivována", + "Room_has_been_unarchived": "MÃstnost již nenà archivována", "Room_Info": "Informace o mÃstnosti", "room_is_read_only": "Tato mÃstnost je pouze pro ÄtenÃ", "room_name": "jméno mÃstnosti", @@ -1035,8 +1122,11 @@ "SAML_Custom_Cert": "Vlastnà Certifikát", "SAML_Custom_Entry_point": "Vlastnà vstupnà bod", "SAML_Custom_Generate_Username": "Generovat uživatelské jméno", + "SAML_Custom_IDP_SLO_Redirect_URL": "IDP SLO pÅ™esmÄ›rovacà URL", "SAML_Custom_Issuer": "Vlastnà vydavatel", "SAML_Custom_Provider": "Vlastnà poskytovatel", + "SAML_Custom_Public_Cert": "Obsah veÅ™ejného certifikátu", + "SAML_Custom_Private_Key": "Obsah privátnÃho klÃÄe", "Sandstorm_Powerbox_Share": "SdÃlet Sandstorm grain", "Saturday": "Sobota", "Save": "Uložit", @@ -1050,8 +1140,6 @@ "Script_Enabled": "Skript Povolen", "Search": "VyhledávánÃ", "Search_by_username": "Vyhledávánà podle jména", - "Search_Channels": "Vyhledávánà mÃstnostÃ", - "Search_Direct_Messages": "Vyhledávánà pÅ™Ãmých konveracÃ", "Search_Messages": "Hledat zprávy", "Search_Private_Groups": "Vyhledávánà soukromých skupin", "seconds": "sekundy", @@ -1094,6 +1182,7 @@ "Show_all": "Ukázat vÅ¡e", "Show_more": "Zobrazit vÃce", "show_offline_users": "zobrazit offline uživatele", + "Show_on_registration_page": "Zobrazit na registraÄnà stránce", "Show_only_online": "Pouze on-line", "Show_preregistration_form": "Ukázat pÅ™ed-registraÄnà formulář", "Show_queue_list_to_all_agents": "Zobrazit frontu vÅ¡ech operátorů", @@ -1106,7 +1195,13 @@ "Site_Url_Description": "NapÅ™Ãklad: https://chat.domain.com/", "Skip": "PÅ™eskoÄit", "SlackBridge_error": "SlackBridge narazil na chybu pÅ™i importu zpráv ve %s: %s", + "SlackBridge_Out_All": "Slackbridge OdesÃlat VÅ¡e", + "SlackBridge_Out_All_Description": "OdesÃlat zprávy ze vÅ¡ech mÃstnostà které existujà i na slacku a bot je v nich pÅ™ipojen", + "SlackBridge_Out_Channels": "Slackbridge OdesÃlat mÃstnosti", + "SlackBridge_Out_Channels_Description": "Vyberte které mÃstnosti budou odesÃlány", "SlackBridge_finish": "Slackbridge dokonÄil import zpráv ve %s. ProsÃm obnovte stránku pro zobrazenà vÅ¡ech zpráv.", + "SlackBridge_Out_Enabled": "SlackBridge OdesáÃlánà povoleno", + "SlackBridge_Out_Enabled_Description": "Zvolit zda by mÄ›l SlackBridge také odesÃlat zprávy zpÄ›t do Slacku", "SlackBridge_start": "@%s spustil import pÅ™es SlackBridge ve `#%s`. Dáme vÄ›dÄ›t až to bude hotové.", "Slash_Gimme_Description": "Zobrazà ༼ 㤠◕_â—• ༽㤠pÅ™ed Vašà zprávou", "Slash_LennyFace_Description": "Zobrazà ( ͡° ͜ʖ ͡°) za Vašà zprávou", @@ -1131,6 +1226,9 @@ "SMTP_Port": "Port SMTP", "SMTP_Test_Button": "Test nastavenà SMTP", "SMTP_Username": "Uživatelské jméno SMTP", + "Snippet_Added": "VytvoÅ™eno v %s", + "Snippet_Messages": "PÅ™edvolené zprávy", + "Snippeted_a_message": "VytvoÅ™ena pÅ™edvolená zpráva __snippetLink__", "Sound": "Zvuk", "SSL": "SSL", "Star_Message": "OhvÄ›zdiÄkovat zprávu", @@ -1161,6 +1259,8 @@ "Stats_Total_Users": "Celkem uživatelů", "Status": "Stav", "Stop_Recording": "Zastavit záznam", + "Stream_Cast_Address": "Adresa Stream Castu", + "Stream_Cast_Address_Description": "IP nebo Server vašà Rocket.Chat hlavnÃho Stream Castu. NapÅ™. `192.168.1.1:3000` or `localhost:4000`", "strike": "pÅ™eÅ¡krtnuté", "Subject": "PÅ™edmÄ›t", "Submit": "Odeslat", @@ -1190,11 +1290,23 @@ "The_user_will_be_removed_from_s": "Uživatel bude odstranÄ›n z %s", "The_user_wont_be_able_to_type_in_s": "Uživatel nebude moci psát v %s", "Theme": "Barevné nastavenÃ", + "theme-color-transparent-darker": "Průhledná tmavÅ¡Ã", + "theme-color-transparent-dark": "Průhledná tmavá", + "theme-color-transparent-light": "Průhledná svÄ›tlá", + "theme-color-transparent-lighter": "Průhledná svÄ›tlejÅ¡Ã", + "theme-color-transparent-lightest": "Průhledná nejsvÄ›tlejÅ¡Ã", "theme-color-content-background-color": "Barva pozadà obsahu", "theme-color-primary-background-color": "Primárnà Barva pozadÃ", "theme-color-primary-font-color": "Primárnà Barva pÃsma", + "theme-color-primary-action-color": "Primárnà barva akce", "theme-color-secondary-background-color": "Sekundárnà Barva pozadÃ", "theme-color-secondary-font-color": "Sekundárnà Barva pÃsma", + "theme-color-secondary-action-color": "Sekundárnà barva akce", + "theme-color-component-color": "Barva komponenty", + "theme-color-success-color": "Barva zdaÅ™ené akce", + "theme-color-pending-color": "Barva ÄekajÃcà akce", + "theme-color-error-color": "Barva chybové akce", + "theme-color-selection-color": "Barva výbÄ›ru", "theme-color-tertiary-background-color": "Terciárnà Barva pozadÃ", "theme-color-tertiary-font-color": "Terciárnà Barva pÃsma", "theme-color-link-font-color": "Barva pÃsma odkazů", @@ -1227,6 +1339,9 @@ "To_users": "Uživatelům", "Topic": "Téma", "Travel_and_Places": "Cestovánà & MÃsta", + "Transcript_Enabled": "Zeptat se po skonÄenà chatu, zda uživateli odeslat kopii konverzace", + "Transcript_message": "Zpráva kterou zobrazit jako dotaz zda odeslat kopii konverzace", + "Transcript_of_your_livechat_conversation": "Kopie Vašà livechat konverzace", "Trigger_removed": "Trigger odstranÄ›n", "Trigger_Words": "KlÃÄová slova", "Triggers": "Trigery", @@ -1313,11 +1428,14 @@ "Users_in_role": "Uživatelé v roli", "UTF8_Names_Slugify": "Url podoba UTF8 jmen", "UTF8_Names_Validation": "UTF8 validace jmen", - "UTF8_Names_Validation_Description": "Nelze použÃt speciálnà znaky a mezery. Můžete použÃt - _ a . ale ne na konci jména", + "UTF8_Names_Validation_Description": "Regulárnà výraz validujÃcà uživatelská jména a jména mÃstnostÃ", "Verification_email_sent": "Ověřovacà email odeslán", "Verified": "Ověřený", "Version": "Verze", "Video_Chat_Window": "Video chat", + "Video_Conference": "Video konference", + "Videocall_declined": "Videohovor odmÃtnut", + "Videocall_enabled": "Videohovor povolen", "View_All": "Zobrazit vÅ¡e", "View_Logs": "Zobrazit logy", "View_mode": "Režim zobrazenÃ", @@ -1331,6 +1449,7 @@ "Visitor_page_URL": "URL Stránky pro návÅ¡tÄ›vnÃky", "Visitor_time_on_site": "Doba návÅ¡tÄ›vnÃka na stránce", "Wait_activation_warning": "PÅ™edtÃm, než se můžete pÅ™ihlásit, Váš úÄet musà být ruÄnÄ› aktivován správcem.", + "Warnings": "VarovánÃ", "We_are_offline_Sorry_for_the_inconvenience": "Jsme offline. Omlouváme se za nepÅ™Ãjemnosti.", "We_have_sent_password_email": "Poslali jsme vám e-mail s pokyny k obnovenà hesla. Pokud neobdržÃte e-mail v blÃzké dobÄ›, zkuste to prosÃm znovu.", "We_have_sent_registration_email": "Poslali jsme vám e-mail pro potvrzenà registrace. Pokud e-mail neobdržÃte v blÃzké dobÄ›, zkuste to prosÃm znovu.", @@ -1348,6 +1467,8 @@ "will_be_able_to": "bude moci", "Would_you_like_to_return_the_inquiry": "Chcete požadavek zamÃtnout?", "Yes": "Ano", + "Yes_archive_it": "Ano archivovat", + "Yes_unarchive_it": "Ano zruÅ¡it archivaci", "Yes_clear_all": "Ano, vyÄistit vÅ¡echny!", "Yes_delete_it": "Ano, smazat!", "Yes_hide_it": "Ano, skrýt!", diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json index 1522fdc8c1ba2a152daf4d37bffcdfb07574392e..d5e5f6368e34a03adbd56e6f42df9d6564e65c13 100644 --- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -271,7 +271,7 @@ "Created_at_s_by_s": "Erstellt am <strong>%s</strong> von <strong>%s</strong>", "Current_Chats": "Aktuelle Chats", "Custom": "Benutzerdefiniert", - "Custom_Emoji": "Plattformspezifischer Emoji", + "Custom_Emoji": "Plattformspezifische Emoji", "Custom_Emoji_Add": "Neuen Emoji hinzufügen", "Custom_Emoji_Added_Successfully": "Benutzerdefinierter Emoji erfolgreich hinzugefügt", "Custom_Emoji_Delete_Warning": "Das Löschen eines Emojis kann nicht rückgängig gemacht werden.", @@ -467,9 +467,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Geben Sie dem benutzerdefinierten OAuth-Konto einen eindeutigen Namen.", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Geben Sie der Anwendung einen Namen. Die Nutzer können den Namen sehen.", "Global": "Global", - "GoogleSiteVerification_id": "Verifikations-ID für Googleseite", "GoogleTagManager_id": "Google-Tag-Manager-ID", - "Has_more": "Mehr", "Hash": "Hash", "Header": "Kopfzeile", "Hidden": "Versteckt", @@ -707,6 +705,11 @@ "Message_editing": "Bearbeiten von Nachrichten", "Message_GroupingPeriod": "Gruppierungsdauer (in Sekunden)", "Message_GroupingPeriodDescription": "Nachrichten werden einer vorherigen Nachricht zugeordnet, wenn beide Nachrichten von dem gleichen Benutzer kommen und die abgelaufene Zeit kleiner als die mitgeteilte Zeit in Sekunden war.", + "Message_HideType_au": "\"Benutzer hinzugeben\" Nachrichten verstecken", + "Message_HideType_mute_unmute": "\"Benutzer stillgeschalten\" Nachrichten verstecken", + "Message_HideType_ru": "\"Benutzer entfernt\" Nachrichten verstecken", + "Message_HideType_uj": "\"Benutzer beigetreten\" Nachrichten verstecken", + "Message_HideType_ul": "\"Benutzer verlassen\" Nachrichten verstecken", "Message_KeepHistory": "Nachrichtenverlauf behalten", "Message_MaxAll": "Maximale Kanalgröße für ALL Nachricht", "Message_MaxAllowedSize": "Maximal zulässige Größe der Nachrichten\n", @@ -732,7 +735,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Roboter", "minutes": "Minuten", - "Mobile_push": "mobile Push-Benachrichtigungen", "Monitor_history_for_changes_on": "Verlaufsänderungen beobachten für", "More_channels": "Weitere Kanäle", "More_direct_messages": "Mehr Direktnachrichten", @@ -961,8 +963,6 @@ "Script_Enabled": "Das Script ist aktiviert.", "Search": "Suche", "Search_by_username": "Anhand des Nutzernamen suchen", - "Search_Channels": "Kanäle suchen", - "Search_Direct_Messages": "Durchsuche Direktnachrichten", "Search_Messages": "Nachrichten durchsuchen", "Search_Private_Groups": "Durchsuche private Chatgruppen", "seconds": "Sekunden", @@ -1123,6 +1123,7 @@ "Unmute_user": "Benutzern das Chatten erlauben ", "Unnamed": "Unbenannt", "Unpin_Message": "Nachicht nicht mehr fixieren", + "Unread_Alert": "Über Ungelesenes benachrichtigen", "Unread_Rooms": "Ungelesene Räume", "Unread_Rooms_Mode": "Ungelesene Räume aufgelistet anzeigen ", "Unstar_Message": "Markierung entfernen", @@ -1227,7 +1228,7 @@ "Yes": "Ja", "Yes_clear_all": "Ja!", "Yes_delete_it": "Ja!", - "Yes_hide_it": "Ja!", + "Yes_hide_it": "Ja, verstecken!", "Yes_leave_it": "Ja!", "Yes_mute_user": "Ja, Benutzer stumm schalten!\n", "Yes_remove_user": "Ja, Nutzer entfernen!", diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index adb712db329236558f14e319e47a46a46decad9a..4270f720c9a2c23df29aff13016bcdc8b3726673 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -281,7 +281,7 @@ "Created_at_s_by_s": "Erstellt am <strong>%s</strong> von <strong>%s</strong>", "Current_Chats": "Aktuelle Chats", "Custom": "Benutzerdefiniert", - "Custom_Emoji": "Benutzerdefinierter Emoji", + "Custom_Emoji": "Benutzerdefinierte Emoji", "Custom_Emoji_Add": "Neuen Emoji hinzufügen", "Custom_Emoji_Added_Successfully": "Benutzerdefinierter Emoji erfolgreich hinzugefügt", "Custom_Emoji_Delete_Warning": "Das Löschen eines Emojis kann nicht rückgänig gemacht werden.", @@ -397,7 +397,7 @@ "error-invalid-domain": "Ungültige Domain", "error-invalid-email": "Ungültige E-Mail-Adresse: __email__", "error-invalid-file-height": "Ungültige Dateihöhe", - "error-invalid-file-type": "Ungültiges Dateiformat.", + "error-invalid-file-type": "Ungültiges Dateiformat", "error-invalid-file-width": "Ungültige Dateibreite", "error-invalid-from-address": "Sie haben eine ungültige E-Mail-Adresse als Empfänger angegeben.", "error-invalid-integration": "Ungültige Integration", @@ -426,7 +426,7 @@ "error-not-allowed": "Nicht erlaubt", "error-not-authorized": "Nicht berechtigt", "error-push-disabled": "Push-Benachrichtigungen sind deaktiviert", - "error-remove-last-owner": "Das ist der letzte Besitzer. Bitte bestimmen Sie einen neuen Besitzer, bevor Sie diesen Raum löschen.", + "error-remove-last-owner": "Das ist der letzte Besitzer. Bitte bestimmen Sie einen neuen Besitzer, bevor Sie diesen entfernen.", "error-role-in-use": "Die Rolle kann nicht gelöscht werden, da sie gerade verwendet wird.", "error-role-name-required": "Ein Rollenname muss angegeben werden", "error-the-field-is-required": "Das Feld __field__ ist erforderlich.", @@ -494,10 +494,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Geben Sie dem benutzerdefinierten OAuth-Konto einen eindeutigen Namen.", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Geben Sie der Anwendung einen Namen. Die Nutzer können den Namen sehen.", "Global": "Global", - "GoogleSiteVerification_id": "Verifikations-ID für Googleseite", "GoogleTagManager_id": "Google-Tag-Manager-ID", "Guest_Pool": "Gästepool", - "Has_more": "Mehr", "Hash": "Hash", "Header": "Kopfzeile", "Hidden": "Versteckt", @@ -747,6 +745,11 @@ "Message_editing": "Bearbeiten von Nachrichten", "Message_GroupingPeriod": "Gruppierungsdauer (in Sekunden)", "Message_GroupingPeriodDescription": "Nachrichten werden einer vorherigen Nachricht zugeordnet, wenn beide Nachrichten von dem gleichen Benutzer kommen und die abgelaufene Zeit kleiner als die mitgeteilte Zeit in Sekunden war.", + "Message_HideType_au": "\"Benutzer hinzugeben\" Nachrichten verstecken", + "Message_HideType_mute_unmute": "\"Benutzer stillgeschalten\" Nachrichten verstecken", + "Message_HideType_ru": "\"Benutzer entfernt\" Nachrichten verstecken", + "Message_HideType_uj": "\"Benutzer beigetreten\" Nachrichten verstecken", + "Message_HideType_ul": "\"Benutzer verlassen\" Nachrichten verstecken", "Message_KeepHistory": "Nachrichtenverlauf behalten", "Message_MaxAll": "Maximale Kanalgröße für ALL Nachricht", "Message_MaxAllowedSize": "Maximal zulässige Größe der Nachrichten\n", @@ -772,7 +775,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Roboter", "minutes": "Minuten", - "Mobile_push": "mobile Push-Benachrichtigungen", "Monday": "Montag", "Monitor_history_for_changes_on": "Verlaufsänderungen beobachten für", "More_channels": "Mehr Kanäle", @@ -1008,8 +1010,6 @@ "Script_Enabled": "Das Script ist aktiviert.", "Search": "Suche", "Search_by_username": "Anhand des Nutzernamen suchen", - "Search_Channels": "Kanäle suchen", - "Search_Direct_Messages": "Durchsuche Direktnachrichten", "Search_Messages": "Nachrichten durchsuchen", "Search_Private_Groups": "Durchsuche private Gruppen", "seconds": "Sekunden", @@ -1185,6 +1185,7 @@ "Unmute_user": "Benutzern das Chatten erlauben ", "Unnamed": "Unbenannt", "Unpin_Message": "Nachicht nicht mehr fixieren", + "Unread_Alert": "Über Ungelesenes benachrichtigen", "Unread_Rooms": "Ungelesene Räume", "Unread_Rooms_Mode": "Ungelesene Räume aufgelistet anzeigen ", "Unstar_Message": "Markierung entfernen", @@ -1290,7 +1291,7 @@ "Yes": "Ja", "Yes_clear_all": "Ja!", "Yes_delete_it": "Ja!", - "Yes_hide_it": "Ja!", + "Yes_hide_it": "Ja, verstecken!", "Yes_leave_it": "Ja!", "Yes_mute_user": "Ja, Benutzer stumm schalten!\n", "Yes_remove_user": "Ja, Nutzer entfernen!", diff --git a/packages/rocketchat-i18n/i18n/el.i18n.json b/packages/rocketchat-i18n/i18n/el.i18n.json index 8a3512d53ec89e31229bffc075d0c9e550f9c47a..b3edc3db5529061e58066985f6544a02e42012fe 100644 --- a/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/packages/rocketchat-i18n/i18n/el.i18n.json @@ -449,9 +449,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Δώστε Îνα μοναδικό όνομα για την Ï€ÏοσαÏμοσμÎνη OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Δώστε στην εφαÏμογή Îνα όνομα. Αυτό θα φανεί από τους χÏήστες σας.", "Global": "Παγκόσμια", - "GoogleSiteVerification_id": "Id επαλήθευσης Google Site", "GoogleTagManager_id": "Id στο Google Tag Manager", - "Has_more": "Îχει πεÏισσότεÏα", "Hash": "Χασίσι", "Header": "Επί κεφαλής", "Hidden": "ΚεκÏυμμÎνος", @@ -710,7 +708,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Ρομπότ", "minutes": "λεπτά", - "Mobile_push": "κινητό ώθηση", "More_channels": "ΠεÏισσότεÏα κανάλια", "More_direct_messages": "Πιο άμεσα μηνÏματα", "More_groups": "ΠεÏισσότεÏες ιδιωτικÎÏ‚ ομάδες", @@ -927,8 +924,6 @@ "Script_Enabled": "script Enabled", "Search": "Αναζήτηση", "Search_by_username": "Αναζήτηση με το όνομα χÏήστη", - "Search_Channels": "Αναζήτηση καναλιών", - "Search_Direct_Messages": "Απευθείας ΜηνÏματα", "Search_Messages": "Αναζήτηση μηνυμάτων", "Search_Private_Groups": "Αναζήτηση ιδιωτικοÏÏ‚ ομίλους", "seconds": "δευτεÏόλεπτα", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 046e16834409fe8c688f9bbf1f284b429344b202..2a9c0e0648c99580f4cb5e500453d62345f29d30 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -56,10 +56,12 @@ "Accounts_OAuth_Custom_id": "Id", "Accounts_OAuth_Custom_Identity_Path": "Identity Path", "Accounts_OAuth_Custom_Login_Style": "Login Style", + "Accounts_OAuth_Custom_Merge_Users": "Merge users", "Accounts_OAuth_Custom_Scope": "Scope", "Accounts_OAuth_Custom_Secret": "Secret", "Accounts_OAuth_Custom_Token_Path": "Token Path", "Accounts_OAuth_Custom_Token_Sent_Via": "Token Sent Via", + "Accounts_OAuth_Custom_Username_Field": "Username field", "Accounts_OAuth_Facebook": "Facebook Login", "Accounts_OAuth_Facebook_callback_url": "Facebook Callback URL", "Accounts_OAuth_Facebook_id": "Facebook App Id", @@ -107,6 +109,8 @@ "Accounts_RegistrationForm_SecretURL_Description": "You must provide a random string that will be added to your registration URL. Example: https://demo.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "Require Name For Signup", "Accounts_RequirePasswordConfirmation": "Require Password Confirmation", + "Accounts_SetDefaultAvatar": "Set Default Avatar", + "Accounts_SetDefaultAvatar_Description": "Tries to determine default avatar based on OAuth Account or Gravatar", "Accounts_ShowFormLogin": "Show form-based Login", "Accounts_UseDefaultBlockedDomainsList": "Use Default Blocked Domains List", "Accounts_UseDNSDomainCheck": "Use DNS Domain Check", @@ -118,6 +122,7 @@ "Add": "Add", "Add_agent": "Add agent", "Add_custom_oauth": "Add custom oauth", + "Add_Domain": "Add Domain", "Add_manager": "Add manager", "Add_user": "Add user", "Add_User": "Add User", @@ -142,6 +147,7 @@ "All_messages": "All messages", "Allow_Invalid_SelfSigned_Certs": "Allow Invalid Self-Signed Certs", "Allow_Invalid_SelfSigned_Certs_Description": "Allow invalid and self-signed SSL certificate's for link validation and previews.", + "Always_open_in_new_window": "Always open in new window", "Analytics_features_enabled": "Features Enabled", "Analytics_features_messages_Description": "Tracks custom events related to actions a user does on messages.", "Analytics_features_rooms_Description": "Tracks custom events related to actions on a channel or group (create, leave, delete).", @@ -150,7 +156,11 @@ "And_more": "And __length__ more", "Animals_and_Nature": "Animals & Nature", "API": "API", + "API_Allow_Infinite_Count": "Allow Getting Everything", + "API_Allow_Infinite_Count_Description": "Should calls to the REST API be allowed to return everything in one call?", "API_Analytics": "Analytics", + "API_Default_Count": "Default Count", + "API_Default_Count_Description": "The default count for REST API results if the consumer did not provided any.", "API_Embed": "Embed Link Previews", "API_Embed_Description": "Whether embedded link previews are enabled or not when a user posts a link to a website.", "API_EmbedCacheExpirationDays": "Embed cache expiration days", @@ -164,6 +174,8 @@ "API_GitHub_Enterprise_URL_Description": "Example: http://domain.com (excluding trailing slash)", "API_Gitlab_URL": "GitLab URL", "API_Token": "API Token", + "API_Upper_Count_Limit": "Max Record Amount", + "API_Upper_Count_Limit_Description": "What is the maximum number of records the REST API should return (when not unlimited)?", "API_User_Limit": "User Limit for adding all Users to Channel", "API_Wordpress_URL": "WordPress URL", "Apiai_Key": "Api.ai Key", @@ -214,6 +226,7 @@ "Back_to_login": "Back to login", "Back_to_permissions": "Back to permissions", "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta feature. Depends on Video Conference to be enabled.", + "Block_User": "Block User", "Body": "Body", "bold": "bold", "bot_request": "Bot request", @@ -252,6 +265,7 @@ "CDN_PREFIX": "CDN Prefix", "Certificates_and_Keys": "Certificates and Keys", "Changing_email": "Changing email", + "Change_Room_Type": "Changing the Room Type", "channel": "channel", "Channel": "Channel", "Channel_already_exist": "The channel '#%s' already exists.", @@ -325,6 +339,8 @@ "Custom_Translations_Description": "Should be a valid JSON where keys are languages containing a dictionary of key and translations. Example:</br><code>{\n\t\"en\": {\n\t\t\"key\": \"translation\"\n\t},\n\t\"pt\": {\n\t\t\"key\": \"tradução\"\n\t}\n}</code> ", "Dashboard": "Dashboard", "Date": "Date", + "Date_From": "From", + "Date_to": "to", "days": "days", "DB_Migration": "Database Migration", "DB_Migration_Date": "Database Migration Date", @@ -355,6 +371,8 @@ "Displays_action_text": "Displays action text", "Do_you_want_to_change_to_s_question": "Do you want to change to <strong>%s</strong>?", "Domain": "Domain", + "Domain_added": "domain Added", + "Domain_removed": "Domain Removed", "Domains": "Domains", "Download_Snippet": "Download", "Drop_to_upload_file": "Drop to upload file", @@ -368,6 +386,7 @@ "Edit": "Edit", "Edit_Custom_Field": "Edit Custom Field", "Edit_Department": "Edit Department", + "Edit_Trigger": "Edit Trigger", "edited": "edited", "Editing_room": "Editing room", "Editing_user": "Editing user", @@ -424,9 +443,11 @@ "error-invalid-channel-start-with-chars": "Invalid channel. Start with @ or #", "error-invalid-custom-field": "Invalid custom field", "error-invalid-custom-field-name": "Invalid custom field name. Use only letters, numbers, hyphens and underscores.", + "error-invalid-date": "Invalid date provided.", "error-invalid-description": "Invalid description", "error-invalid-domain": "Invalid domain", "error-invalid-email": "Invalid email __email__", + "error-invalid-email-address": "Invalid email address", "error-invalid-file-height": "Invalid file height", "error-invalid-file-type": "Invalid file type", "error-direct-message-file-upload-not-allowed": "File sharing not allowed in direct messages", @@ -513,6 +534,8 @@ "Food_and_Drink": "Food & Drink", "Footer": "Footer", "For_your_security_you_must_enter_your_current_password_to_continue": "For your security, you must enter your current password to continue", + "Force_Disable_OpLog_For_Cache": "Force disable OpLog for Cache", + "Force_Disable_OpLog_For_Cache_Description": "Will not use OpLog to sync cache even when it's available", "Force_SSL": "Force SSL", "Force_SSL_Description": "*Caution!* _Force SSL_ should never be used with reverse proxy. If you have a reverse proxy, you should do the redirect THERE. This option exists for deployments like Heroku, that does not allow the redirect configuration at the reverse proxy.", "Forgot_password": "Forgot your password", @@ -530,10 +553,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Give a unique name for the custom oauth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Give the application a name. This will be seen by your users.", "Global": "Global", - "GoogleSiteVerification_id": "Google Site Verification Id", "GoogleTagManager_id": "Google Tag Manager Id", "Guest_Pool": "Guest Pool", - "Has_more": "Has more", "Hash": "Hash", "Header": "Header", "Hidden": "Hidden", @@ -569,6 +590,8 @@ "Iframe_Integration_send_target_origin_Description": "Only pages with given origin will be able to listen to events or `*` for all origins. Example `http://localhost`", "Importer_Archived": "Archived", "Importer_CSV_Information": "The CSV importer requires a specific format, please read the documentation for how to structure your zip file:", + "Importer_HipChatEnterprise_Information": "The file uploaded must be a decrypted tar.gz, please read the documentation for further information:", + "Importer_HipChatEnterprise_BetaWarning": "Please be aware that this import is still a work in progress, please report any errors which occur in GitHub:", "Importer_done": "Importing complete!", "Importer_finishing": "Finishing up the import.", "Importer_From_Description": "Imports __from__ data into Rocket.Chat.", @@ -755,6 +778,8 @@ "List_of_Channels": "List of Channels", "List_of_Direct_Messages": "List of Direct Messages", "Livechat_agents": "Livechat agents", + "Livechat_AllowedDomainsList": "Livechat allowed domains", + "Domains_allowed_to_embed_the_livechat_widget": "Comma-separated list of domains allowed to embed the livechat widget. Leave blank to allow all domains.", "Livechat_Dashboard": "Livechat Dashboard", "Livechat_enabled": "Livechat enabled", "Livechat_forward_open_chats": "Forward open chats", @@ -764,6 +789,7 @@ "Livechat_managers": "Livechat managers", "Livechat_offline": "Livechat offline", "Livechat_online": "Livechat online", + "Livechat_open_inquiery_show_connecting": "Show connecting message instead of input when guest is not yet connected to an agent", "Livechat_Queue": "Livechat Queue", "Livechat_room_count": "Livechat room count", "Livechat_Routing_Method": "Livechat Routing Method", @@ -864,13 +890,14 @@ "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Messages that are sent to the Incoming WebHook will be posted here.", "Meta": "Meta", "Meta_fb_app_id": "Facebook App Id", + "Meta_custom": "Custom Meta Tags", "Meta_google-site-verification": "Google Site Verification", "Meta_language": "Language", "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "Min_length_is": "Min length is %s", "minutes": "minutes", - "Mobile_push": "Mobile push", + "Mobile": "Mobile", "Monday": "Monday", "Monitor_history_for_changes_on": "Monitor history for changes on", "More_channels": "More channels", @@ -901,6 +928,7 @@ "New_role": "New role", "New_Room_Notification": "New Room Notification", "New_videocall_request": "New videocall request", + "New_Trigger": "New Trigger", "No_available_agents_to_transfer": "No available agents to transfer", "No_channel_with_name_%s_was_found": "No channel with name <strong>\"%s\"</strong> was found!", "No_channels_yet": "You aren't part of any channel yet.", @@ -956,6 +984,7 @@ "Open_days_of_the_week": "Open Days of the Week", "Open_Livechats": "Open Livechats", "Opened": "Opened", + "Opened_in_a_new_window": "Opened in a new window.", "Opens_a_channel_group_or_direct_message": "Opens a channel, group or direct message", "optional": "optional", "Use_minor_colors": "Use minor color palette (defaults inherit major colors)", @@ -997,6 +1026,7 @@ "Please_enter_value_for_url": "Please enter a value for the url of your avatar.", "Please_enter_your_new_password_below": "Please enter your new password below:", "Please_enter_your_password": "Please enter your password", + "please_enter_valid_domain": "Please enter a valid domain", "Please_fill_a_label": "Please fill a label", "Please_fill_a_name": "Please fill a name", "Please_fill_a_username": "Please fill a username", @@ -1044,6 +1074,8 @@ "quote": "quote", "Quote": "Quote", "Random": "Random", + "React_when_read_only": "Allow Reacting", + "React_when_read_only_changed_successfully": "Allow reacting when read only changed successfully", "Reacted_with": "Reacted with", "Reactions": "Reactions", "Read_only": "Read Only", @@ -1052,6 +1084,7 @@ "Read_only_group": "Read Only Group", "Record": "Record", "Redirect_URI": "Redirect URI", + "Refresh_oauth_services": "Refresh OAuth Services", "Refresh_keys": "Refresh keys", "Refresh_your_page_after_install_to_enable_screen_sharing": "Refresh your page after install to enable screen sharing", "Register": "Register a new account", @@ -1074,6 +1107,7 @@ "Require_password_change": "Require password change", "Resend_verification_email": "Resend verification email", "Reset": "Reset", + "Reset_section_settings": "Reset section settings", "Reset_password": "Reset password", "Restart": "Restart", "Restart_the_server": "Restart the server", @@ -1090,7 +1124,10 @@ "room_changed_topic": "Room topic changed to: <em>__room_topic__</em> by <em>__user_by__</em>", "Room_description_changed_successfully": "Room description changed successfully", "Room_has_been_deleted": "Room has been deleted", + "Room_has_been_archived": "Room has been archived", + "Room_has_been_unarchived": "Room has been unarchived", "Room_Info": "Room Info", + "room_is_blocked": "This room is blocked", "room_is_read_only": "This room is read only", "room_name": "room name", "Room_name_changed": "Room name changed to: <em>__room_name__</em> by <em>__user_by__</em>", @@ -1108,8 +1145,11 @@ "SAML_Custom_Cert": "Custom Certificate", "SAML_Custom_Entry_point": "Custom Entry Point", "SAML_Custom_Generate_Username": "Generate Username", + "SAML_Custom_IDP_SLO_Redirect_URL": "IDP SLO Redirect URL", "SAML_Custom_Issuer": "Custom Issuer", "SAML_Custom_Provider": "Custom Provider", + "SAML_Custom_Public_Cert": "Public cert contents", + "SAML_Custom_Private_Key": "Private key contents", "Sandstorm_Powerbox_Share": "Share a Sandstorm grain", "Saturday": "Saturday", "Save": "Save", @@ -1123,8 +1163,6 @@ "Script_Enabled": "Script Enabled", "Search": "Search", "Search_by_username": "Search by username", - "Search_Channels": "Search Channels", - "Search_Direct_Messages": "Search Direct Messages", "Search_Messages": "Search Messages", "Search_Private_Groups": "Search Private Groups", "seconds": "seconds", @@ -1167,6 +1205,7 @@ "Show_all": "Show all", "Show_more": "Show more", "show_offline_users": "show offline users", + "Show_on_registration_page": "Show on registration page", "Show_only_online": "Show only online", "Show_preregistration_form": "Show pre-registration form", "Show_queue_list_to_all_agents": "Show queue list to all agents", @@ -1278,6 +1317,7 @@ "theme-color-transparent-dark": "Transparent Dark", "theme-color-transparent-light": "Transparent Light", "theme-color-transparent-lighter": "Transparent Lighter", + "theme-color-transparent-lightest": "Transparent Lightest", "theme-color-content-background-color": "Content Background Color", "theme-color-primary-background-color": "Primary Background Color", "theme-color-primary-font-color": "Primary Font Color", @@ -1339,6 +1379,7 @@ "UI_Merge_Channels_Groups": "Merge private groups with channels", "UI_Use_Name_Avatar": "Use name for default user avatar", "Unarchive": "Unarchive", + "Unblock_User": "Unblock User", "Unmute_someone_in_room": "Unmute someone in the room", "Unmute_user": "Unmute user", "Unnamed": "Unnamed", @@ -1347,10 +1388,13 @@ "Unread_Rooms": "Unread Rooms", "Unread_Rooms_Mode": "Unread Rooms Mode", "Unstar_Message": "Remove Star", + "Upload_file_description": "File description", + "Upload_file_name": "File name", "Upload_file_question": "Upload file?", "Uploading_file": "Uploading file...", "Uptime": "Uptime", "URL": "URL", + "URL_room_prefix": "URL room prefix", "Use_account_preference": "Use account preference", "Use_Emojis": "Use Emojis", "Use_Global_Settings": "Use Global Settings", @@ -1374,8 +1418,10 @@ "User_has_been_muted_in_s": "User has been muted in %s", "User_has_been_removed_from_s": "User has been removed from %s", "User_Info": "User Info", + "User_is_blocked": "User is blocked", "User_is_no_longer_an_admin": "User is no longer an admin", "User_is_now_an_admin": "User is now an admin", + "User_is_unblocked": "User is unblocked", "User_joined_channel": "Has joined the channel.", "User_joined_channel_female": "Has joined the channel.", "User_joined_channel_male": "Has joined the channel.", @@ -1413,10 +1459,12 @@ "UTF8_Names_Slugify": "UTF8 Names Slugify", "UTF8_Names_Validation": "UTF8 Names Validation", "UTF8_Names_Validation_Description": "RegExp that will be used to validate usernames and channel names", + "Validate_email_address": "Validate email address", "Verification_email_sent": "Verification email sent", "Verified": "Verified", "Version": "Version", "Video_Chat_Window": "Video Chat", + "Video_Conference": "Video Conference", "Videocall_declined": "Videocall declined.", "Videocall_enabled": "Videocall enabled", "View_All": "View All", @@ -1450,6 +1498,8 @@ "will_be_able_to": "will be able to", "Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?", "Yes": "Yes", + "Yes_archive_it": "Yes, archive it!", + "Yes_unarchive_it": "Yes, unarchive it!", "Yes_clear_all": "Yes, clear all!", "Yes_delete_it": "Yes, delete it!", "Yes_hide_it": "Yes, hide it!", diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json index fa8a8eab08f27805fd0c12e57d19b98ef7805f1b..1c10333a76fc922f2aa85725ef16dba01c23a750 100644 --- a/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/packages/rocketchat-i18n/i18n/es.i18n.json @@ -466,10 +466,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Establezca un nombre único para el oauth personalizado", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Dar a la aplicación un nombre. Esto será visto por los usuarios.", "Global": "Global", - "GoogleSiteVerification_id": "Verificación de la identificación del sitio de Google", "GoogleTagManager_id": "Id Google Administrador de etiquetas", "Guest_Pool": "Pool de invitados", - "Has_more": "Tiene mas", "Hash": "Picadillo", "Header": "Encabezamiento", "Hidden": "Oculto", @@ -740,7 +738,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "minutes": "minutos", - "Mobile_push": "Push Mobile", "Monitor_history_for_changes_on": "Monitoriza el historial de cambios en", "More_channels": "Más canales", "More_direct_messages": "Más mensajes directos", @@ -974,8 +971,6 @@ "Script_Enabled": "Guión Habilitado", "Search": "Buscar", "Search_by_username": "Búsqueda por nombre de usuario", - "Search_Channels": "Canales de búsqueda", - "Search_Direct_Messages": "Buscar mensajes directos", "Search_Messages": "Buscar Mensajes", "Search_Private_Groups": "Grupos privados", "seconds": "segundos", diff --git a/packages/rocketchat-i18n/i18n/fa.i18n.json b/packages/rocketchat-i18n/i18n/fa.i18n.json index 4178483bac02a2b4811f45df0a23d0aaabda8d1d..040e0fbb69747d11d8bfcd8d881e95de77358d57 100644 --- a/packages/rocketchat-i18n/i18n/fa.i18n.json +++ b/packages/rocketchat-i18n/i18n/fa.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "یک نام Ù…Ù†ØØµØ± به ÙØ±Ø¯ برای OAuth ØÙظ Ø³ÙØ§Ø±Ø´ÛŒ", "Give_the_application_a_name_This_will_be_seen_by_your_users": "به نرم Ø§ÙØ²Ø§Ø± یک نام. این خواهد بود Ú©Ù‡ توسط کاربران شما دیده Ù…ÛŒ شود.", "Global": "جهانی", - "GoogleSiteVerification_id": "سایت ID تأیید Ú¯ÙˆÚ¯Ù„", "GoogleTagManager_id": "Ú¯ÙˆÚ¯Ù„ ID مدیر برچسب", - "Has_more": "بیشتر داشتن", "Hash": "مخلوط", "Header": "سربرگ", "Hidden": "پنهان", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "روبات", "minutes": "دقایق", - "Mobile_push": "ÙØ´Ø§Ø± های تلÙÙ† همراه", "More_channels": "کانال های بیشتر", "More_direct_messages": "پیام های مستقیم بیشتر", "More_groups": "گروه بیشتر خصوصی", @@ -923,8 +920,6 @@ "Script_Enabled": "اسکریپت ÙØ¹Ø§Ù„", "Search": "جستجو کردن", "Search_by_username": "جستجو بر اساس نام کاربری", - "Search_Channels": "کانال های جستجو", - "Search_Direct_Messages": "جستجو پیام های مستقیم", "Search_Messages": "پیام های جستجو", "Search_Private_Groups": "جستجوی گروه ها شخصی", "seconds": "ثانیه", diff --git a/packages/rocketchat-i18n/i18n/fi.i18n.json b/packages/rocketchat-i18n/i18n/fi.i18n.json index 605f60c127df0a1942844c82ead76e9efe73f124..2b0f459d43b82c879e547fefa8c6e6f5106fbb9b 100644 --- a/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -466,10 +466,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Anna yksilöllinen nimi mukautettua oauth varten", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Anna sovelluksen nimi. Käyttäjät näkevät tämän.", "Global": "Yleinen", - "GoogleSiteVerification_id": "Google Site Verification Id", "GoogleTagManager_id": "Google Tag Manager Id", "Guest_Pool": "Vieraspooli", - "Has_more": "Löytyy lisää", "Hash": "Hash", "Header": "Ylätunniste", "Hidden": "Piilotettu", @@ -741,7 +739,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Hakurobotit", "minutes": "minuuttia", - "Mobile_push": "Puhelimen push-ilmoitukset", "Monitor_history_for_changes_on": "Tarkastele muutoshistoriaa koskien", "More_channels": "Lisää kanavia", "More_direct_messages": "Lisää yksityisviestejä", @@ -972,8 +969,6 @@ "Script_Enabled": "Skripti käytössä", "Search": "Hae", "Search_by_username": "Hae käyttäjätunnuksella", - "Search_Channels": "Hae kanavia", - "Search_Direct_Messages": "Hae yksityisviestejä", "Search_Messages": "Hae viestejä", "Search_Private_Groups": "Hae yksityisryhmiä", "seconds": "sekuntia", diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index e2c53d0395497a0b0d1ce7475efda18c1b98eae7..b06fe3d961ca62d95f9e57200e5b01b582c8b29a 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -491,10 +491,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Indiquez un nom unique pour l'OAuth personnalisé", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Donnez un nom à l'application. Il sera visible par les utilisateurs.", "Global": "Global", - "GoogleSiteVerification_id": "Google Site Vérification Id", "GoogleTagManager_id": "Google Tag Manager ID", "Guest_Pool": "Invités en attente", - "Has_more": "a plus de", "Hash": "Hachage", "Header": "En-tête", "Hidden": "Caché", @@ -785,7 +783,6 @@ "Meta_robots": "Robots", "Min_length_is": "La longueur minimale est %s", "minutes": "minutes", - "Mobile_push": "Notifications push sur mobile", "Monday": "Lundi", "Monitor_history_for_changes_on": "Surveiller l'historique des changements sur", "More_channels": "Plus de canaux", @@ -1026,8 +1023,6 @@ "Script_Enabled": "Script activé", "Search": "Recherche", "Search_by_username": "Rechercher par nom d'utilisateur", - "Search_Channels": "Recherche de canaux", - "Search_Direct_Messages": "Rechercher dans les Messages Privés", "Search_Messages": "Rechercher dans les messages", "Search_Private_Groups": "Rechercher dans les Groupes Privés", "seconds": "secondes", diff --git a/packages/rocketchat-i18n/i18n/he.i18n.json b/packages/rocketchat-i18n/i18n/he.i18n.json index 2e1a16486ca12df2110228bcf7e74660b7928234..459b1008f5f8bfc46174c6b47d8b9809a0a9b2e6 100644 --- a/packages/rocketchat-i18n/i18n/he.i18n.json +++ b/packages/rocketchat-i18n/i18n/he.i18n.json @@ -1,5 +1,5 @@ { - "0_Errors_Only": "0 - שגי×ות רק", + "0_Errors_Only": "0 - שגי×ות בלבד", "1_Errors_and_Information": "1 - טעויות ומידע", "2_Erros_Information_and_Debug": "2 - erros, מידע Debug", "403": "×סור", @@ -73,8 +73,8 @@ "Accounts_OAuth_Google_secret": "סוד גוגל", "Accounts_OAuth_Linkedin": "×›× ×™×¡×” ל־LinkedIn", "Accounts_OAuth_Linkedin_callback_url": "כתובת ×”×תר Callback Linkedin", - "Accounts_OAuth_Linkedin_id": "מזהה LinkedIn", - "Accounts_OAuth_Linkedin_secret": "סוד LinkedIn", + "Accounts_OAuth_Linkedin_id": "מזהה LinkedIn", + "Accounts_OAuth_Linkedin_secret": "סוד LinkedIn", "Accounts_OAuth_Meteor": "התחברות ×¢× Meteor", "Accounts_OAuth_Meteor_callback_url": "כתובת ×”×תר Callback מט×ור", "Accounts_OAuth_Meteor_id": "מזהה Meteor", @@ -178,7 +178,7 @@ "AutoLinker_Urls_www": "כתובות 'www' AutoLinker", "AutoLinker_UrlsRegExp": "ביטוי AutoLinker URL רגיל", "Available": "זמין", - "Available_agents": "×¡×•×›× ×™ זמין", + "Available_agents": "×¡×•×›× ×™× ×–×ž×™× ×™×", "Avatar": "×ª×ž×•× ×ª פרופיל", "Avatar_changed_successfully": "×”×ª×ž×•× ×” הוחלפה בהצלחה", "Avatar_URL": "כתובת ×תר ×ישי", @@ -206,7 +206,7 @@ "by": "על ידי", "Cancel": "ביטול", "Cancel_message_input": "ביטול", - "Cannot_invite_users_to_direct_rooms": "×ין ×פשרות להזמין ×ž×©×ª×ž×©×™× ×œ×›×•×•×Ÿ חדרי×", + "Cannot_invite_users_to_direct_rooms": "×ין ×פשרות להזמין ×ž×©×ª×ž×©×™× ×œ×—×“×¨×™× ×™×©×™×¨×™×", "CDN_PREFIX": "תחילית CDN", "Certificates_and_Keys": "תעודות ומפתחות", "Changing_email": "דו×\"ל ×©×™× ×•×™", @@ -214,7 +214,7 @@ "Channel_already_exist": "ערוץ '#% s' כבר ×§×™×™×.", "Channel_already_Unarchived": "ערוץ ×¢× ×”×©× `#% s` ×”×•× ×›×‘×¨ במצב ×”×•×¦× ×ž×”×רכיון", "Channel_Archived": "ערוץ ×¢× ×”×©× `#% s` כבר ב×רכיון בהצלחה", - "Channel_doesnt_exist": "הערוץ `#%s` ×œ× ×§×™×™×.", + "Channel_doesnt_exist": "הערוץ `#%s` ×œ× ×§×™×™×.", "Channel_Unarchived": "ערוץ ×¢× ×”×©× `#% s` הוסר מ×רכיון בהצלחה", "Channels": "ערוצי×", "Channels_list": "רשימת ×¢×¨×•×¦×™× ×¦×™×‘×•×¨×™×™×", @@ -267,21 +267,21 @@ "Date": "ת×ריך", "days": "ימי×", "DB_Migration": "הגירת מסד × ×ª×•× ×™×", - "DB_Migration_Date": "ת×ריך הגירה מסד", + "DB_Migration_Date": "ת×ריך הגירת מסד", "Deactivate": "הושבת", "Default": "בְּרִירַת מֶחדָל", "Delete": "מחיקה", "Delete_message": "מחיקת הודעה", "Delete_my_account": "מחק ×ת ×—×©×‘×•× ×™", - "Delete_Room_Warning": "מחיקת חדר ×™×’×¨×•× ×œ×ž×—×™×§×ª כל ההודעות שפורסמו בחדר. פעולה זו מוחלטת.", - "Delete_User_Warning": "מחיקת משתמש תמחק ×ת כל הודעותיו, פעולה זו בלתי הפיכה.", + "Delete_Room_Warning": "מחיקת חדר ×™×’×¨×•× ×œ×ž×—×™×§×ª כל ההודעות שפורסמו בחדר. פעולה זו בלתי הפיכה.", + "Delete_User_Warning": "מחיקת משתמש תמחק ×ת כל הודעותיו. פעולה זו בלתי הפיכה.", "Deleted": "× ×ž×—×§!", "Department_removed": "המחלקה הוסרה", "Departments": "מחלקות", "Deployment_ID": "מזהה פריסה", "Description": "תי×ור", "Desktop": "שולחן עבודה", - "Desktop_Notification_Test": "מבחן הודעה Desktop", + "Desktop_Notification_Test": "בדיקת הודעה שולחן עבודה", "Desktop_Notifications": "התר×ות בשולחן העבודה", "Desktop_Notifications_Disabled": "הודעות עבור שולחן עבודה ××™× ×Ÿ משופעלות. ×©× ×” ×ת הגדרות הדפדפן שלך ×× ×תה רוצה לשפעל הודעות עבור שולחן עבודה.", "Desktop_Notifications_Duration": "משך הודעות", @@ -300,13 +300,13 @@ "Duplicate_archived_channel_name": "×¢× ×©× ×¢×¨×•×¥ ב×רכיון ' %s' קיימת", "Duplicate_archived_private_group_name": "קבוצה פרטית ב×רכיון ×¢× ×”×©× ' %s' קיימת", "Duplicate_channel_name": "ערוץ ×¢× ×”×©× '% s' ×§×™×™×", - "Duplicate_private_group_name": "כבר קיימת קבוצה פרטית בש× ‚%s‘", + "Duplicate_private_group_name": "כבר קיימת קבוצה פרטית ×‘×©× â€š%s‘", "Edit": "עריכה", - "Edit_Custom_Field": "שדה מות×× ×ישית ערוך", + "Edit_Custom_Field": "ערוך שדה מות×× ×ישית", "Edit_Department": "עריכת מחלקה", "edited": "× ×¢×¨×š", "Editing_room": "עריכת חדר", - "Editing_user": "המשתמש עריכה", + "Editing_user": "עריכת משתמש", "Email": "דו×״ל", "Email_address_to_send_offline_messages": "כתובת דו×\"ל לשלוח הודעות מחוברות", "Email_already_exists": "כתובת הדו×״ל כבר קיימת", @@ -335,11 +335,11 @@ "Enter_to": "×”×™×›× ×¡ ל", "Error": "שגי××”", "error-action-not-allowed": "__action__ ×סור", - "error-application-not-found": "×פליקציה ×œ× × ×ž×¦×", + "error-application-not-found": "×פליקציה ×œ× × ×ž×¦××”", "error-archived-duplicate-name": "יש ערוץ ב×רכיון ×¢× ×”×©× '__room_name__'", "error-avatar-invalid-url": "כתובת ×תר ×ישי ×œ× ×—×•×§×™: __url__", "error-avatar-url-handling": "שגי××” בעת טיפול הגדרה ×ישית מכתובת URL (__url__) עבור __×©× ×ž×©×ª×ž×©__", - "error-cant-invite-for-direct-room": "×œ× × ×™×ª×Ÿ להזמין ×ת משתמש ×œ×—×“×¨×™× ×™×©×™×¨×™×", + "error-cant-invite-for-direct-room": "×œ× × ×™×ª×Ÿ להזמין משתמש ×œ×—×“×¨×™× ×™×©×™×¨×™×", "error-could-not-change-email": "×œ× × ×™×ª×Ÿ ×œ×©× ×•×ª ×ת כתובת הדו×\"ל", "error-could-not-change-name": "×œ× × ×™×ª×Ÿ ×œ×©× ×•×ª ×ת הש×", "error-could-not-change-username": "×œ× × ×™×ª×Ÿ ×œ×©× ×•×ª ×ת ×©× ×”×ž×©×ª×ž×©", @@ -357,7 +357,7 @@ "error-invalid-channel": "ערוץ ×œ× ×—×•×§×™.", "error-invalid-channel-start-with-chars": "ערוץ שגוי. יש להתחיל ×‘×¡×™×ž× ×™× @ ×ו #", "error-invalid-custom-field": "שדה מות×× ×ישית ×œ× ×—×•×§×™", - "error-invalid-custom-field-name": "×©× ×©×“×” מות×× ×ישית ×œ× ×—×•×§×™. השתמש רק ×ותיות, מספרי×, ×ž×§×¤×™× ×•×§×•×•×™× ×ª×—×ª×•× ×™×.", + "error-invalid-custom-field-name": "×©× ×©×“×” מות×× ×ישית ×œ× ×—×•×§×™. השתמש רק ב×ותיות, מספרי×, ×ž×§×¤×™× ×•×§×•×•×™× ×ª×—×ª×•× ×™×.", "error-invalid-description": "תי×ור שגוי", "error-invalid-domain": "×ª×—×•× ×œ× ×—×•×§×™", "error-invalid-email": "__email__ דו×\"ל ×œ× ×—×•×§×™", @@ -370,7 +370,7 @@ "error-invalid-method": "שיטה שגויה", "error-invalid-name": "×©× ×©×’×•×™", "error-invalid-password": "סיסמה שגויה", - "error-invalid-redirectUri": "חוקי redirectUri", + "error-invalid-redirectUri": "×œ× ×—×•×§×™ redirectUri", "error-invalid-role": "תפקיד שגוי", "error-invalid-room": "חדר ×œ× ×—×•×§×™", "error-invalid-room-name": "<strong> %s</strong> ××™× ×• ×©× ×—×“×¨ חוקי, <br/> להשתמש ×ותיות, מספרי×, ×ž×§×¤×™× ×•×§×•×•×™× ×ª×—×ª×•× ×™× ×‘×œ×‘×“", @@ -400,7 +400,7 @@ "error-user-not-in-room": "המשתמש ××™× ×• בחדר ×”×–×”", "error-user-registration-disabled": "×¨×™×©×•× ×ž×©×ª×ž×© מושבת", "error-user-registration-secret": "×¨×™×©×•× ×ž×©×ª×ž×© מותר רק ב×מצעות כתובת ×תר סודית", - "error-you-are-last-owner": "×תה ×”×‘×¢×œ×™× ×”×חרון. ×× × ×”×’×“×¨ ×‘×¢×œ×™× ×—×“×©×™× ×œ×¤× ×™ שעזבתי ×ת החדר.", + "error-you-are-last-owner": "×תה ×”×‘×¢×œ×™× ×”×חרון. ×× × ×”×’×“×¨ ×‘×¢×œ×™× ×—×“×©×™× ×œ×¤× ×™ שתעזוב ×ת החדר.", "Error_changing_password": "הססמה הוחלפה", "Esc_to": "×¦× ×œ", "Example_s": "לדוגמה: <code class=\"inline\">%s</code>", @@ -410,7 +410,7 @@ "Features_Enabled": "×ª×›×•× ×•×ª מופעלות", "Field": "שדה", "Field_removed": "שדה הוסר", - "File_exceeds_allowed_size_of_bytes": "קובץ עולה על גודל מוותר של בתי __size__", + "File_exceeds_allowed_size_of_bytes": "קובץ עולה על גודל מותר של ×‘×ª×™× __size__", "FileUpload": "העל×ת קובץ", "FileUpload_Enabled": "העל×ת ×§×‘×¦×™× ×ž×©×•×¤×¢×œ×ª", "FileUpload_File_Empty": "קובץ ריק", @@ -420,7 +420,7 @@ "FileUpload_MediaTypeWhiteList": "סוגי מדיה מ×ופשרי×", "FileUpload_MediaTypeWhiteListDescription": "רשימת סוגי מדיה ×ž×•×¤×¨×“×™× ×‘×¤×¡×™×§. הש×ר ריק לקבל ×ת כלל סוגי המדיה.", "FileUpload_ProtectFiles": "×”×’× ×” על ×§×‘×¦×™× ×©×”×•×¢×œ×•", - "FileUpload_ProtectFilesDescription": "רק ×ž×©×ª×ž×©×™× ×ž××•×ž×ª×™× ×ª×”×™×” גישה", + "FileUpload_ProtectFilesDescription": "רק ×œ×ž×©×ª×ž×©×™× ×ž××•×ž×ª×™× ×ª×”×™×” גישה", "FileUpload_S3_Acl": "ACL Amazon S3", "FileUpload_S3_AWSAccessKeyId": "Amazon S3 AWSAccessKeyId", "FileUpload_S3_AWSSecretAccessKey": "Amazon S3 AWSSecretAccessKey", @@ -444,18 +444,18 @@ "General": "כללי", "github_no_public_email": "×ין לך ××£ כתובת דו×״ל פומבית בחשבון ×”Ö¾GitHub שלך", "Give_a_unique_name_for_the_custom_oauth": "תן ×©× ×™×™×—×•×“×™ ב-OAuth מות×× ×ישית", - "Give_the_application_a_name_This_will_be_seen_by_your_users": "תן ×”×™×™×©×•× ×©×. ×–×” ייר××” על ידי ×”×ž×©×ª×ž×©×™× ×©×œ×š.", + "Give_the_application_a_name_This_will_be_seen_by_your_users": "תן ×©× ×œ×™×™×©×•×. ×©× ×–×” יופיע למשתמשיך", "Global": "גלוֹבָּלִי", - "GoogleSiteVerification_id": "Google Site ×ימות זיהוי", "GoogleTagManager_id": "×ž× ×”×œ ×”×ª×’×™× ×©×œ Google Id", - "Has_more": "יש עוד", "Hash": "בְּלִיל", "Header": "כּוֹתֶרֶת", "Hidden": "מוּסתָר", - "Hide_Group_Warning": "×”×× ×תה בטוח ש×תה ×ž×¢×•× ×™×™×Ÿ להסתחר ×ת הקבוצה \"%s\"?", + "Hide_Avatars": "הסתרת ×וו×טרי×", + "Hide_flextab": "הסתרת תפריט ×™×ž× ×™ בלחיצה", + "Hide_Group_Warning": "×”×× ×תה בטוח ש×תה ×ž×¢×•× ×™×™×Ÿ להסתיר ×ת הקבוצה \"%s\"?", "Hide_Private_Warning": "×”×× ×תה בטוח ש×תה רוצה להסתיר ×ת הדיון ×¢× \" %s\"?", "Hide_room": "להסתיר ×ת החדר", - "Hide_Room_Warning": "×”×× ×תה בטוח ש×תה רוצה להסתיר בחדר \" %s\"?", + "Hide_Room_Warning": "×”×× ×תה בטוח ש×תה רוצה להסתיר ×ת חדר \" %s\"?", "Hide_usernames": "הסתרת שמות משתמשי×", "Highlights": "עיקרי הדברי×", "Highlights_How_To": "כדי לקבל הודעה ×›×שר מישהו מזכיר ×ת המילה ×ו הביטוי, להוסיף ×ותו ×›×ן. × ×™×ª×Ÿ להפריד ×ž×™×œ×™× ×ו ×‘×™×˜×•×™×™× ×¢× ×¤×¡×™×§×™×. מילות דגש ××™× ×Ÿ תלויות-רישיות.", @@ -592,13 +592,13 @@ "LDAP_Host": "מ×רח", "LDAP_Host_Description": "המ×רח LDAP, למשל `ldap.example.com` ×ו` 10.0.0.30`.", "LDAP_Port": "פתחה", - "LDAP_Port_Description": "פורט לגישת LDAP. לדוגמה: `389` ×ו `636` עבור LDAPS.", + "LDAP_Port_Description": "פורט לגישת LDAP. לדוגמה: `389` ×ו `636` עבור LDAPS.", "LDAP_Reject_Unauthorized": "דחית מורשה", "LDAP_Sync_User_Avatar": "Sync משתמש ×ישי", "LDAP_Sync_User_Data": "×¡× ×›×¨×Ÿ מידע", "LDAP_Sync_User_Data_Description": "ד××’ למידע ×ž×¡×•× ×›×¨×Ÿ על המשתמש בהתחברות לשרת (ש×, ×ימייל).", "LDAP_Sync_User_Data_FieldMap": "מיפוי שדה User Data", - "LDAP_Sync_User_Data_FieldMap_Description": "הגדר כיצד שדות חשבון המשתמש (כגון ×ימייל) ישלפו מרשומת ×”-LDAP (×›×שר × ×ž×¦××”).<br/> לדוגמה, `{\"cn\":\"name\", \"mail\":\"email\"}` יבחרו ×ת ×”×©× ×”×§×¨×™× ×©×œ המשתמש מהשדה cn וה×ימייל ×©×œ×”× ×ž×”×©×“×” mail.<br/> השדות ×”××¤×©×¨×™×™× ×›×•×œ×œ×™× `name`, ו-`email`.", + "LDAP_Sync_User_Data_FieldMap_Description": "הגדר כיצד שדות חשבון המשתמש (כגון ×ימייל) ישלפו מרשומת ×”-LDAP (×›×שר × ×ž×¦××”).<br/> לדוגמה, `{\"cn\":\"name\", \"mail\":\"email\"}` יבחרו ×ת ×”×©× ×”×§×¨×™× ×©×œ המשתמש מהשדה cn וה×ימייל ×©×œ×”× ×ž×”×©×“×” mail.<br/> השדות ×”××¤×©×¨×™×™× ×›×•×œ×œ×™× `name`, ו-`email`.", "LDAP_Sync_Users": "×ž×©×ª×ž×©×™× Sync", "LDAP_Test_Connection": "בדיקת חיבור", "LDAP_Unique_Identifier_Field": "ייחודיות ×ž×–×”×™× ×©×“×”", @@ -635,7 +635,7 @@ "Log_File": "צג קבצי קו", "Log_Level": "רמה התחבר", "Log_Package": "חבילת הצג", - "Log_View_Limit": "התחבר צפו הגבל", + "Log_View_Limit": "הגבלת צפיה בלוג", "Logged_out_of_other_clients_successfully": "הביקור ×”×חרון מתוך לקוחות ××—×¨×™× ×‘×”×¦×œ×—×”", "Login": "התחברות", "Login_with": "×›× ×™×¡×” ×¢× %s", @@ -663,10 +663,10 @@ "Mentions": "×זכורי×", "Mentions_default": "××–×›×•×¨×™× (ברירת מחדל)", "Message": "הודעה", - "Message_AllowBadWordsFilter": "×פשר ×ž×™×œ×™× ×¨×¢×•×ª מסר ×¡×™× ×•×Ÿ", + "Message_AllowBadWordsFilter": "×פשר ×¡×™× ×•×Ÿ ×ž×™×œ×™× ×¨×¢×•×ª", "Message_AllowDeleting": "ל×פשר מחיקת הודעות", "Message_AllowDeleting_BlockDeleteInMinutes": "בלוק הודעה מחיק ×חרי (n) פרוטוקול", - "Message_AllowDeleting_BlockDeleteInMinutes_Description": "זן 0 להשבית חסימה.", + "Message_AllowDeleting_BlockDeleteInMinutes_Description": "הזן 0 להשבית חסימה.", "Message_AllowEditing": "ל×פשר עריכת הודעות", "Message_AllowEditing_BlockEditInMinutes": "× ×¢×œ עריכת הודעה ל×חר (n) דקות", "Message_AllowEditing_BlockEditInMinutesDescription": "הזן 0 לביטול × ×¢×™×œ×”.", @@ -676,14 +676,14 @@ "Message_AlwaysSearchRegExp": "תמיד לחפש ב×מצעות RegExp", "Message_AlwaysSearchRegExp_Description": "×× ×• ×ž×ž×œ×™×¦×™× ×œ×”×’×“×™×¨ TRUE ×× ×”×©×¤×” שלך ××™× ×” × ×ª×ž×›×ª על <a target=\"_blank\" href=\"https://docs.mongodb.org/manual/reference/text-search-languages/#text-search-languages\">חיפוש טקסט MongoDB</a> .", "Message_AudioRecorderEnabled": "מקליט ×ודיו משופעל", - "Message_AudioRecorderEnabledDescription": "דורש שקבצי 'audio/wav' יתקבלו בסוגי מדיה בהגדרות 'File Upload'.  ", + "Message_AudioRecorderEnabledDescription": "דורש שקבצי 'audio/wav' יתקבלו בסוגי מדיה בהגדרות 'File Upload'. ", "Message_BadWordsFilterList": "להוסיף ×ž×™×œ×™× ×¨×¢×•×ª לרשימה השחורה", "Message_BadWordsFilterListDescription": "להוסיף רשימה של רשימה מופרדת ×‘×¤×¡×™×§×™× ×©×œ ×ž×™×œ×™× ×¨×¢×•×ª ×œ×¡× ×Ÿ", "Message_DateFormat": "×ª×‘× ×™×ª ת×ריך", "Message_DateFormat_Description": "לעיון × ×•×¡×£: <a href=\"http://momentjs.com/docs/#/displaying/format/\" target=\"momemt\">Moment.js</a>", "Message_deleting_blocked": "הודעה זו ×œ× × ×™×ª×Ÿ למחוק עוד", "Message_editing": "עריכת הודעות", - "Message_GroupingPeriod": "הקבצה תקופה (×‘×©× ×™×•×ª)", + "Message_GroupingPeriod": "משך הקבצת הודעה (×‘×©× ×™×•×ª)", "Message_GroupingPeriodDescription": "הודעות ×ª×§×•×‘×¦× ×” ×¢× ×”×•×“×¢×” קודמת ×× ×©× ×™×”× ×ž×ותו משתמש ו×ת הזמן שהחלף ×”×™×” פחות הזמן המושכל ×‘×©× ×™×•×ª.", "Message_KeepHistory": "שמירה על היסטוריית הודעות", "Message_MaxAll": "ALL גודל ערוץ מקסימ×לי עבור הודעה", @@ -706,7 +706,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "רובוטי×", "minutes": "דקות", - "Mobile_push": "דחיפה × ×™×™×“×ª", "More_channels": "עוד ערוצי×", "More_direct_messages": "עוד הודעות ישירות", "More_groups": "קבוצות פרטיות × ×•×¡×¤×•×ª", @@ -725,42 +724,46 @@ "Name_optional": "×©× (××•×¤×¦×™×•× ×לי)", "Navigation_History": "× ×™×•×•×˜ ההיסטוריה", "New_Application": "×™×™×©×•× ×—×“×©", - "New_Custom_Field": "חדש בשדה מות×× ×ישית", + "New_Custom_Field": "שדה מות×× ×ישית חדש", "New_Department": "מחלקה חדשה", "New_integration": "שילוב חדש", "New_logs": "×™×•×ž× ×™× ×—×“×©×™×", - "New_Message_Notification": "הודעת הודעה חדשה", + "New_Message_Notification": "התרעת הודעה חדשה", "New_messages": "הודעות חדשות", "New_password": "ססמה חדשה", "New_role": "תפקיד חדש", "New_Room_Notification": "הודעת חדר חדשה", - "No_channel_with_name_%s_was_found": "×œ× × ×ž×¦× ×¢×¨×•×¥ ×¢× ×”×©× <strong>\"%s\"</strong>!", + "No_channel_with_name_%s_was_found": "×œ× × ×ž×¦× ×¢×¨×•×¥ ×¢× ×”×©× <strong>\"%s\"</strong>!", "No_channels_yet": "××™× ×š חבר ב××£ ערוץ עד ×›×”.", "No_direct_messages_yet": "×œ× ×”×ª×—×œ×ª ××£ שיחה עד ×›×”.", "No_Encryption": "×œ×œ× ×”×¦×¤× ×”", - "No_group_with_name_%s_was_found": "×œ× × ×ž×¦××” קבוצה פרטית ×¢× ×”×©× <strong>\"%s\"</strong>!", + "No_group_with_name_%s_was_found": "×œ× × ×ž×¦××” קבוצה פרטית ×¢× ×”×©× <strong>\"%s\"</strong>!", "No_groups_yet": "×œ× ×§×™×™×ž×•×ª לך קבוצות פרטיות.", "No_livechats": "×ין לך ××£ Livechats.", "No_mentions_found": "×œ× × ×ž×¦×ו ×זכורי×", "No_pinned_messages": "×ין הודעות מוצמדות", "No_results_found": "×œ× × ×ž×¦×ו תוצ×ות", "No_starred_messages": "×ין הודעות מועדפות", - "No_user_with_username_%s_was_found": "×œ× × ×ž×¦× ×”×ž×©×ª×ž×©Â <strong>\"%s\"</strong>!", + "No_user_with_username_%s_was_found": "×œ× × ×ž×¦× ×”×ž×©×ª×ž×© <strong>\"%s\"</strong>!", "Node_version": "גרסת Node", + "Normal": "× ×•×¨×ž×œ×™", "Not_authorized": "×œ× ×ž×ומת", "Not_Available": "×œ× ×–×ž×™×Ÿ", "Not_found_or_not_allowed": "×œ× × ×ž×¦× ×ו ש×ין לך הרש××”", "Nothing": "×©×•× ×“×‘×¨", "Nothing_found": "×ין תוצ×ות", + "Notification_Duration": "משך התרעה", "Notifications": "התרעות", "Notify_all_in_this_room": "להודיע לכל מי שבחדר", + "Notify_active_in_this_room": "להודיע לכל ×”×ž×—×•×‘×¨×™× ×©×‘×—×“×¨", "Num_Agents": "×¡×•×›× ×™ #", "Number_of_messages": "מספר הודעות", "OAuth_Application": "×™×™×©×•× OAuth", "OAuth_Applications": "יישומי OAuth", "Objects": "×ובייקטי×", - "Off_the_record_conversation": "Off-the-×©×™× ×”×©×™×—×”", - "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Off-the-×©×™× ×”×©×™×—×” ××™× ×” ×–×ž×™× ×” בדפדפן ×ו במכשיר שלך.", + "Off": "×œ× ×¤×•×¢×œ", + "Off_the_record_conversation": "שיחה ×œ× ×œ×¦×™×˜×•×˜", + "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "שיחה ×œ× ×œ×¦×™×˜×•×˜ ××™× ×” ×–×ž×™× ×” בדפדפן ×ו במכשיר שלך.", "Offline": "×ž× ×•×ª×§", "Offline_DM_Email": "קבלת הודעות במשך ישיר על ידי __user__", "Offline_form": "טופס ×ž× ×•×ª×§", @@ -769,6 +772,7 @@ "Offline_message": "הודעה ×ž× ×•×ª×§×ª", "Offline_success_message": "הודעת הצלחה ×ž× ×•×ª×§×ª", "Offline_unavailable": "זמין ×ž× ×•×ª×§", + "On": "פועל", "Online": "מחובר", "Only_you_can_see_this_message": "רק ×תה יכול לר×ות הודעה זו", "Oops!": "×ופס", @@ -820,7 +824,7 @@ "Please_wait_while_OTR_is_being_established": "×× × ×”×ž×ª×Ÿ בזמן OTR ומוק×", "Please_wait_while_your_account_is_being_deleted": "×× × ×”×ž×ª×Ÿ בזמן ×—×©×‘×•× ×š מתבצע מחיקה ...", "Please_wait_while_your_profile_is_being_saved": "×× × ×”×ž×ª×Ÿ ×›×שר הפרופיל שלך חוזר לקדמותו ...", - "Port": "פתחה", + "Port": "פורט", "Post_as": "×¤×¨×¡×•× ×‘×©×", "Post_to_Channel": "×¤×¨×¡×•× ×œ×¢×¨×•×¥", "Post_to_s_as_s": "×¤×¨×¡×•× ×ל <strong>%s</strong> ×‘×©× <strong>%s</strong>", @@ -858,16 +862,19 @@ "Random": "×קר××™", "Reacted_with": "הגיבו", "Reactions": "תגובות", + "Read_only": "קרי××” בלבד", + "Read_only_changed_successfully": "קרי××” בלבד ×”×©×ª× ×” בהצלחה", + "Read_only_channel": "חדר קרי××” בלבד", "Record": "הקלט", "Redirect_URI": "×”×¤× ×™×” URI", - "Refresh_keys": "מפתחות ×¨×¢× × ×™×", - "Refresh_your_page_after_install_to_enable_screen_sharing": "×¨×¢× ×Ÿ ×ת הדף ל×חר להתקין כדי ל×פשר שיתוף מסך", + "Refresh_keys": "×¨×¢× ×Ÿ מפתחות", + "Refresh_your_page_after_install_to_enable_screen_sharing": "×¨×¢× ×Ÿ ×ת הדף ל×חר ×”×ª×§× ×” כדי ל×פשר שיתוף מסך", "Register": "הרשמה ×¢× ×—×©×‘×•×Ÿ חדש", "Registration_Succeeded": "הרשמה הצליחה", "Release": "מהדורה", "Remove": "מחיקה", "Remove_Admin": "הסר ×דמין", - "Remove_as_moderator": "הסר כמו ×ž× ×—×”", + "Remove_as_moderator": "הסר ×›×ž× ×—×”", "Remove_as_owner": "הסר כבעלי×", "Remove_custom_oauth": "הסר הזדהות OAuth מות×× ×ישית", "Remove_from_room": "הסר מחדר", @@ -878,18 +885,18 @@ "Report_sent": "הדוח × ×©×œ×—", "Report_this_message_question_mark": "דווח על הודעה זו?", "Require_password_change": "דרוש ×©×™× ×•×™ סיסמה", - "Resend_verification_email": "לשלוח דו×\"ל ל×ימות", + "Resend_verification_email": "לשלוח שוב דו×\"ל ל×ימות", "Reset": "×יפוס", "Reset_password": "×יפוס ססמה", "Restart": "הפעלה מחדש", - "Restart_the_server": "×יפוס השרת", + "Restart_the_server": "×תחול השרת", "Role": "תפקיד", "Role_Editing": "עריכת תפקידי×", "Role_removed": "התפקיד הוסר", "Room": "חדר", - "Room_archivation_state": "×žÖ°×“Ö´×™× Ö¸×”", + "Room_archivation_state": "מצב", "Room_archivation_state_false": "פָּעִיל", - "Room_archivation_state_true": "ל×רכיון", + "Room_archivation_state_true": "ב×רכיון", "Room_archived": "חדר ב×רכיון", "room_changed_privacy": "סוג החדר ×”×©× ×” ל:<em>__room_type__</em> ×¢\"×™ <em>__user_by__</em>", "room_changed_topic": "× ×•×©× ×”×—×“×¨ ×©×•× ×” ל:<em>__room_topic__</em> ×¢\"×™ <em>__user_by__</em>", @@ -923,8 +930,6 @@ "Script_Enabled": "מ×ופשר סקריפט", "Search": "חיפוש", "Search_by_username": "חיפוש לפי ×©× ×ž×©×ª×ž×©", - "Search_Channels": "ערוצי חיפוש", - "Search_Direct_Messages": "חפש בהודעות ישירות", "Search_Messages": "חיפוש בהודעות", "Search_Private_Groups": "חיפוש בקבוצות פרטיות", "seconds": "×©× ×™×•×ª", @@ -948,21 +953,21 @@ "Send_invitation_email_info": "× ×™×ª×Ÿ לשלוח ×”×–×ž× ×•×ª מרובות ב×ימייל ×‘×¤×¢× ×חת", "Send_invitation_email_success": "שלחת בהצלחה ×”×–×ž× ×” ב×מצעות ×ימייל לכתובות הב×ות:", "Send_request_on_chat_close": "×œ×¤× ×•×ª לסוכן על קרוב צ'×ט", - "Send_request_on_offline_messages": "×œ×¤× ×•×ª לסוכן על הודעות מחוברות", + "Send_request_on_offline_messages": "×œ×¤× ×•×ª לסוכן על הודעות ×œ× ×ž×—×•×‘×¨×•×ª", "Send_Test": "שלח מבחן", "Send_welcome_email": "שליחת דו×״ל קבלת ×¤× ×™×", "Send_your_JSON_payloads_to_this_URL": "שלח ×ž×˜×¢× ×™ JSON שלך לכתובת ×תר זו.", "Sending": "ש×ְלִיחָה...", "Service": "שירות", - "Set_as_moderator": "Set as ×ž× ×—×”", - "Set_as_owner": "גדר כבעלי×", + "Set_as_moderator": "הגדר ×›×ž× ×—×”", + "Set_as_owner": "הגדר כבעלי×", "Settings": "הגדרות", "Settings_updated": "ההגדרות ×¢×•×“×›× ×•", "Should_be_a_URL_of_an_image": "צריך להיות כתובת ×תר של ×ª×ž×•× ×”.", "Should_exists_a_user_with_this_username": "המשתמש חייב להיות ×§×™×™×.", "Show_all": "הצג הכול", "Show_more": "הר××” עוד", - "Show_only_online": "הצג רק ב××™× ×˜×¨× ×˜", + "Show_only_online": "הצג רק מחוברי×", "Show_preregistration_form": "צג טופס הרשמה מר×ש", "Showing_archived_results": "<p style=\";text-align:right;direction:rtl\"> מציג תוצ×ות <b>ב×רכיון %s</b> </p>", "Showing_online_users": "מציג <b>__total_showing__</b> מתוך __total__ משתמשי×", @@ -982,9 +987,9 @@ "Smileys_and_People": "×¡×ž×™×™×œ×™× & ×× ×©×™×", "SMS_Enabled": "SMS מופעל", "SMTP": "SMTP", - "SMTP_Host": "מ×רח ", + "SMTP_Host": "מ×רח ", "SMTP_Password": "ססמה ל־", - "SMTP_Port": "פתחת SMTP", + "SMTP_Port": "פורט SMTP", "SMTP_Test_Button": "הגדרות SMTP מבחן", "SMTP_Username": "×©× ×ž×©×ª×ž×© ב־SMTP", "Sound": "שמע", @@ -1028,7 +1033,7 @@ "Sync_Users": "×ž×©×ª×ž×©×™× Sync", "Tag": "תָג", "Test_Connection": "בדיקת חיבור", - "Test_Desktop_Notifications": "הודעות בשולחן העבודה מבחן", + "Test_Desktop_Notifications": "בדיקת הודעות בשולחן עבודה", "Thank_you_exclamation_mark": "תודה רבה!", "Thank_you_for_your_feedback": "תודה לך על המשוב", "The_application_name_is_required": "×©× ×”×פליקציה × ×“×¨×©", @@ -1037,7 +1042,7 @@ "The_field_is_required": "השדה %s ×”×•× ×—×•×‘×”.", "The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "×©×™× ×•×™ גודל ×”×ª×ž×•× ×” ×œ× ×™×¢×‘×•×“ ×›×™ ×× ×—× ×• ×œ× ×™×›×•×œ×™× ×œ×–×”×•×ª ImageMagick ×ו GraphicsMagick מותקן על השרת שלך.", "The_redirectUri_is_required": "RedirectUri × ×“×¨×©", - "The_server_will_restart_in_s_seconds": "השרת יפעיל ×ת עצמו מחדש בעוד ", + "The_server_will_restart_in_s_seconds": "השרת יפעיל ×ת עצמו מחדש בעוד ", "The_setting_s_is_configured_to_s_and_you_are_accessing_from_s": "×”- <strong>S הגדרת%</strong> <strong>מוגדר %s</strong> ו×תה × ×™×’×© <strong>מ %s!</strong>", "The_user_will_be_removed_from_s": "המשתמש יוסר %s", "The_user_wont_be_able_to_type_in_s": "המשתמש ×œ× ×™×•×›×œ להקליד %s", @@ -1047,7 +1052,7 @@ "theme-color-primary-font-color": "צבע ×¤×•× ×˜ ר×שי", "theme-color-secondary-background-color": "צבע רקע ×ž×©× ×™", "theme-color-secondary-font-color": "צבע ×¤×•× ×˜ ×ž×©× ×™", - "theme-color-tertiary-background-color": "צבע סטטוס ", + "theme-color-tertiary-background-color": "צבע סטטוס ", "theme-color-tertiary-font-color": "צבע ×¤×•× ×˜ שלישי", "theme-color-link-font-color": "צבע ×¤×•× ×˜ של ×œ×™× ×§", "theme-color-info-font-color": "צבע של ×¤×•× ×˜ Info", @@ -1058,10 +1063,10 @@ "theme-color-status-online": "צבע סטטוס זמין", "theme-color-unread-notification-color": "צבע הודעות ×©×œ× × ×§×¨×ו", "theme-custom-css": "CSS בהת×מה ×ישית", - "There_are_no_agents_added_to_this_department_yet": "×ין ×¡×•×›× ×™× ×”×•×¡×™×£ למחלקה זו עדיין.", + "There_are_no_agents_added_to_this_department_yet": "×ין ×¡×•×›× ×™× ×©× ×•×¡×¤×• למחלקה זו עדיין.", "There_are_no_integrations": "×ין ו××™× ×˜×’×¨×¦×™×•×ª", "There_are_no_users_in_this_role": "×ין ×ž×©×ª×ž×©×™× ×‘×ª×¤×§×™×“ ×–×”.", - "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "דו×\"ל ×–×” כבר × ×¢×©×” שימוש ×•×œ× ×ומת. ×©× ×” ×ת הסיסמה שלך.", + "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "בדו×\"ל ×–×” כבר × ×¢×©×” שימוש ×•×”×•× ×œ× ×ומת. ×©× ×” ×ת הסיסמה שלך.", "This_is_a_desktop_notification": "זוהי הודעת שולחן עבודה", "This_is_a_push_test_messsage": "זוהי הודעת בדיקה של ×”-push", "This_room_has_been_archived_by__username_": "חדר ×–×” כבר ב×רכיון ידי __×©× ×ž×©×ª×ž×©__", @@ -1088,10 +1093,11 @@ "UI_DisplayRoles": "תפקידי תצוגה", "UI_Merge_Channels_Groups": "מיזוג קבוצות פרטיות ×¢× ×¢×¨×•×¦×™×", "Unarchive": "הוצ××” מ×רכיון", - "Unmute_someone_in_room": "מישהו לבטל ×ת ההשתקה בחדר", - "Unmute_user": "המשתמש לבטל ×ת ההשתקה", + "Unmute_someone_in_room": "ביטול השתקה עבור מישהו בחדר", + "Unmute_user": "בטל השתקת משתמש", "Unnamed": "×œ×œ× ×©×", "Unpin_Message": "שחרור הודעה", + "Unread_Alert": "התרעה על ×œ× × ×§×¨×", "Unread_Rooms": "×—×“×¨×™× ×©×œ× × ×§×¨×ו", "Unread_Rooms_Mode": "מצב ×—×“×¨×™× ×©×œ× × ×§×¨×ו", "Unstar_Message": "הסר ממועדפי×", @@ -1099,13 +1105,15 @@ "Uploading_file": "מעלה קובץ...", "Uptime": "uptime", "URL": "כתובת ×”×תר", - "Use_account_preference": "השתמש העדפת חשבון", + "Use_account_preference": "השתמש בהעדפת חשבון", "Use_Emojis": "שימוש ב×ימוג׳י", + "Use_Global_Settings": "השתמש בהגדרות ברירת מחדל", "Use_initials_avatar": "שימוש בר×שי התיבות של ×©× ×”×ž×©×ª×ž×© שלך", "Use_service_avatar": "שימוש ×‘×ª×ž×•× ×” מ־%s", "Use_this_username": "יש להשתמש ×‘×©× ×”×ž×©×ª×ž×© ×”×–×”", "Use_uploaded_avatar": "שימוש ×‘×ª×ž×•× ×” שהועלתה", "Use_url_for_avatar": "השתמש ב-url עבור ×ª×ž×•× ×ª הפרופיל", + "Use_User_Preferences_or_Global_Settings": "השתמש בהגדרות משתמש ×ו הגדרות ברירת מחדל", "User__username__is_now_a_moderator_of__room_name_": "__×©× ×ž×©×ª×ž×©__ המשתמש עכשיו ×ž× ×—×” של __room_name__", "User__username__is_now_a_owner_of__room_name_": "__×©× ×ž×©×ª×ž×©__ המשתמש עכשיו ×‘×¢×œ×™× ×©×œ __room_name__", "User__username__removed_from__room_name__moderators": "__×©× ×ž×©×ª×ž×©__ המשתמש הוסר ממפקחי __room_name__", @@ -1113,7 +1121,7 @@ "User_added": "המשתמש × ×•×¡×£.", "User_added_by": "המשתמש <em>__user_added__</em> × ×•×¡×£ על ידי <em>__user_by__</em>", "User_added_successfully": "משתמש × ×•×¡×£ בהצלחה", - "User_doesnt_exist": "××£ משתמש ×œ× ×§×™×™× ×‘×©× `@%s`.", + "User_doesnt_exist": "××£ משתמש ×œ× ×§×™×™× ×‘×©× `@%s`.", "User_has_been_activated": "המשתמש הופעל", "User_has_been_deactivated": "המשתמש × ×•×˜×¨×œ", "User_has_been_deleted": "המשתמש הוסר", @@ -1141,7 +1149,7 @@ "User_unmuted_in_room": "משתמש ההשתקה בחדר", "User_updated_successfully": "המשתמש עודכן בהצלחה", "Username": "×©× ×ž×©×ª×ž×©", - "Username_and_message_must_not_be_empty": "×©× ×ž×©×ª×ž×© הודעה ×œ× ×™×›×•×œ×™× ×œ×”×™×•×ª ריקי×.", + "Username_and_message_must_not_be_empty": "×©× ×ž×©×ª×ž×© והודעה ×œ× ×™×›×•×œ×™× ×œ×”×™×•×ª ריקי×.", "Username_cant_be_empty": "יש להזין ×©× ×ž×©×ª×ž×©", "Username_Change_Disabled": "×ž× ×”×œ המערכת שלך ביטל ×ת ×”×פשרות ×œ×©× ×•×ª שמות משתמשי×", "Username_denied_the_OTR_session": "__×©× ×ž×©×ª×ž×©__ הכחיש ×ת הפגישה OTR", @@ -1190,11 +1198,11 @@ "Why_do_you_want_to_report_question_mark": "מדוע ×‘×¨×¦×•× ×š לדווח?", "will_be_able_to": "תוכל", "Yes": "כן", - "Yes_clear_all": "כן, הכל × ×•×§×”!", + "Yes_clear_all": "כן, × ×§×” הכל!", "Yes_delete_it": "כן, למחוק ×ותה!", "Yes_hide_it": "כן, להסתיר ×ת ×–×”!", "Yes_leave_it": "כן, להש×יר ×ותו!", - "Yes_mute_user": "כן, המשתמש ×יל×!", + "Yes_mute_user": "כן, השתק משתמש!", "Yes_remove_user": "כן, להסיר ×ת המשתמש!", "You": "×תה", "you_are_in_preview_mode_of": "You are in preview mode of channel #<strong>__room_name__</strong>", @@ -1206,20 +1214,20 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "×תה יכול להשתמש webhooks לשלב LiveChat בקלות ×¢× CRM שלך.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "×תה ×œ× ×™×›×•×œ להש×יר בחדר LiveChat. ×× ×, השתמש בלחצן הסגירה.", "You_have_been_muted": "×תה הושתקת ×•×œ× ×™×›×•×œ לדבר בחדר ×”×–×”", - "You_have_not_verified_your_email": "×œ× ×ימת הדו×\"ל שלך.", + "You_have_not_verified_your_email": "×œ× ×ימתת הדו×\"ל שלך.", "You_have_successfully_unsubscribed": "הוסרת מרשימת התפוצה בהצלחה.", "You_need_confirm_email": "עליך ל×מת ×ת כתובת הדו×״ל על ×ž× ×ª להתחבר!", "You_need_install_an_extension_to_allow_screen_sharing": "צריך להתקין הרחבה כדי ל×פשר שיתוף מסך", "You_need_to_change_your_password": "×תה צריך ×œ×©× ×•×ª ×ת ×”×¡×™×¡×ž× ×©×œ×š", "You_need_to_type_in_your_password_in_order_to_do_this": "×תה צריך להקליד ×ת הסיסמה שלך על ×ž× ×ª לעשות ×–×ת!", "You_need_to_type_in_your_username_in_order_to_do_this": "×תה צריך להקליד ×ת ×©× ×”×ž×©×ª×ž×© שלך כדי לעשות ×ת ×–×”!", - "You_need_to_verifiy_your_email_address_to_get_notications": "×תה צריך verifiy כתובת הדו×\"ל שלך כדי לקבל הודעות", + "You_need_to_verifiy_your_email_address_to_get_notications": "×תה צריך ל×מת ×ת כתובת הדו×\"ל שלך כדי לקבל הודעות", "You_need_to_write_something": "עליך לכתוב משהו!", "You_should_inform_one_url_at_least": "עליך להגדיר כתובת ×חת לפחות.", "You_should_name_it_to_easily_manage_your_integrations": "×תה צריך ×©× ×ת ×–×” כדי ×œ× ×”×œ ו××™× ×˜×’×¨×¦×™×•×ª שלך בקלות.", "You_will_not_be_able_to_recover": "×œ× ×ª×”×™×” לך ×פשרות לשחזר ×ת ההודעה הזו!", "You_will_not_be_able_to_recover_file": "×תה ×œ× ×ª×•×›×œ לשחזר קובץ ×–×”!", - "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "×œ× ×ª×§×‘×œ הודעות דו×\"ל, ×›×™ ×תה ×œ× ×ž×ומת הדו×\"ל שלך.", + "You_wont_receive_email_notifications_because_you_have_not_verified_your_email": "×œ× ×ª×§×‘×œ הודעות דו×\"ל, ×›×™ ×œ× ×ימתת הדו×\"ל שלך.", "Your_email_has_been_queued_for_sending": "הדו×\"ל שלך × ×•×¡×£ לתור לשליחה", "Your_entry_has_been_deleted": "הרשומה שלך × ×ž×—×§×”", "Your_file_has_been_deleted": "הקובץ שלך × ×ž×—×§.", diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json index f83f6ffdc93a060b40f72dcb97af6e925b6be2ce..36e241e06ed8797e492c1681e8fa1b2442553457 100644 --- a/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -41,6 +41,7 @@ "Accounts_Enrollment_Email_Default": "<h2> DobrodoÅ¡li u </h2><h1> [Site_Name] </h1><p> Idite na [Site_URL] i pokuÅ¡ajte najbolje open source chat rjeÅ¡enje danas! </p>", "Accounts_Enrollment_Email_Description": "Možete koristiti sljedeće oznake: <br/><ul><li>[name], [fname], [lname] za korisniÄko ime, ime te prezime.</li><li>[email] za korisnikov email.</li><li>[Site_Name] i [Site_URL] za naziv aplikacije i URL.\n</li></ul> ", "Accounts_Enrollment_Email_Subject_Default": "Dobro doÅ¡li na [Site_Name]", + "Accounts_ForgetUserSessionOnWindowClose": "Zaboravi korisnikovu prijavu pri zatvaranju prozora", "Accounts_Iframe_api_method": "API metoda", "Accounts_Iframe_api_url": "Api Url", "Accounts_iframe_enabled": "Omogućeno", @@ -55,10 +56,12 @@ "Accounts_OAuth_Custom_id": "Id", "Accounts_OAuth_Custom_Identity_Path": "Put identiteta", "Accounts_OAuth_Custom_Login_Style": "Stil Prijave", + "Accounts_OAuth_Custom_Merge_Users": "Spoji korisnike", "Accounts_OAuth_Custom_Scope": "Å irina zahtjeva", "Accounts_OAuth_Custom_Secret": "Tajna", "Accounts_OAuth_Custom_Token_Path": "Putanja Tokena", "Accounts_OAuth_Custom_Token_Sent_Via": "Token poslan putem", + "Accounts_OAuth_Custom_Username_Field": "Polje korisniÄkog imena", "Accounts_OAuth_Facebook": "Facebook prijava", "Accounts_OAuth_Facebook_callback_url": "URL povratnog poziva Facebook", "Accounts_OAuth_Facebook_id": "ID aplikacije Facebook", @@ -106,6 +109,8 @@ "Accounts_RegistrationForm_SecretURL_Description": "Morate navesti nasumiÄni niz znakova koji će biti dodan u vaÅ¡u registracijski URL. Na primjer: https://demo.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "Zahtijevaj ime pri prijavi", "Accounts_RequirePasswordConfirmation": "Zahtjevaj potvrdu lozinke", + "Accounts_SetDefaultAvatar": "Namjesti zadani Avatar", + "Accounts_SetDefaultAvatar_Description": "PokuÅ¡ava odrediti zadani avatar prema OAuth raÄunu ili Gravataru", "Accounts_ShowFormLogin": "Prikaži obrazac za prijavu", "Accounts_UseDefaultBlockedDomainsList": "Koristi zadanu listu blokiranih domena", "Accounts_UseDNSDomainCheck": "Koristi DNS provjeru domena", @@ -141,6 +146,7 @@ "All_messages": "Sve poruke", "Allow_Invalid_SelfSigned_Certs": "Dopusti neispravne samopotpisane certifikate", "Allow_Invalid_SelfSigned_Certs_Description": "Dopusti nevažeće i samopotpisane SSL certifikate za poveznicu validacije i pretpregleda.", + "Always_open_in_new_window": "Uvijek otvori u novom prozoru", "Analytics_features_enabled": "Omogućene znaÄajke", "Analytics_features_messages_Description": "Prati prilagoÄ‘ene dogaÄ‘aje vezane za postupke korisnika na porukama.", "Analytics_features_rooms_Description": "Prati prilagoÄ‘ene dogaÄ‘anja vezana za aktivnosti u sobu ili grupi (stvaranje, napuÅ¡tanje, brisanje).", @@ -211,6 +217,7 @@ "Back_to_integrations": "Povratak na integracije", "Back_to_login": "Natrag na prijavu", "Back_to_permissions": "Povratak na dozvole", + "Block_User": "Blokiraj korisnika", "Body": "Tijelo", "bold": "podebljaj", "bot_request": "Bot zahtjev", @@ -224,9 +231,11 @@ "busy_male": "zauzet", "Busy_male": "Zauzet", "by": "od", + "cache_cleared": "Predmemorija oÄišćena", "Cancel": "Otkaži", "Cancel_message_input": "Otkaži", "Cannot_invite_users_to_direct_rooms": "Ne mogu pozvati korisnike da izravne sobe", + "CAS_autoclose": "Automatski zatvori prijavni prozor", "CAS_button_color": "Pozadinska boja gumba za prijavu", "CAS_button_label_color": "Boja teksta gumba za prijavu", "CAS_enabled": "Omogućeno", @@ -253,6 +262,8 @@ "Choose_messages": "Odaberite poruke", "Choose_the_alias_that_will_appear_before_the_username_in_messages": "Odaberite pseudonim koji će se pojaviti pred korisniÄko ime u porukama.", "Choose_the_username_that_this_integration_will_post_as": "Odaberite korisniÄko ime za koje će ova integracija objavljivati.", + "clear": "OÄisti", + "clear_cache_now": "OÄisti sada predmemoriju", "Clear_all_unreads_question": "OÄisti sve neproÄitane?", "Click_here": "Kliknite ovdje", "Client_ID": "ID klijenta", @@ -303,6 +314,8 @@ "Custom_Translations_Description": "Treba biti ispravan JSON gdje su kljuÄevi jezici koji zadrže rijeÄnik kljuÄeva i prijevoda. Npr: \n <code>{\n \"en\": {\n  \"key\": \"translation\"\n },\n \"pt\": {\n  \"key\": \"tradução\"\n }\n}</code>", "Dashboard": "Kontrolna ploÄa", "Date": "Datum", + "Date_From": "Od", + "Date_to": "za", "days": "dana", "DB_Migration": "Baza podataka migracija", "DB_Migration_Date": "Datum migracije baze", @@ -334,6 +347,7 @@ "Do_you_want_to_change_to_s_question": "Želite li promijeniti na <strong> %s?</strong>", "Domain": "Domena", "Domains": "Domene", + "Download_Snippet": "Preuzmi", "Drop_to_upload_file": "Ispusti datoteku kako bi ju prenio", "Dry_run": "Testno pokretanje", "Dry_run_description": "Poslat ćemo samo jedan email, istoj adresi koja je u Od polju. Email mora pripadati postojećem korisniku.", @@ -401,11 +415,13 @@ "error-invalid-channel-start-with-chars": "Nevažeća soba. PoÄnite s @ ili #", "error-invalid-custom-field": "Nevažeće prilagoÄ‘eno polje", "error-invalid-custom-field-name": "Neispravan naziv prilagoÄ‘enog polje. Koristite samo slova, brojeve, crtice i podvlake.", + "error-invalid-date": "PogreÅ¡an datum.", "error-invalid-description": "Nevažeći opis", "error-invalid-domain": "Domena nije valjana", "error-invalid-email": "PogreÅ¡an __email__ email", "error-invalid-file-height": "Neispravna visina datoteke", "error-invalid-file-type": "PogreÅ¡na vrsta datoteke", + "error-direct-message-file-upload-not-allowed": "Dijeljenje datoteka nije dozvoljeno u direktnim porukama", "error-invalid-file-width": "PogreÅ¡na Å¡irina datoteke", "error-invalid-from-address": "Unijeta je kriva OD adresa", "error-invalid-integration": "PogreÅ¡na integracija", @@ -460,9 +476,12 @@ "Field_removed": "Polje je uklonjeno", "Field_required": "Polje je obavezno", "File_exceeds_allowed_size_of_bytes": "Datoteka premaÅ¡uje dopuÅ¡tenu veliÄinu __size__.", + "File_not_allowed_direct_messages": "Dijeljenje datoteka nije dozvoljeno u direktnim porukama", "File_type_is_not_accepted": "Tip datoteke nije prihvaćen", "FileUpload": "Prijenos datoteke", "FileUpload_Enabled": "Prijenos Datoteka Omogućeno", + "FileUpload_Disabled": "Prijenosi datoteka su onemogućeni", + "FileUpload_Enabled_Direct": "Prijenos datoteka u direktnim porukama", "FileUpload_File_Empty": "Prazna datoteka", "FileUpload_FileSystemPath": "OdrediÅ¡te u sustavu", "FileUpload_MaxFileSize": "Maksimalna VeliÄina Prenijete Datoteke (u bajtovima)", @@ -503,10 +522,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Daj jedinstveno ime prilagoÄ‘enom OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Dajte ime aplikaciji. To će korisnici vidjeti.", "Global": "Globalno", - "GoogleSiteVerification_id": "Google Site verifikacijski Id", "GoogleTagManager_id": "Google Tag Manager Id", "Guest_Pool": "Popis gostiju", - "Has_more": "Ima viÅ¡e", "Hash": "Hash", "Header": "Zaglavlje", "Hidden": "Skriveno", @@ -543,7 +560,7 @@ "Importer_Archived": "Arhivirano", "Importer_done": "Uvoz dovrÅ¡en!", "Importer_finishing": "ZavrÅ¡avanje uvoza.", - "Importer_From_Description": "Uvozi __from __ podatke u Rocket.Chat.", + "Importer_From_Description": "Uvezi __from __ podatke u Rocket.Chat.", "Importer_import_cancelled": "Uvoz je otkazan.", "Importer_import_failed": "DoÅ¡lo je do pogreÅ¡ke pri izvrÅ¡avanju uvoza.", "Importer_importing_channels": "Prebacivanje kanala.", @@ -798,7 +815,6 @@ "Meta_robots": "Roboti", "Min_length_is": "Minimalna dužina je %s", "minutes": "minuta", - "Mobile_push": "Mobilne push obavijesti", "Monday": "Ponedjeljak", "Monitor_history_for_changes_on": "Prati povijest za promjena na", "More_channels": "ViÅ¡e kanala", @@ -1045,8 +1061,6 @@ "Script_Enabled": "Skripta Omogućena", "Search": "Traži", "Search_by_username": "Pretraživanje po korisniÄkom imenu", - "Search_Channels": "Pretraži Kanale", - "Search_Direct_Messages": "Traži izravne poruke", "Search_Messages": "Pretraži poruke", "Search_Private_Groups": "Traži privatne grupe", "seconds": "sekunde", @@ -1303,7 +1317,7 @@ "Users_in_role": "Korisnici u ulozi", "UTF8_Names_Slugify": "UTF8 Imena Slugify", "UTF8_Names_Validation": "UTF8 Provjera Imena", - "UTF8_Names_Validation_Description": "Nemojte dopustiti posebne znakove i razmake. Možete koristiti - _ i . ali ne i na kraju imena", + "UTF8_Names_Validation_Description": "RegExp koji će biti koriÅ¡ten za provjeru korisniÄkih imena i imena soba", "Verification_email_sent": "Provjera e-maila poslana", "Verified": "Ovjeren", "Version": "Verzija", diff --git a/packages/rocketchat-i18n/i18n/hu.i18n.json b/packages/rocketchat-i18n/i18n/hu.i18n.json index c3add36993c39ae2f9dd47d9b8e41f088fb9e0ce..9a1c4837bac65be04174fff7daca8f0bb54e6c2f 100644 --- a/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -447,9 +447,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Adj egy egyedi nevet az egyéni OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Adjon az alkalmazás nevét. Ez látható lesz az Ön számára.", "Global": "Globális", - "GoogleSiteVerification_id": "Google WebhelyellenÅ‘rzÅ‘ Id", "GoogleTagManager_id": "Google CÃmkekezelÅ‘ Id", - "Has_more": "több", "Hash": "hash", "Header": "Fejléc", "Hidden": "Rejtett", @@ -707,7 +705,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "robotok", "minutes": "percek", - "Mobile_push": "Mobile push-", "More_channels": "Még több csatorna", "More_direct_messages": "Több közvetlen üzenetek", "More_groups": "Több privát csoport", @@ -924,8 +921,6 @@ "Script_Enabled": "Script engedélyezve", "Search": "Keresés", "Search_by_username": "Keresés felhasználónév", - "Search_Channels": "Keresés Csatornák", - "Search_Direct_Messages": "Keresés közvetlen üzenetek", "Search_Messages": "Üzenetek keresése", "Search_Private_Groups": "Keresés Privát Csoportok", "seconds": "másodperc", diff --git a/packages/rocketchat-i18n/i18n/id.i18n.json b/packages/rocketchat-i18n/i18n/id.i18n.json index 27e5cd281bbe92219f570b4def2ef4fbc164b209..c41f59d8ca82474050b926e05feea905f019c271 100644 --- a/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/packages/rocketchat-i18n/i18n/id.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Berikan nama unik untuk kustom oauth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Memberikan aplikasi nama. Ini akan terlihat oleh pengguna Anda.", "Global": "Global", - "GoogleSiteVerification_id": "Situs Verifikasi Id Google", "GoogleTagManager_id": "Google Id Pengelola Tag", - "Has_more": "Ada lebih lanjut", "Hash": "hash", "Header": "Header", "Hidden": "Tersembunyi", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "minutes": "menit", - "Mobile_push": "mobile push", "More_channels": "Lebih lanjut channel", "More_direct_messages": "pesan lebih langsung", "More_groups": "Grup Privat lebih lanjut", @@ -923,8 +920,6 @@ "Script_Enabled": "Script Diaktifkan", "Search": "Pencarian", "Search_by_username": "Cari berdasarkan nama", - "Search_Channels": "Cari Saluran", - "Search_Direct_Messages": "Cari Pesan Langsung", "Search_Messages": "Pencarian di Pesan", "Search_Private_Groups": "Cari Grup Swasta", "seconds": "detik", diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index 8d01c5dbb8d9d460c69e3463d5093b49345f7291..9771a6653c974ab6a7d17d0636a86e93d2816b00 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -500,10 +500,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Dai un nome univoco per OAuth personalizzato", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Dai un nome all'applicazione. Sarà visibile agli utenti.", "Global": "Globale", - "GoogleSiteVerification_id": "Google Site Verification Id", "GoogleTagManager_id": "Google Tag Manager Id", "Guest_Pool": "Stanza ospiti", - "Has_more": "Ha di più", "Hash": "Hash", "Header": "Header", "Hidden": "Nascosto", @@ -813,7 +811,6 @@ "Meta_robots": "Robots", "Min_length_is": "nghezza minima é %s", "minutes": "minuti", - "Mobile_push": "Notifiche Push", "Monday": "Lunedì", "Monitor_history_for_changes_on": "Osserva lo storico per le modifiche su", "More_channels": "Piu canali", @@ -1062,8 +1059,6 @@ "Script_Enabled": "Abilita Script ", "Search": "Cerca", "Search_by_username": "Cerca per nome utente", - "Search_Channels": "Ricerca Canali", - "Search_Direct_Messages": "Cerca messaggi diretti", "Search_Messages": "Cerca Messaggi", "Search_Private_Groups": "Ricerca gruppi privati", "seconds": "secondi", diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json index 8d474fe708a8fd833d6acf2625699cff923476e0..8cc4b4e22f4ef63c61d1718ad5446aeb098db92c 100644 --- a/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -449,9 +449,7 @@ "Give_a_unique_name_for_the_custom_oauth": "カスタムOAuth ã®ä¸€æ„ãªåå‰ã‚’入力ã—ã¦ãã ã•ã„", "Give_the_application_a_name_This_will_be_seen_by_your_users": "アプリケーションåを入力ã—ã¦ãã ã•ã„。ã“ã®åå‰ã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«è¡¨ç¤ºã•れã¾ã™ã€‚", "Global": "ã‚°ãƒãƒ¼ãƒãƒ«", - "GoogleSiteVerification_id": "Googleサイトã®ç¢ºèªåŒä¸Š", "GoogleTagManager_id": "Google タグマãƒãƒ¼ã‚¸ãƒ£ãƒ¼ ID", - "Has_more": "ã•らã«ã‚りã¾ã™", "Hash": "ãƒãƒƒã‚·ãƒ¥", "Header": "ヘッダ", "Hidden": "éžè¡¨ç¤ºä¸", @@ -713,7 +711,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "robots属性", "minutes": "分", - "Mobile_push": "モãƒã‚¤ãƒ«ãƒ—ッシュ通知", "Monitor_history_for_changes_on": "å±¥æ´ã®å¤‰æ›´ã‚’監視ã™ã‚‹", "More_channels": "ãã®ä»–ã®ãƒãƒ£ãƒ³ãƒãƒ«", "More_direct_messages": "ãã®ä»–ã®ãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸", @@ -939,8 +936,6 @@ "Script_Enabled": "スクリプトを有効ã«ã™ã‚‹", "Search": "検索", "Search_by_username": "ユーザーåã§æ¤œç´¢", - "Search_Channels": "ãƒãƒ£ãƒ³ãƒãƒ«ã‚’検索", - "Search_Direct_Messages": "ダイレクトメッセージを検索", "Search_Messages": "メッセージを検索", "Search_Private_Groups": "プライベートグループを検索", "seconds": "ç§’", diff --git a/packages/rocketchat-i18n/i18n/km.i18n.json b/packages/rocketchat-i18n/i18n/km.i18n.json index 0f15b6e2c59d5cbe6f844813c15f990161941906..2374ff4d1139cf1e90478811edd9482b0af74c8c 100644 --- a/packages/rocketchat-i18n/i18n/km.i18n.json +++ b/packages/rocketchat-i18n/i18n/km.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "ផ្ážáž›áŸ‹â€‹áž±áŸ’យ​ឈ្មោះ​ážáŸ‚​មួយ​គážáŸ‹â€‹ážŸáž˜áŸ’រាប់ oauth ផ្ទាល់​ážáŸ’លួន", "Give_the_application_a_name_This_will_be_seen_by_your_users": "ផ្ážáž›áŸ‹áž±áŸ’យកម្មវិធីនáŸáŸ‡áž˜áž¶áž“ឈ្មោះមួយ។ áž“áŸáŸ‡áž“ឹងážáŸ’រូវបានគáŸáž˜áž¾áž›ážƒáž¾áž‰ážŠáŸ„យអ្នកប្រើរបស់អ្នក។", "Global": "សកល", - "GoogleSiteVerification_id": "ការផ្ទៀងផ្ទាážáŸ‹áž›áŸážážŸáž˜áŸ’គាល់របស់ Google ážáŸ†áž”ន់បណ្ážáž¶áž‰", "GoogleTagManager_id": "ការគ្រប់គ្រងស្លាកលáŸážážŸáž˜áŸ’គាល់របស់ Google", - "Has_more": "សញ្ញា Has ច្រើនទៀáž", "Hash": "ហាស់", "Header": "បឋមកážáž¶", "Hidden": "លាក់", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "កូដ MSValidate.01", "Meta_robots": "មនុស្ស​យន្áž", "minutes": "នាទី", - "Mobile_push": "ការជំរុញទូរសáŸáž–្ទដៃ", "More_channels": "ប៉ុស្ážáž·áŸâ€‹áž…្រើន​ទៀáž", "More_direct_messages": "សារដោយផ្ទាល់", "More_groups": "ក្រុមឯកជនច្រើនទៀáž", @@ -923,8 +920,6 @@ "Script_Enabled": "ស្គ្រីបបានអនុញ្ញាáž", "Search": "ស្វែង​រក", "Search_by_username": "ការស្វែងរកដោយប្រើឈ្មោះអ្នកប្រើ", - "Search_Channels": "ស្វែងរកឆានែល", - "Search_Direct_Messages": "ស្វែងរកសារដោយផ្ទាល់", "Search_Messages": "ស្វែងរក​សារ", "Search_Private_Groups": "ស្វែងរកឯកជនក្រុម", "seconds": "វិនាទ", diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json index d20ba06d7c7368f0926e44de2a96202dd1636897..a8349ea92fd3eaf998eaeaed550423e5186a15dc 100644 --- a/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -451,9 +451,7 @@ "Give_a_unique_name_for_the_custom_oauth": "ì‚¬ìš©ìž ì •ì˜ OAuthì— ëŒ€í•œ ê³ ìœ ì´ë¦„ ì§€ì •", "Give_the_application_a_name_This_will_be_seen_by_your_users": "ì‘ìš© 프로그램 ì´ë¦„ì„ ì§€ì •í•©ë‹ˆë‹¤. ì´ë ‡ê²Œí•˜ë©´ 사용ìžê°€ ë³¼ 수 있습니다.", "Global": "글로벌", - "GoogleSiteVerification_id": "Google 사ì´íЏ ì¸ì¦ ì´ë“œ", "GoogleTagManager_id": "Google 태그 ê´€ë¦¬ìž ì•„ì´ë””", - "Has_more": "ë” ë§Žì´", "Hash": "해시시", "Header": "머리글", "Hidden": "숨겨진", @@ -711,7 +709,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "로봇", "minutes": "ë¶„", - "Mobile_push": "ëª¨ë°”ì¼ í‘¸ì‹œ", "More_channels": "추가 채ë„", "More_direct_messages": "보다 ì§ì ‘ì ì¸ ë©”ì‹œì§€", "More_groups": "비밀 그룹 ë”보기", @@ -928,8 +925,6 @@ "Script_Enabled": "스í¬ë¦½íЏ 사용", "Search": "검색", "Search_by_username": "ì´ë¦„으로 검색", - "Search_Channels": "검색 채ë„", - "Search_Direct_Messages": "ì§ì ‘ 메시지를 검색", "Search_Messages": "메시지 찾기", "Search_Private_Groups": "ê°œì¸ ê·¸ë£¹ 검색", "seconds": "ì´ˆ", diff --git a/packages/rocketchat-i18n/i18n/ku.i18n.json b/packages/rocketchat-i18n/i18n/ku.i18n.json index 9a75d94ecb1df41f8d9668c065581a8a548a14e4..ac6d31aede773be005f66283da2d496aff55d2d0 100644 --- a/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Give a bi navê yekta ji bo OAuth custom li", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Bide ku sepan a name. Ev jî dê ji aliyê bikarhênerên xwe dît.", "Global": "Cîhane", - "GoogleSiteVerification_id": "Site Id Verification Google", "GoogleTagManager_id": "Google Id Manager Tag", - "Has_more": "bêtir", "Hash": "Hash", "Header": "header", "Hidden": "veÅŸartî", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Qûna", "minutes": "minutes", - "Mobile_push": "push Mobile", "More_channels": "کەناڵی تر", "More_direct_messages": "mesajên direct More", "More_groups": "komên More taybet", @@ -923,8 +920,6 @@ "Script_Enabled": "script çalake", "Search": "گەڕان", "Search_by_username": "Search ji aliyê bikarhêner", - "Search_Channels": "Channels Search", - "Search_Direct_Messages": "Search Messages Direct", "Search_Messages": "Messages Search", "Search_Private_Groups": "Search komên taybet", "seconds": "seconds", diff --git a/packages/rocketchat-i18n/i18n/lo.i18n.json b/packages/rocketchat-i18n/i18n/lo.i18n.json index b9eb0394e5a3be4d6e8921ee27ef7cbb33c8d205..6c71bb90512df3662e92222303c815f20ad083bc 100644 --- a/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "ໃຫ້ຊື່ເປັນເàºàºàº°àº¥àº±àºàºªà»àº²àº¥àº±àºšàºàº²àº™ OAuth ທີ່àºà»àº²àº«àº™àº»àº”ເàºàº‡", "Give_the_application_a_name_This_will_be_seen_by_your_users": "ໃຫ້ຄà»àº²àº®à»‰àºàº‡àºªàº°àº«àº¡àº±àºàº‚àºàº‡àºŠàº·à»ˆ. ນີ້ຈະໄດ້ຮັບàºàº²àº™à»€àº«àº±àº™à»‚ດàºàºœàº¹à»‰à»ƒàºŠà»‰àº‚àºàº‡àº—່ານ.", "Global": "Global", - "GoogleSiteVerification_id": "Id àºàº§àº”ສàºàºšà»€àº§àº±àºšà»„ຊàºàº¹à»‚àº", "GoogleTagManager_id": "Id Tag Manager àºàº¹à»‚àº", - "Has_more": "ມີຫຼາàº", "Hash": "hash", "Header": "Header", "Hidden": "ເຊື່àºàº‡à»„ວ້", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "ຫຸ່ນàºàº»àº™", "minutes": "ນາທີ", - "Mobile_push": "ຊຸàºàºàº¹à»‰àºàº²àº™à»‚ທລະສັບມືຖື", "More_channels": "ຊ່àºàº‡àº—າງເພີ່ມເຕີມ", "More_direct_messages": "ຂà»à»‰àº„ວາມໂດàºàºàº»àº‡", "More_groups": "àºàº¸à»ˆàº¡à»€àºàºàº°àºŠàº»àº™àº«àº¼àº²àº", @@ -923,8 +920,6 @@ "Script_Enabled": "script ເປີດໃຊ້ວຽàº", "Search": "ຄົ້ນຫາ", "Search_by_username": "ຄົ້ນຫາໂດàºàºŠàº·à»ˆàºœàº¹à»‰à»ƒàºŠà»‰", - "Search_Channels": "ຊ່àºàº‡àº„ົ້ນຫາ", - "Search_Direct_Messages": "ຄົ້ນຫາຂà»à»‰àº„ວາມໂດàºàºàº»àº‡", "Search_Messages": "àºàº²àº™àº„ົ້ນຫາ", "Search_Private_Groups": "ຄົ້ນຫາàºàº¸à»ˆàº¡à»€àºàºàºàº°àºŠàº»àº™", "seconds": "ວິນາທີ", diff --git a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index 2a9b8237bd5c03003db8c92734520b3b8296b571..dfb52d0a5e78631caffe7fa26d69c0f2d155e307 100644 --- a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Beri nama unik untuk OAuth adat", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Beri permohonan itu nama. Ini akan dapat dilihat oleh pengguna-pengguna anda.", "Global": "global", - "GoogleSiteVerification_id": "Id Pengesahan laman Google", "GoogleTagManager_id": "Id Pengurus Teg Google", - "Has_more": "Ada lagi", "Hash": "hash", "Header": "Kepala", "Hidden": "tersembunyi", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robot", "minutes": "minit", - "Mobile_push": "Mobile push", "More_channels": "Lagi saluran", "More_direct_messages": "Lebih mesej langsung", "More_groups": "Lagi kumpulan persendirian", @@ -923,8 +920,6 @@ "Script_Enabled": "Script Didayakan", "Search": "Cari", "Search_by_username": "Cari dengan nama pengguna", - "Search_Channels": "Cari Saluran", - "Search_Direct_Messages": "Mencari Mesej Langsung", "Search_Messages": "Cari Mesej", "Search_Private_Groups": "Cari Kumpulan Swasta", "seconds": "saat", diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index da8ace640ff1c1038596cc6fe7d783fd50fa5fb6..a175e83d61644546329dd248ec9dab136479aa53 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Geef een unieke naam voor de aangepaste OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Geef de toepassing een naam. Dit zal worden gezien door de gebruiker.", "Global": "Globaal", - "GoogleSiteVerification_id": "Google Site Verificatie Id", "GoogleTagManager_id": "Google Tag Manager Id", - "Has_more": "Heeft meer", "Hash": "hachee", "Header": "hoofd", "Hidden": "Verborgen", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "minutes": "minuten", - "Mobile_push": "Mobile push", "More_channels": "Meer kanalen", "More_direct_messages": "Meer directe berichten", "More_groups": "Meer privé-berichten", @@ -923,8 +920,6 @@ "Script_Enabled": "script Ingeschakeld", "Search": "Zoeken", "Search_by_username": "Zoek op gebruikersnaam", - "Search_Channels": "Zoek kanalen", - "Search_Direct_Messages": "Zoeken Direct Messages", "Search_Messages": "Berichten zoeken", "Search_Private_Groups": "Zoeken Private Groepen", "seconds": "seconden", diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json index 631a7427bf44fba8d29ae79fa708d7d8b28d3317..85a9d5cfc41444c0819f1c94eee910ffe715e7db 100644 --- a/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -504,9 +504,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Podaj unikalnÄ… nazwÄ™ dla wÅ‚asnego serwisu OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Nadaj aplikacji nazwÄ™. BÄ™dzie ona widoczna dla użytkowników.", "Global": "Åšwiatowy", - "GoogleSiteVerification_id": "Google Site Weryfikacja Id", "GoogleTagManager_id": "Menedżer tagów Google Id", - "Has_more": "Jest wiÄ™cej", "Hash": "Hash", "Header": "Nagłówek", "Hidden": "Ukryty", @@ -783,7 +781,6 @@ "Meta_robots": "Roboty", "Min_length_is": "Minimalna dÅ‚ugość to %s", "minutes": "minut", - "Mobile_push": "Mobile push", "Monday": "PoniedziaÅ‚ek", "Monitor_history_for_changes_on": "Sprawdź historiÄ™ zmian na", "More_channels": "WiÄ™cej kanałów", @@ -1021,8 +1018,6 @@ "Script_Enabled": "Skrypt włączony", "Search": "Szukaj", "Search_by_username": "Szukaj wedÅ‚ug nazwy użytkownika", - "Search_Channels": "Szukaj kanałów", - "Search_Direct_Messages": "Wyszukaj wiadomoÅ›ci bezpoÅ›rednie", "Search_Messages": "Przeszukaj wiadomoÅ›ci", "Search_Private_Groups": "Wyszukaj prywatne grupy", "seconds": "sekund", diff --git a/packages/rocketchat-i18n/i18n/pt.i18n.json b/packages/rocketchat-i18n/i18n/pt.i18n.json index cbea202314d5da03953f6fd473c6de8ee185e587..c67621277c14c45f732fafaa8b543f5d58c3e722 100644 --- a/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -307,6 +307,8 @@ "Custom_Script_Logged_Out": "Script Personalizado para usuários não logados", "Dashboard": "Dashboard", "Date": "Data", + "Date_From": "De", + "Date_to": "até", "days": "dias", "DB_Migration": "Migração de banco de dados", "DB_Migration_Date": "Data da migração do banco de dados", @@ -349,6 +351,7 @@ "Edit": "Editar", "Edit_Custom_Field": "Editar Campo Personalizado", "Edit_Department": "Editar Departamento", + "Edit_Trigger": "Editar Gatilho", "edited": "editado", "Editing_room": "Edição de sala", "Editing_user": "Edição de usuário", @@ -407,6 +410,7 @@ "error-invalid-description": "Descrição inválida", "error-invalid-domain": "DomÃnio inválido", "error-invalid-email": "__email__ não é um e-mail válido", + "error-invalid-email-address": "Endereço de e-mail inválido", "error-invalid-file-height": "Altura de arquivo inválida", "error-invalid-file-type": "Tipo de arquivo inválido", "error-invalid-file-width": "Altura de arquivo inválida", @@ -497,9 +501,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Dê um nome exclusivo para o oauth customizado", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Dê um nome à aplicação. Isso será visto por seus usuários.", "Global": "Global", - "GoogleSiteVerification_id": "ID de verificação do Google Sites", "GoogleTagManager_id": "Google Tag Manager Id", - "Has_more": "Há mais", "Hash": "Hash", "Header": "Cabeçalho", "Hidden": "Escondido", @@ -765,7 +767,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "minutes": "minutos", - "Mobile_push": "Notificação por celular", "Monitor_history_for_changes_on": "Monitorar mudanças de histórico para", "More_channels": "Mais canais", "More_direct_messages": "Mais mensagens diretas", @@ -795,6 +796,7 @@ "New_role": "Novo papel", "New_Room_Notification": "Notificação de nova sala", "New_videocall_request": "Nova requisição de chamada de vÃdeo", + "New_Trigger": "Novo Gatilho", "No_available_agents_to_transfer": "Nenhum agente disponÃvel para transferir", "No_channel_with_name_%s_was_found": "Nenhum canal com nome <strong>\"%s\"</strong> foi encontrado!", "No_channels_yet": "Você não faz parte de nenhum canal ainda.", @@ -838,6 +840,7 @@ "Oops!": "Ops", "Open": "Aberto", "Opened": "Aberto", + "Opened_in_a_new_window": "Aberto em nova janela.", "Opens_a_channel_group_or_direct_message": "Abre um canal, grupo ou mensagem direta", "optional": "opcional", "or": "ou", @@ -993,8 +996,6 @@ "Script_Enabled": "Script Ativado", "Search": "Pesquisar", "Search_by_username": "Busca por nome de usuário", - "Search_Channels": "Pesquisar Canais", - "Search_Direct_Messages": "Pesquisar Mensagens Diretas", "Search_Messages": "Pesquisar Mensagens", "Search_Private_Groups": "Pesquisar Grupos Privados", "seconds": "segundos", @@ -1175,6 +1176,7 @@ "Uploading_file": "Subindo arquivo...", "Uptime": "Tempo online", "URL": "URL", + "URL_room_prefix": "Prefixo da URL da sala", "Use_account_preference": "Use preferências da conta", "Use_Emojis": "Usar Emojis", "Use_initials_avatar": "Usar as iniciais do seu nome de usuário", @@ -1201,9 +1203,9 @@ "User_joined_channel": "Entrou no canal.", "User_joined_channel_female": "Entrou no canal.", "User_joined_channel_male": "Entrou no canal.", - "User_left": "Usuário <em>__user_left__</em> deixou a conversa.", - "User_left_female": "Usuário <em>__user_left__</em> deixou a conversa.", - "User_left_male": "Usuário <em>__user_left__</em> deixou a conversa.", + "User_left": "Saiu da conversa.", + "User_left_female": "Saiu da conversa.", + "User_left_male": "Saiu da conversa.", "User_logged_out": "Usuário não logado", "User_management": "Gerenciamento de usuários", "User_muted_by": "Usuário <em>__user_muted__</em> silenciado por <em>__user_by__</em>.", @@ -1234,10 +1236,12 @@ "UTF8_Names_Slugify": "Slugify Nomes UTF8 ", "UTF8_Names_Validation": "Validação de Nomes UTF8", "UTF8_Names_Validation_Description": "Não permitir caracteres especiais e espaços. Você pode usar - _ e. mas não no final do nome", + "Validate_email_address": "Validar endereço de e-mail", "Verification_email_sent": "E-mail de verificação enviado", "Verified": "Verificado", "Version": "Versão", "Video_Chat_Window": "VÃdeo Chat", + "Video_Conference": "VÃdeo Conferência", "Videocall_declined": "Chamada de vÃdeo negada.", "Videocall_enabled": "VÃdeoconferência habilitada", "View_All": "Ver Todos", diff --git a/packages/rocketchat-i18n/i18n/ro.i18n.json b/packages/rocketchat-i18n/i18n/ro.i18n.json index b7a13c78cd39a7872631d0d686670142c4a2d386..94f08574b0e6f9ddc176f2af419f3e4d8ff620b3 100644 --- a/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "AlegeÈ›i un nume unic pentru OAuth personalizat", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Da aplicaÈ›iei un nume. Acest lucru va fi văzut de utilizatori.", "Global": "Global", - "GoogleSiteVerification_id": "Id-ul Google de verificare site-ului", "GoogleTagManager_id": "ID Google Manager de etichete", - "Has_more": "Are mai multe", "Hash": "haÈ™iÈ™", "Header": "Antet", "Hidden": "Ascuns", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "RoboÈ›i", "minutes": "minute", - "Mobile_push": "împinge mobil", "More_channels": "Mai multe canale", "More_direct_messages": "Mai multe mesaje directe", "More_groups": "Mai multe grupuri private", @@ -923,8 +920,6 @@ "Script_Enabled": "script-ul activat", "Search": "Căutare", "Search_by_username": "Căutare după nume de utilizator", - "Search_Channels": "canale de căutare", - "Search_Direct_Messages": "Căutare mesaje directe", "Search_Messages": "Căutare mesaje", "Search_Private_Groups": "Căutare Grupuri private", "seconds": "secunde", diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index eef65c1bbc25bfc7957a4694d6dfa593e97304c7..d2bd983890313c3b2ba516f9c113a4e37d431c98 100644 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -448,9 +448,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Задайте уникальное Ð¸Ð¼Ñ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÑкого OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Задайте приложению имÑ. Оно будет видно вÑем пользователÑм.", "Global": "Общие", - "GoogleSiteVerification_id": "Идентификатор Google Site Verification", "GoogleTagManager_id": "Google Tag Manager Id", - "Has_more": "Еще", "Hash": "Ð¥Ñш", "Header": "Заголовок", "Hidden": "Скрытый", @@ -710,7 +708,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Боты", "minutes": "минут(Ñ‹)", - "Mobile_push": "Push уведомлениÑ", "More_channels": "Другие чаты", "More_direct_messages": "Больше личных Ñообщений", "More_groups": "Больше приватных чатов", @@ -930,8 +927,6 @@ "Script_Enabled": "Сценарий включен", "Search": "ПоиÑк", "Search_by_username": "ПоиÑк по имени пользователÑ", - "Search_Channels": "ПоиÑк каналов", - "Search_Direct_Messages": "ПоиÑк личных Ñообщений", "Search_Messages": "ПоиÑк Ñообщений", "Search_Private_Groups": "ПоиÑк приватных чатов", "seconds": "Ñекунд(Ñ‹)", diff --git a/packages/rocketchat-i18n/i18n/sq.i18n.json b/packages/rocketchat-i18n/i18n/sq.i18n.json index 3d19d80484e517a4aa363e5ebebe040ced7b9486..2789b1173d7f2293b71cfb758cf3bacaa4acc3d2 100644 --- a/packages/rocketchat-i18n/i18n/sq.i18n.json +++ b/packages/rocketchat-i18n/i18n/sq.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Jepni një emër të veçantë për OAuth porosi", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Jepni aplikacionin një emër. Kjo do të shihet nga përdoruesit e juaj.", "Global": "global", - "GoogleSiteVerification_id": "Site Verifikimi Id Google", "GoogleTagManager_id": "Google Tag Menaxheri Id", - "Has_more": "Ka më shumë", "Hash": "hashish", "Header": "arkitra", "Hidden": "I fshehur", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "minutes": "minuta", - "Mobile_push": "shtytje Mobile", "More_channels": "Më shumë kanale", "More_direct_messages": "Më shumë mesazhe të drejtpërdrejta", "More_groups": "Më shumë grupe private", @@ -923,8 +920,6 @@ "Script_Enabled": "script aktivizuar", "Search": "Kërko", "Search_by_username": "Kërko sipas emrin", - "Search_Channels": "Kërko Kanalet", - "Search_Direct_Messages": "Kërko mesazhe të drejtpërdrejta", "Search_Messages": "Kërko mesazhet", "Search_Private_Groups": "Kërko Grupe private", "seconds": "sekonda", diff --git a/packages/rocketchat-i18n/i18n/sr.i18n.json b/packages/rocketchat-i18n/i18n/sr.i18n.json index cb960fcff2b6dce30c49683f6aceb87acb851109..86b6a9570fcb433f27aae3558d0b21b705bfc065 100644 --- a/packages/rocketchat-i18n/i18n/sr.i18n.json +++ b/packages/rocketchat-i18n/i18n/sr.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Дати јединÑтвено име за прилагођени ОÐутх", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Дајте Ðпликација име. Ово ће бити виђена од Ñвојих кориÑника.", "Global": "глобалан", - "GoogleSiteVerification_id": "Верификација ИД Гоогле Ñајт", "GoogleTagManager_id": "Гоогле ИД менаџер ознака", - "Has_more": "има више", "Hash": "хаÑÑ…", "Header": "хеадер", "Hidden": "Сакривен", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "МСВалидате.01", "Meta_robots": "Роботи", "minutes": "минута", - "Mobile_push": "мобиле ПуÑÑ…", "More_channels": "Више канала", "More_direct_messages": "Више директне поруке", "More_groups": "Више приватне групе", @@ -923,8 +920,6 @@ "Script_Enabled": "Ñцрипт Омогућено", "Search": "Претрага", "Search_by_username": "Претрага по кориÑничком имену", - "Search_Channels": "Сеарцх ЦханнелÑ", - "Search_Direct_Messages": "Тражи директне поруке", "Search_Messages": "Сеарцх МеÑÑагеÑ", "Search_Private_Groups": "Тражи приватних група", "seconds": "Ñекунде", diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json index ba7f8e7cbded27c2a867edfebd10c40e5b097fd8..04158212186557ce94b2055bda251d15e735dff0 100644 --- a/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -6,6 +6,7 @@ "500": "internt serverfel", "__username__is_no_longer__role__defined_by__user_by_": "__username__ är inte längre __role__, genom __user_by__", "__username__was_set__role__by__user_by_": "__username__ sattes __role__ genom __user_by__", + "Accept": "Acceptera", "Access_not_authorized": "TillgÃ¥ng som inte godkänts", "Access_Token_URL": "TillgÃ¥ng Token URL", "Accessing_permissions": "Ã…tkomstbehörigheter", @@ -134,7 +135,7 @@ "Analytics_features_users_Description": "SpÃ¥r anpassade händelser i samband med Ã¥tgärder som rör användare (lösenordsÃ¥terställning gÃ¥nger, profilbild förändring, etc).", "and": "och", "And_more": "Och mer __length __", - "Animals_and_Nature": "Djur & natur", + "Animals_and_Nature": "Djur & Natur", "API": "API", "API_Analytics": "Analytics", "API_Embed": "Inbäddad", @@ -259,6 +260,7 @@ "Created_at_s_by_s": "Skapad vid <strong>%s</strong> av <strong>%s</strong>", "Current_Chats": "nuvarande Chatt", "Custom": "Beställnings", + "Custom_Emoji_Add": "Lägg till ny emoji", "Custom_Fields": "Anpassade fält", "Custom_oauth_helper": "När du ställer in din OAuth leverantör, mÃ¥ste du informera en Ã¥teruppringning webbadress. Användning <pre> %s </pre> .", "Custom_oauth_unique_name": "Anpassad oauth unikt namn", @@ -430,7 +432,7 @@ "FileUpload_S3_CDN": "CDN domän för nedladdningar", "FileUpload_S3_Region": "OmrÃ¥de", "FileUpload_Storage_Type": "lagring Typ", - "Flags": "flags", + "Flags": "Flaggor", "Follow_social_profiles": "Följ vÃ¥ra sociala mediakonton, forka oss pÃ¥ github och dela med dig av dina tankar om rocket.chatt pÃ¥ vÃ¥r trello.", "Food_and_Drink": "Mat & Dryck", "Footer": "footer", @@ -438,7 +440,7 @@ "Force_SSL": "force SSL", "Force_SSL_Description": "* OBS! * _Force SSL_ ska aldrig användas med reverse proxy. Om du har en omvänd proxy, bör du göra omdirigeringen DET. Det här alternativet finns för installationer som Heroku, som inte tillÃ¥ter konfiguration omdirigeringen pÃ¥ den omvända proxyservern.", "Forgot_password": "Glömt ditt lösenord?", - "Frequently_Used": "Vanliga Används", + "Frequently_Used": "Ofta använd", "From": "FrÃ¥n", "From_Email": "FrÃ¥n e-postadress", "From_email_warning": "<b>Varning:</b> Fältet <b>FrÃ¥n</b> är föremÃ¥l för e-postserverinställningar.", @@ -447,9 +449,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Ge ett unikt namn för den anpassade oauth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Ge applikationen ett namn. Detta kommer att ses av dina användare.", "Global": "Global", - "GoogleSiteVerification_id": "Google Site Verifiering Id", "GoogleTagManager_id": "Google Tagghanteraren Id", - "Has_more": "har mer", "Hash": "Hash", "Header": "Rubrik", "Hidden": "Dolda", @@ -708,7 +708,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robots", "minutes": "minuter", - "Mobile_push": "Mobila notifieringar", "More_channels": "Fler kanaler", "More_direct_messages": "Fler direktmeddelanden", "More_groups": "Fler privata grupper", @@ -925,8 +924,6 @@ "Script_Enabled": "script Enabled", "Search": "Sök", "Search_by_username": "Sök pÃ¥ användarnamn", - "Search_Channels": "Sök kanaler", - "Search_Direct_Messages": "Sök bland direktmeddelanden ", "Search_Messages": "Sök meddelanden", "Search_Private_Groups": "Sök i privata grupper", "seconds": "sekunder", @@ -1025,7 +1022,7 @@ "Success_message": "framgÃ¥ng meddelande", "Survey": "Enkät", "Survey_instructions": "Betygsätt varje frÃ¥ga efter hur nöjd du är, 1 betyder att du inte alls är nöjd och 5 betyder att du är helt nöjd.\n", - "Symbols": "symboler", + "Symbols": "Symboler", "Sync_success": "synk framgÃ¥ng", "Sync_Users": "Synkronisera Användare", "Tag": "Märka", @@ -1077,7 +1074,7 @@ "to_see_more_details_on_how_to_integrate": "att se mer information om hur man kan integrera.", "To_users": "Till användare", "Topic": "Ämne", - "Travel_and_Places": "Travel & Places", + "Travel_and_Places": "Fordon & Platser", "Trigger_removed": "trigger avlägsnades", "Trigger_Words": "trigger ord", "Triggers": "triggers", diff --git a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index 16a29858df0cbae540596cc7681142c91d353e7b..58ea59cd2b62ad74ed4278c0b26ac508d1a0c786 100644 --- a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "விரà¯à®ªà¯à®ª OAuth ஒர௠தனிபà¯à®ªà®Ÿà¯à®Ÿ பெயரை கொடà¯à®™à¯à®•ளà¯", "Give_the_application_a_name_This_will_be_seen_by_your_users": "விணà¯à®£à®ªà¯à®ª ஒர௠பெயர௠கொடà¯à®•à¯à®•. இத௠உஙà¯à®•ள௠பயனர௠பாரà¯à®•à¯à®• வேணà¯à®Ÿà¯à®®à¯.", "Global": "கà¯à®³à¯‡à®¾à®ªà®²à¯", - "GoogleSiteVerification_id": "Google தள சரிபாரà¯à®ªà¯à®ªà¯ அடையாளமà¯", "GoogleTagManager_id": "Google கà¯à®±à®¿ மேலாளர௠à®à®Ÿà®¿", - "Has_more": "இனà¯à®©à¯à®®à¯ உளà¯à®³à®¤à¯", "Hash": "பà¯à®²", "Header": "தலைபà¯à®ªà¯", "Hidden": "மறைகà¯à®•பà¯à®ªà®Ÿà¯à®Ÿ", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "ரோபோகà¯à®•ளà¯", "minutes": "நிமிடஙà¯à®•ளà¯", - "Mobile_push": "மொபைல௠மிகà¯à®¤à®¿", "More_channels": "மேலà¯à®®à¯ பொத௠கà¯à®´à¯à®•à¯à®•ளà¯", "More_direct_messages": "மேலà¯à®®à¯ நேரடி செயà¯à®¤à®¿à®•ளை", "More_groups": "மேலà¯à®®à¯ தனியார௠கà¯à®´à¯à®•à¯à®•ளà¯", @@ -923,8 +920,6 @@ "Script_Enabled": "ஸà¯à®•ிரிபà¯à®Ÿà¯", "Search": "தேடà¯", "Search_by_username": "பயனர௠பெயர௠மூலம௠தேடலà¯", - "Search_Channels": "தேடல௠சேனலà¯à®•ளà¯", - "Search_Direct_Messages": "நேரடி செயà¯à®¤à®¿à®•ள௠தேடலà¯", "Search_Messages": "தேடல௠செயà¯à®¤à®¿à®•ளà¯", "Search_Private_Groups": "தனியார௠கà¯à®´à¯à®•à¯à®•ள௠தேடலà¯", "seconds": "விநாடிகளà¯", diff --git a/packages/rocketchat-i18n/i18n/tr.i18n.json b/packages/rocketchat-i18n/i18n/tr.i18n.json index eafe8ef785909d8b4fe5db53274105646055a646..210bb755effaadec508c060822d28197a8b48b92 100644 --- a/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -489,10 +489,8 @@ "Give_a_unique_name_for_the_custom_oauth": "Özel OAuth için benzersiz bir ad verin", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Uygulamayı bir ad verin. Bu kullanıcılar tarafından görülecektir.", "Global": "global", - "GoogleSiteVerification_id": "Google Site DoÄŸrulama KimliÄŸi", "GoogleTagManager_id": "Google Etiket Yöneticisi KimliÄŸi", "Guest_Pool": "Misafir Havuzu", - "Has_more": "Daha fazla var", "Hash": "esrar", "Header": "üstbilgi", "Hidden": "Gizli", @@ -759,7 +757,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "Robotlar", "minutes": "dakika", - "Mobile_push": "Mobil itme", "More_channels": "Daha fazla", "More_direct_messages": "Daha direkt mesajlar", "More_groups": "Daha fazla özel grup", @@ -976,8 +973,6 @@ "Script_Enabled": "Senaryo Etkin", "Search": "Ara", "Search_by_username": "Kullanıcı adına göre ara", - "Search_Channels": "Kanal Arama", - "Search_Direct_Messages": "DoÄŸrudan Mesajlar Arama", "Search_Messages": "Mesajlarda ara", "Search_Private_Groups": "Özel Gruplar Arama", "seconds": "saniye", diff --git a/packages/rocketchat-i18n/i18n/ug.i18n.json b/packages/rocketchat-i18n/i18n/ug.i18n.json index e4081486dd3f578bab89380a30437294b8645a98..d3a1dc7c7cfadcc4cc4fd52166b46a0a840d5c8f 100644 --- a/packages/rocketchat-i18n/i18n/ug.i18n.json +++ b/packages/rocketchat-i18n/i18n/ug.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "بىردىنبىر ئىسىم بÛÙƒÙ‰ØªÙ‰Ú OAuth ئۆزى بÛكىتىش", "Give_the_application_a_name_This_will_be_seen_by_your_users": "ئەپكە بىر ئىسىم Ù‚ÙˆÙŠÛ‡Ú . بۇ ئىسىمنى Ø³Ù‰Ø²Ù†Ù‰Ú Ø¦Û•Ø²Ø§ÙŠÙ‰Úىز كۆرەلەيدۇ.", "Global": "ئومۇمىي ئەھۋال", - "GoogleSiteVerification_id": "ID Google تورى دەلىللەش", "GoogleTagManager_id": "IDئىز قوغلاش كودنى باشقۇرغۇچى Google", - "Has_more": "ØªÛØ®Ù‰Ù…Û‡ ÙƒÛ†Ù¾ بار", "Hash": "خاش قىممىتى", "Header": "باش", "Hidden": "يوشۇرۇلغان", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "تورى تەكشۈرۈپ ئىسپاتلاشMicrosoft", "Meta_robots": "ماشىنا ئادەم", "minutes": "مىنۇت", - "Mobile_push": "ÙƒÛ†Ú†Ù…Û• ØªÛØ±Ù…ىنال ئىتتىرىش", "More_channels": "ØªÛØ®Ù‰Ù…Û‡ ÙƒÛ†Ù¾ قانال", "More_direct_messages": "ØªÛØ®Ù‰Ù…Û‡ ÙƒÛ†Ù¾ سۆھبەتلىشىش", "More_groups": "ØªÛØ®Ù‰Ù…Û‡ ÙƒÛ†Ù¾ شەخسىي گۇرپپا", @@ -923,8 +920,6 @@ "Script_Enabled": "ئىسكرپت قوزغىتىلدى", "Search": "ئىزدەش", "Search_by_username": "ئەزا نامىغا ئاساسەن ئىزدەش", - "Search_Channels": "قانالنى ئىزدەش", - "Search_Direct_Messages": "بىۋاسىتە دىيالوگنى ئىزدەش", "Search_Messages": "ئۇچۇر ئىزدەش", "Search_Private_Groups": "شەخسىي گۇرۇپپىنى ئىزدەش", "seconds": "سÛكۇنت", diff --git a/packages/rocketchat-i18n/i18n/uk.i18n.json b/packages/rocketchat-i18n/i18n/uk.i18n.json index 12c213e00b66d86e049f1058240e63a3e3de387d..51d83c1911d66c86607057823edb5b6150aa15a7 100644 --- a/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "Дайте унікальне ім'Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾Ð³Ð¾ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача OAuth", "Give_the_application_a_name_This_will_be_seen_by_your_users": "Дайте додатком ім'Ñ. Це буде видно ваших кориÑтувачів.", "Global": "глобальної", - "GoogleSiteVerification_id": "Ідентифікатор Google Site Verification", "GoogleTagManager_id": "Google Id ДиÑпетчер тегів", - "Has_more": "має більш", "Hash": "мішанина", "Header": "заголовок", "Hidden": "прихований", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "роботи", "minutes": "хвилин", - "Mobile_push": "мобільний поштовх", "More_channels": "Більше каналів", "More_direct_messages": "Інші прÑмі повідомленнÑ", "More_groups": "Більш приватні групи", @@ -923,8 +920,6 @@ "Script_Enabled": "Ñценарій Включено", "Search": "Пошук", "Search_by_username": "Пошук по імені кориÑтувача", - "Search_Channels": "Пошук каналів", - "Search_Direct_Messages": "Пошук прÑмих повідомлень", "Search_Messages": "Пошук повідомлень", "Search_Private_Groups": "Пошук приватних груп", "seconds": "Ñекунд", diff --git a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json index a93c8660a4de4f2631b00b899182f7e3e9914a79..a9a19981f020ff7310241c657f1ab46fe5ce84dd 100644 --- a/packages/rocketchat-i18n/i18n/zh-HK.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-HK.i18n.json @@ -71,7 +71,6 @@ "From_Email": "从电å邮件", "General": "通用", "github_no_public_email": "在您的GitHubä¸Šå¸æˆ·ä¸ï¼Œæ‚¨æ²¡æœ‰è®¾ç½®ä»»ä½•电å邮件作为公共电å邮件地å€ã€‚", - "Has_more": "有更多", "Hide_room": "éšè—èŠå¤©å®¤", "History": "历å²", "hours": "å°æ—¶", diff --git a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json index 96f284bdbbbaa142162e81557041cf56d8080021..e9a057f6d62628bf87e65b770e67124e7ebb6452 100644 --- a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json @@ -446,9 +446,7 @@ "Give_a_unique_name_for_the_custom_oauth": "為自訂 OAuth è¨å®šä¸€å€‹ç¨ä¸€ç„¡äºŒçš„å稱", "Give_the_application_a_name_This_will_be_seen_by_your_users": "給應用程åºçš„å稱。這將被用戶看到。", "Global": "å…¨çƒ", - "GoogleSiteVerification_id": "è°·æŒç¶²ç«™é©—è‰æ¨™è˜", "GoogleTagManager_id": "è°·æŒä»£ç¢¼ç®¡ç†æ¨™è˜", - "Has_more": "有更多", "Hash": "哈希", "Header": "é ", "Hidden": "éš±è—", @@ -706,7 +704,6 @@ "Meta_msvalidate01": "MSValidate.01", "Meta_robots": "機器人", "minutes": "分é˜", - "Mobile_push": "移動推é€", "More_channels": "æ›´å¤šé »é“", "More_direct_messages": "更直接的訊æ¯", "More_groups": "æ›´å¤šç§æœ‰ç¾¤çµ„", @@ -923,8 +920,6 @@ "Script_Enabled": "腳本已啟用", "Search": "æœå°‹", "Search_by_username": "通éŽç”¨æˆ¶åæœå°‹", - "Search_Channels": "æœå°‹é »é“", - "Search_Direct_Messages": "æœç´¢ç§èŠè¨Šæ¯", "Search_Messages": "æœå°‹è¨Šæ¯", "Search_Private_Groups": "æœå°‹ç§äººç¾¤çµ„", "seconds": "ç§’", diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index f5f72e2b3a2e59c63e4e6557fd3e78d078036499..145471928d184643744794769a3c1c84d555767b 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -456,9 +456,7 @@ "Give_a_unique_name_for_the_custom_oauth": "请给自定义 OAuth 设置一个唯一的åç§°", "Give_the_application_a_name_This_will_be_seen_by_your_users": "给该应用设置一个å称。该å称将被您的用户看到。", "Global": "全局", - "GoogleSiteVerification_id": "è°·æŒç½‘站验è¯ID", "GoogleTagManager_id": "è°·æŒè·Ÿè¸ªä»£ç 管ç†å™¨ ID", - "Has_more": "有更多", "Hash": "哈希值", "Header": "头", "Hidden": "å·²éšè—", @@ -721,7 +719,6 @@ "Meta_msvalidate01": "微软网站验è¯", "Meta_robots": "机器人", "minutes": "分钟", - "Mobile_push": "移动端推é€", "More_channels": "更多频é“", "More_direct_messages": "更多对è¯", "More_groups": "æ›´å¤šç§æœ‰ç»„", @@ -952,8 +949,6 @@ "Script_Enabled": "脚本已å¯ç”¨", "Search": "æœç´¢", "Search_by_username": "æ ¹æ®ç”¨æˆ·åæœç´¢", - "Search_Channels": "æœç´¢é¢‘é“", - "Search_Direct_Messages": "æœç´¢ç›´æŽ¥å¯¹è¯", "Search_Messages": "æœç´¢æ¶ˆæ¯", "Search_Private_Groups": "æœç´¢ç§æœ‰ç»„", "seconds": "ç§’", diff --git a/packages/rocketchat-importer-csv/main.js b/packages/rocketchat-importer-csv/main.js index 578d360c247079056e4b977e8e439df92d57cb39..cea869b960b37ca7529ce8f73dc2d53777d9ecd7 100644 --- a/packages/rocketchat-importer-csv/main.js +++ b/packages/rocketchat-importer-csv/main.js @@ -6,5 +6,5 @@ Importer.addImporter('csv', Importer.CSV, { text: 'Importer_CSV_Information', href: 'https://rocket.chat/docs/administrator-guides/import/csv/' }], - fileTypeRegex: new RegExp('application\/.*?zip') + mimeType: 'application/zip' }); diff --git a/packages/rocketchat-importer-csv/server.js b/packages/rocketchat-importer-csv/server.js index 9358d9f0df6f652252e21420ff4e845d9de0bea5..98eec8ea44e1cd55710cd7494c0a600392d5a6d8 100644 --- a/packages/rocketchat-importer-csv/server.js +++ b/packages/rocketchat-importer-csv/server.js @@ -1,8 +1,8 @@ /* globals Importer */ Importer.CSV = class ImporterCSV extends Importer.Base { - constructor(name, descriptionI18N, fileTypeRegex) { - super(name, descriptionI18N, fileTypeRegex); + constructor(name, descriptionI18N, mimeType) { + super(name, descriptionI18N, mimeType); this.logger.debug('Constructed a new CSV Importer.'); this.csvParser = Npm.require('csv-parse/lib/sync'); @@ -184,8 +184,7 @@ Importer.CSV = class ImporterCSV extends Importer.Base { } else { const userId = Accounts.createUser({ email: u.email, password: Date.now() + u.name + u.email.toUpperCase() }); Meteor.runAsUser(userId, () => { - Meteor.call('setUsername', u.username); - Meteor.call('joinDefaultChannels', true); + Meteor.call('setUsername', u.username, {joinDefaultChannelsSilenced: true}); RocketChat.models.Users.setName(userId, u.name); RocketChat.models.Users.update({ _id: userId }, { $addToSet: { importIds: u.id } }); u.rocketId = userId; diff --git a/packages/rocketchat-importer-hipchat-enterprise/.npm/package/.gitignore b/packages/rocketchat-importer-hipchat-enterprise/.npm/package/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..3c3629e647f5ddf82548912e337bea9826b434af --- /dev/null +++ b/packages/rocketchat-importer-hipchat-enterprise/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/rocketchat-importer-hipchat-enterprise/.npm/package/README b/packages/rocketchat-importer-hipchat-enterprise/.npm/package/README new file mode 100644 index 0000000000000000000000000000000000000000..3d492553a438e46facd411cd3e206a648395a38c --- /dev/null +++ b/packages/rocketchat-importer-hipchat-enterprise/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/rocketchat-importer-hipchat-enterprise/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-importer-hipchat-enterprise/.npm/package/npm-shrinkwrap.json new file mode 100644 index 0000000000000000000000000000000000000000..c064b4aa3200b8f6023bf91ad49ca8c93936315e --- /dev/null +++ b/packages/rocketchat-importer-hipchat-enterprise/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,81 @@ +{ + "dependencies": { + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "from": "bl@>=1.0.0 <2.0.0", + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "from": "readable-stream@>=2.0.5 <2.1.0" + } + } + }, + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "from": "buffer-shims@>=1.0.0 <2.0.0" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "from": "core-util-is@>=1.0.0 <1.1.0" + }, + "end-of-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", + "from": "end-of-stream@>=1.0.0 <2.0.0" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "from": "inherits@>=2.0.1 <2.1.0" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "from": "isarray@>=1.0.0 <1.1.0" + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "from": "once@>=1.3.0 <1.4.0" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "from": "process-nextick-args@>=1.0.6 <1.1.0" + }, + "readable-stream": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", + "from": "readable-stream@>=2.0.0 <3.0.0" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "from": "string_decoder@>=0.10.0 <0.11.0" + }, + "tar-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.2.tgz", + "from": "tar-stream@1.5.2" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "from": "util-deprecate@>=1.0.1 <1.1.0" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "from": "wrappy@>=1.0.0 <2.0.0" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "from": "xtend@>=4.0.0 <5.0.0" + } + } +} diff --git a/packages/rocketchat-importer-hipchat-enterprise/main.js b/packages/rocketchat-importer-hipchat-enterprise/main.js new file mode 100644 index 0000000000000000000000000000000000000000..69dfcb1f778a10e18e4a9e59afd70af8fcc5c76a --- /dev/null +++ b/packages/rocketchat-importer-hipchat-enterprise/main.js @@ -0,0 +1,15 @@ +/* globals Importer */ + +Importer.addImporter('hipchatenterprise', Importer.HipChatEnterprise, { + name: 'HipChat Enterprise', + warnings: [ + { + text: 'Importer_HipChatEnterprise_Information', + href: 'https://rocket.chat/docs/administrator-guides/import/hipchat/enterprise/' + }, { + text: 'Importer_HipChatEnterprise_BetaWarning', + href: 'https://github.com/RocketChat/Rocket.Chat/issues/new' + } + ], + mimeType: 'application/gzip' +}); diff --git a/packages/rocketchat-importer-hipchat-enterprise/package.js b/packages/rocketchat-importer-hipchat-enterprise/package.js new file mode 100644 index 0000000000000000000000000000000000000000..76844dae9fa622e23c6a5f9dd6419967b056f7f3 --- /dev/null +++ b/packages/rocketchat-importer-hipchat-enterprise/package.js @@ -0,0 +1,22 @@ +Package.describe({ + name: 'rocketchat:importer-hipchat-enterprise', + version: '1.0.0', + summary: 'Importer for Hipchat Importer Files', + git: '' +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:lib', + 'rocketchat:importer' + ]); + + api.use('rocketchat:logger', 'server'); + api.addFiles('server.js', 'server'); + api.addFiles('main.js', ['client', 'server']); +}); + +Npm.depends({ + 'tar-stream': '1.5.2' +}); diff --git a/packages/rocketchat-importer-hipchat-enterprise/server.js b/packages/rocketchat-importer-hipchat-enterprise/server.js new file mode 100644 index 0000000000000000000000000000000000000000..42e007012eb59cff36204189a03ffa18b66a231e --- /dev/null +++ b/packages/rocketchat-importer-hipchat-enterprise/server.js @@ -0,0 +1,461 @@ +/* globals Importer */ + +Importer.HipChatEnterprise = class ImporterHipChatEnterprise extends Importer.Base { + constructor(name, descriptionI18N, mimeType) { + super(name, descriptionI18N, mimeType); + this.logger.debug('Constructed a new HipChat Enterprise Importer.'); + + this.Readable = require('stream').Readable; + this.zlib = require('zlib'); + this.tarStream = Npm.require('tar-stream'); + this.extract = this.tarStream.extract(); + this.path = require('path'); + this.messages = new Map(); + this.directMessages = new Map(); + } + + prepare(dataURI, sentContentType, fileName) { + super.prepare(dataURI, sentContentType, fileName); + + const tempUsers = []; + const tempRooms = []; + const tempMessages = new Map(); + const tempDirectMessages = new Map(); + const promise = new Promise((resolve, reject) => { + this.extract.on('entry', Meteor.bindEnvironment((header, stream, next) => { + if (header.name.indexOf('.json') !== -1) { + const info = this.path.parse(header.name); + + stream.on('data', Meteor.bindEnvironment((chunk) => { + this.logger.debug(`Processing the file: ${header.name}`); + const file = JSON.parse(chunk); + + if (info.base === 'users.json') { + super.updateProgress(Importer.ProgressStep.PREPARING_USERS); + for (let u of file) { + tempUsers.push({ + id: u.User.id, + email: u.User.email, + name: u.User.name, + username: u.User.mention_name, + avatar: u.User.avatar.replace(/\n/g, ''), + timezone: u.User.timezone, + isDeleted: u.User.is_deleted + }); + } + } else if (info.base === 'rooms.json') { + super.updateProgress(Importer.ProgressStep.PREPARING_CHANNELS); + for (let r of file) { + tempRooms.push({ + id: r.Room.id, + creator: r.Room.owner, + created: new Date(r.Room.created), + name: r.Room.name.replace(/ /g, '_').toLowerCase(), + isPrivate: r.Room.privacy === 'private', + isArchived: r.Room.is_archived, + topic: r.Room.topic + }); + } + } else if (info.base === 'history.json') { + const dirSplit = info.dir.split('/'); //['.', 'users', '1'] + const roomIdentifier = `${dirSplit[1]}/${dirSplit[2]}`; + + if (dirSplit[1] === 'users') { + const msgs = []; + for (let m of file) { + if (m.PrivateUserMessage) { + msgs.push({ + type: 'user', + id: `hipchatenterprise-${m.PrivateUserMessage.id}`, + senderId: m.PrivateUserMessage.sender.id, + receiverId: m.PrivateUserMessage.receiver.id, + text: m.PrivateUserMessage.message.indexOf('/me ') === -1 ? m.PrivateUserMessage.message : `${m.PrivateUserMessage.message.replace(/\/me /, '_')}_`, + ts: new Date(m.PrivateUserMessage.timestamp.split(' ')[0]) + }); + } + } + tempDirectMessages.set(roomIdentifier, msgs); + } else if (dirSplit[1] === 'rooms') { + const roomMsgs = []; + + for (let m of file) { + if (m.UserMessage) { + roomMsgs.push({ + type: 'user', + id: `hipchatenterprise-${dirSplit[2]}-${m.UserMessage.id}`, + userId: m.UserMessage.sender.id, + text: m.UserMessage.message.indexOf('/me ') === -1 ? m.UserMessage.message : `${m.UserMessage.message.replace(/\/me /, '_')}_`, + ts: new Date(m.UserMessage.timestamp.split(' ')[0]) + }); + } else if (m.TopicRoomMessage) { + roomMsgs.push({ + type: 'topic', + id: `hipchatenterprise-${dirSplit[2]}-${m.TopicRoomMessage.id}`, + userId: m.TopicRoomMessage.sender.id, + ts: new Date(m.TopicRoomMessage.timestamp.split(' ')[0]), + text: m.TopicRoomMessage.message + }); + } else { + this.logger.warn('HipChat Enterprise importer isn\'t configured to handle this message:', m); + } + } + tempMessages.set(roomIdentifier, roomMsgs); + } else { + this.logger.warn(`HipChat Enterprise importer isn't configured to handle "${dirSplit[1]}" files.`); + } + } else { + //What are these files!? + this.logger.warn(`HipChat Enterprise importer doesn't know what to do with the file "${header.name}" :o`, info); + } + })); + + stream.on('end', () => next()); + stream.on('error', () => next()); + } else { + next(); + } + })); + + this.extract.on('error', (err) => { + this.logger.warn('extract error:', err); + reject(); + }); + + this.extract.on('finish', Meteor.bindEnvironment(() => { + // Insert the users record, eventually this might have to be split into several ones as well + // if someone tries to import a several thousands users instance + const usersId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'users', 'users': tempUsers }); + this.users = this.collection.findOne(usersId); + super.updateRecord({ 'count.users': tempUsers.length }); + super.addCountToTotal(tempUsers.length); + + // Insert the channels records. + const channelsId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'channels', 'channels': tempRooms }); + this.channels = this.collection.findOne(channelsId); + super.updateRecord({ 'count.channels': tempRooms.length }); + super.addCountToTotal(tempRooms.length); + + // Save the messages records to the import record for `startImport` usage + super.updateProgress(Importer.ProgressStep.PREPARING_MESSAGES); + let messagesCount = 0; + for (let [channel, msgs] of tempMessages.entries()) { + if (!this.messages.get(channel)) { + this.messages.set(channel, new Map()); + } + + messagesCount += msgs.length; + super.updateRecord({ 'messagesstatus': channel }); + + if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) { + Importer.Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => { + const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${channel}/${i}`, 'messages': splitMsg }); + this.messages.get(channel).set(`${channel}.${i}`, this.collection.findOne(messagesId)); + }); + } else { + const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${channel}`, 'messages': msgs }); + this.messages.get(channel).set(channel, this.collection.findOne(messagesId)); + } + } + + for (let [directMsgUser, msgs] of tempDirectMessages.entries()) { + this.logger.debug(`Preparing the direct messages for: ${directMsgUser}`); + if (!this.directMessages.get(directMsgUser)) { + this.directMessages.set(directMsgUser, new Map()); + } + + messagesCount += msgs.length; + super.updateRecord({ 'messagesstatus': directMsgUser }); + + if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) { + Importer.Base.getBSONSafeArraysFromAnArray(msgs).forEach((splitMsg, i) => { + const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'directMessages', 'name': `${directMsgUser}/${i}`, 'messages': splitMsg }); + this.directMessages.get(directMsgUser).set(`${directMsgUser}.${i}`, this.collection.findOne(messagesId)); + }); + } else { + const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'directMessages', 'name': `${directMsgUser}`, 'messages': msgs }); + this.directMessages.get(directMsgUser).set(directMsgUser, this.collection.findOne(messagesId)); + } + } + + super.updateRecord({ 'count.messages': messagesCount, 'messagesstatus': null }); + super.addCountToTotal(messagesCount); + + //Ensure we have some users, channels, and messages + if (tempUsers.length === 0 || tempRooms.length === 0 || messagesCount === 0) { + this.logger.warn(`The loaded users count ${tempUsers.length}, the loaded rooms ${tempRooms.length}, and the loaded messages ${messagesCount}`); + super.updateProgress(Importer.ProgressStep.ERROR); + reject(); + return; + } + + const selectionUsers = tempUsers.map((u) => new Importer.SelectionUser(u.id, u.username, u.email, u.isDeleted, false, true)); + const selectionChannels = tempRooms.map((r) => new Importer.SelectionChannel(r.id, r.name, r.isArchived, true, r.isPrivate)); + + super.updateProgress(Importer.ProgressStep.USER_SELECTION); + + resolve(new Importer.Selection(this.name, selectionUsers, selectionChannels)); + })); + + //Wish I could make this cleaner :( + const split = dataURI.split(','); + const s = new this.Readable; + s.push(new Buffer(split[split.length - 1], 'base64')); + s.push(null); + s.pipe(this.zlib.createGunzip()).pipe(this.extract); + }); + + return promise; + } + + startImport(importSelection) { + super.startImport(importSelection); + const started = Date.now(); + + //Ensure we're only going to import the users that the user has selected + for (let user of importSelection.users) { + for (let u of this.users.users) { + if (u.id === user.user_id) { + u.do_import = user.do_import; + } + } + } + this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }}); + + //Ensure we're only importing the channels the user has selected. + for (let channel of importSelection.channels) { + for (let c of this.channels.channels) { + if (c.id === channel.channel_id) { + c.do_import = channel.do_import; + } + } + } + this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }}); + + const startedByUserId = Meteor.userId(); + Meteor.defer(() => { + super.updateProgress(Importer.ProgressStep.IMPORTING_USERS); + //Import the users + for (let u of this.users.users) { + this.logger.debug(`Starting the user import: ${u.username} and are we importing them? ${u.do_import}`); + if (!u.do_import) { + continue; + } + + Meteor.runAsUser(startedByUserId, () => { + let existantUser = RocketChat.models.Users.findOneByEmailAddress(u.email); + + //If we couldn't find one by their email address, try to find an existing user by their username + if (!existantUser) { + existantUser = RocketChat.models.Users.findOneByUsername(u.username); + } + + if (existantUser) { + //since we have an existing user, let's try a few things + u.rocketId = existantUser._id; + RocketChat.models.Users.update({ _id: u.rocketId }, { $addToSet: { importIds: u.id } }); + } else { + const userId = Accounts.createUser({ email: u.email, password: Date.now() + u.name + u.email.toUpperCase() }); + Meteor.runAsUser(userId, () => { + Meteor.call('setUsername', u.username, {joinDefaultChannelsSilenced: true}); + //TODO: Use moment timezone to calc the time offset - Meteor.call 'userSetUtcOffset', user.tz_offset / 3600 + RocketChat.models.Users.setName(userId, u.name); + //TODO: Think about using a custom field for the users "title" field + + if (u.avatar) { + Meteor.call('setAvatarFromService', `data:image/png;base64,${u.avatar}`); + } + + //Deleted users are 'inactive' users in Rocket.Chat + if (u.deleted) { + Meteor.call('setUserActiveStatus', userId, false); + } + + RocketChat.models.Users.update({ _id: userId }, { $addToSet: { importIds: u.id } }); + u.rocketId = userId; + }); + } + + super.addCountCompleted(1); + }); + } + this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }}); + + //Import the channels + super.updateProgress(Importer.ProgressStep.IMPORTING_CHANNELS); + for (let c of this.channels.channels) { + if (!c.do_import) { + continue; + } + + Meteor.runAsUser(startedByUserId, () => { + let existantRoom = RocketChat.models.Rooms.findOneByName(c.name); + //If the room exists or the name of it is 'general', then we don't need to create it again + if (existantRoom || c.name.toUpperCase() === 'GENERAL') { + c.rocketId = c.name.toUpperCase() === 'GENERAL' ? 'GENERAL' : existantRoom._id; + RocketChat.models.Rooms.update({ _id: c.rocketId }, { $addToSet: { importIds: c.id } }); + } else { + //Find the rocketchatId of the user who created this channel + let creatorId = startedByUserId; + for (let u of this.users.users) { + if (u.id === c.creator && u.do_import) { + creatorId = u.rocketId; + } + } + + //Create the channel + Meteor.runAsUser(creatorId, () => { + const roomInfo = Meteor.call(c.isPrivate ? 'createPrivateGroup' : 'createChannel', c.name, []); + c.rocketId = roomInfo.rid; + }); + + RocketChat.models.Rooms.update({ _id: c.rocketId }, { $set: { ts: c.created, topic: c.topic }, $addToSet: { importIds: c.id } }); + } + + super.addCountCompleted(1); + }); + } + this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }}); + + //Import the Messages + super.updateProgress(Importer.ProgressStep.IMPORTING_MESSAGES); + for (let [ch, messagesMap] of this.messages.entries()) { + const hipChannel = this.getChannelFromRoomIdentifier(ch); + if (!hipChannel.do_import) { + continue; + } + + const room = RocketChat.models.Rooms.findOneById(hipChannel.rocketId, { fields: { usernames: 1, t: 1, name: 1 } }); + Meteor.runAsUser(startedByUserId, () => { + for (let [msgGroupData, msgs] of messagesMap.entries()) { + super.updateRecord({ 'messagesstatus': `${ch}/${msgGroupData}.${msgs.messages.length}` }); + for (let msg of msgs.messages) { + if (isNaN(msg.ts)) { + this.logger.warn(`Timestamp on a message in ${ch}/${msgGroupData} is invalid`); + super.addCountCompleted(1); + continue; + } + + const creator = this.getRocketUserFromUserId(msg.userId); + if (creator) { + switch (msg.type) { + case 'user': + RocketChat.sendMessage(creator, { + _id: msg.id, + ts: msg.ts, + msg: msg.text, + rid: room._id, + u: { + _id: creator._id, + username: creator.username + } + }, room, true); + break; + case 'topic': + RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_topic', room._id, msg.text, creator, { _id: msg.id, ts: msg.ts }); + break; + } + } + + super.addCountCompleted(1); + } + } + }); + } + + //Import the Direct Messages + for (let [directMsgRoom, directMessagesMap] of this.directMessages.entries()) { + const hipUser = this.getUserFromDirectMessageIdentifier(directMsgRoom); + if (!hipUser.do_import) { + continue; + } + + //Verify this direct message user's room is valid (confusing but idk how else to explain it) + if (!this.getRocketUserFromUserId(hipUser.id)) { + continue; + } + + for (let [msgGroupData, msgs] of directMessagesMap.entries()) { + super.updateRecord({ 'messagesstatus': `${directMsgRoom}/${msgGroupData}.${msgs.messages.length}` }); + for (let msg of msgs.messages) { + if (isNaN(msg.ts)) { + this.logger.warn(`Timestamp on a message in ${directMsgRoom}/${msgGroupData} is invalid`); + super.addCountCompleted(1); + continue; + } + + //make sure the message sender is a valid user inside rocket.chat + const sender = this.getRocketUserFromUserId(msg.senderId); + if (!sender) { + continue; + } + + //make sure the receiver of the message is a valid rocket.chat user + const receiver = this.getRocketUserFromUserId(msg.receiverId); + if (!receiver) { + continue; + } + + let room = RocketChat.models.Rooms.findOneById([receiver._id, sender._id].sort().join('')); + if (!room) { + Meteor.runAsUser(sender._id, () => { + const roomInfo = Meteor.call('createDirectMessage', receiver.username); + room = RocketChat.models.Rooms.findOneById(roomInfo.rid); + }); + } + + Meteor.runAsUser(sender._id, () => { + RocketChat.sendMessage(sender, { + _id: msg.id, + ts: msg.ts, + msg: msg.text, + rid: room._id, + u: { + _id: sender._id, + username: sender.username + } + }, room, true); + }); + } + } + } + + super.updateProgress(Importer.ProgressStep.FINISHING); + super.updateProgress(Importer.ProgressStep.DONE); + const timeTook = Date.now() - started; + this.logger.log(`HipChat Enterprise Import took ${timeTook} milliseconds.`); + }); + + return super.getProgress(); + } + + getSelection() { + const selectionUsers = this.users.users.map((u) => new Importer.SelectionUser(u.id, u.username, u.email, false, false, true)); + const selectionChannels = this.channels.channels.map((c) => new Importer.SelectionChannel(c.id, c.name, false, true, c.isPrivate)); + + return new Importer.Selection(this.name, selectionUsers, selectionChannels); + } + + getChannelFromRoomIdentifier(roomIdentifier) { + for (let ch of this.channels.channels) { + if (`rooms/${ch.id}` === roomIdentifier) { + return ch; + } + } + } + + getUserFromDirectMessageIdentifier(directIdentifier) { + for (let u of this.users.users) { + if (`users/${u.id}` === directIdentifier) { + return u; + } + } + } + + getRocketUserFromUserId(userId) { + for (let u of this.users.users) { + if (u.id === userId) { + return RocketChat.models.Users.findOneById(u.rocketId, { fields: { username: 1 }}); + } + } + } +}; diff --git a/packages/rocketchat-importer-hipchat/main.coffee b/packages/rocketchat-importer-hipchat/main.coffee index 441aa85e0b9f1f9d38a213afce3d6465ffb90cd0..1198383d75c6a801856f1fd8f6b44f93e3731d0e 100644 --- a/packages/rocketchat-importer-hipchat/main.coffee +++ b/packages/rocketchat-importer-hipchat/main.coffee @@ -1,3 +1,3 @@ Importer.addImporter 'hipchat', Importer.HipChat, name: 'HipChat' - fileTypeRegex: new RegExp 'application\/.*?zip' + mimeType: 'application/zip' diff --git a/packages/rocketchat-importer-hipchat/server.coffee b/packages/rocketchat-importer-hipchat/server.coffee index 80f79a5b41c333e6e7e256a68430f28976ec7fb7..c84a6413b8f1fae11bcd5e4134d335ec16b70932 100644 --- a/packages/rocketchat-importer-hipchat/server.coffee +++ b/packages/rocketchat-importer-hipchat/server.coffee @@ -5,8 +5,8 @@ Importer.HipChat = class Importer.HipChat extends Importer.Base @RoomPrefix = 'hipchat_export/rooms/' @UsersPrefix = 'hipchat_export/users/' - constructor: (name, descriptionI18N, fileTypeRegex) -> - super(name, descriptionI18N, fileTypeRegex) + constructor: (name, descriptionI18N, mimeType) -> + super(name, descriptionI18N, mimeType) @logger.debug('Constructed a new Slack Importer.') @userTags = [] @@ -135,8 +135,7 @@ Importer.HipChat = class Importer.HipChat extends Importer.Base hipchat: "@#{user.mention_name}" rocket: "@#{user.mention_name}" Meteor.runAsUser userId, () => - Meteor.call 'setUsername', user.mention_name - Meteor.call 'joinDefaultChannels', true + Meteor.call 'setUsername', user.mention_name, {joinDefaultChannelsSilenced: true} Meteor.call 'setAvatarFromService', user.photo_url, undefined, 'url' Meteor.call 'userSetUtcOffset', parseInt moment().tz(user.timezone).format('Z').toString().split(':')[0] diff --git a/packages/rocketchat-importer-slack/main.coffee b/packages/rocketchat-importer-slack/main.coffee index 62e470ded88e2e99ab70f156df1b8a26aa7a2836..da0518e538746aec7087d8489e95646a4ad79a8a 100644 --- a/packages/rocketchat-importer-slack/main.coffee +++ b/packages/rocketchat-importer-slack/main.coffee @@ -1,3 +1,3 @@ Importer.addImporter 'slack', Importer.Slack, name: 'Slack' - fileTypeRegex: new RegExp 'application\/.*?zip' + mimeType: 'application/zip' diff --git a/packages/rocketchat-importer-slack/server.coffee b/packages/rocketchat-importer-slack/server.coffee index 85813b34d6e234b0551e4af2209e4d2b075bc214..ccd1975175cc941fcdecc8a5272b13c64c2f6857 100644 --- a/packages/rocketchat-importer-slack/server.coffee +++ b/packages/rocketchat-importer-slack/server.coffee @@ -1,6 +1,6 @@ Importer.Slack = class Importer.Slack extends Importer.Base - constructor: (name, descriptionI18N, fileTypeRegex) -> - super(name, descriptionI18N, fileTypeRegex) + constructor: (name, descriptionI18N, mimeType) -> + super(name, descriptionI18N, mimeType) @userTags = [] @bots = {} @logger.debug('Constructed a new Slack Importer.') @@ -126,10 +126,9 @@ Importer.Slack = class Importer.Slack extends Importer.Base if user.profile.email userId = Accounts.createUser { email: user.profile.email, password: Date.now() + user.name + user.profile.email.toUpperCase() } else - userId = Accounts.createUser { username: user.name, password: Date.now() + user.name } + userId = Accounts.createUser { username: user.name, password: Date.now() + user.name, joinDefaultChannelsSilenced: true } Meteor.runAsUser userId, () => - Meteor.call 'setUsername', user.name - Meteor.call 'joinDefaultChannels', true + Meteor.call 'setUsername', user.name, {joinDefaultChannelsSilenced: true} url = null if user.profile.image_original url = user.profile.image_original diff --git a/packages/rocketchat-importer/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-importer/.npm/package/npm-shrinkwrap.json index 4fb75e8129e6a98c1a9a020fbdf16a0850365369..238dae99d6986a1bec8c53c14bf877b752638f43 100644 --- a/packages/rocketchat-importer/.npm/package/npm-shrinkwrap.json +++ b/packages/rocketchat-importer/.npm/package/npm-shrinkwrap.json @@ -9,6 +9,11 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/bson/-/bson-0.5.5.tgz", "from": "bson@0.5.5" + }, + "file-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.0.0.tgz", + "from": "file-type@4.0.0" } } } diff --git a/packages/rocketchat-importer/client/admin/adminImport.html b/packages/rocketchat-importer/client/admin/adminImport.html index 03a7a274faa02cfe55978b7b1d6d024d867fd296..3e22cbcbc29dbafba76c20ac61bf869d207f0d1d 100644 --- a/packages/rocketchat-importer/client/admin/adminImport.html +++ b/packages/rocketchat-importer/client/admin/adminImport.html @@ -1,6 +1,6 @@ <template name="adminImport"> <section class="page-container page-home page-static page-settings"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">Import</span> diff --git a/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee b/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee index b246e4ffaf516b2faaab144aff5dec813d438931..823418efed047b62b312260eda1f49f4610f2274 100644 --- a/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee +++ b/packages/rocketchat-importer/client/admin/adminImportPrepare.coffee @@ -23,6 +23,8 @@ Template.adminImportPrepare.helpers Template.adminImportPrepare.events 'change .import-file-input': (event, template) -> importer = @ + return if not importer.key + e = event.originalEvent or event files = e.target.files if not files or files.length is 0 @@ -30,18 +32,14 @@ Template.adminImportPrepare.events for blob in files template.preparing.set true - if not importer.fileTypeRegex.test blob.type - toastr.error t('Invalid_Import_File_Type') - template.preparing.set false - return reader = new FileReader() reader.readAsDataURL(blob) reader.onloadend = -> Meteor.call 'prepareImport', importer.key, reader.result, blob.type, blob.name, (error, data) -> if error - console.warn 'Errored out preparing the import:', error - handleError(error) + toastr.error t('Invalid_Import_File_Type') + template.preparing.set false return if !data @@ -51,7 +49,7 @@ Template.adminImportPrepare.events return if data.step - console.warn 'Invalid file.' + console.warn 'Invalid file, contains `data.step`.', data toastr.error t('Invalid_Export_File', importer.key) template.preparing.set false return @@ -131,21 +129,24 @@ Template.adminImportPrepare.onCreated -> console.warn 'Invalid progress information.', progress # Load the initial progress to determine what we need to do - Meteor.call 'getImportProgress', FlowRouter.getParam('importer'), (error, progress) -> - if error - console.warn 'Error while getting the import progress:', error - handleError error - return - - # if the progress isnt defined, that means there currently isn't an instance - # of the importer, so we need to create it - if progress is undefined - Meteor.call 'setupImporter', FlowRouter.getParam('importer'), (err, data) -> - if err - handleError(err) - instance.preparing.set false - loadSelection(data) - else - # Otherwise, we might need to do something based upon the current step - # of the import - loadSelection(progress) + if FlowRouter.getParam('importer') + Meteor.call 'getImportProgress', FlowRouter.getParam('importer'), (error, progress) -> + if error + console.warn 'Error while getting the import progress:', error + handleError error + return + + # if the progress isnt defined, that means there currently isn't an instance + # of the importer, so we need to create it + if progress is undefined + Meteor.call 'setupImporter', FlowRouter.getParam('importer'), (err, data) -> + if err + handleError(err) + instance.preparing.set false + loadSelection(data) + else + # Otherwise, we might need to do something based upon the current step + # of the import + loadSelection(progress) + else + FlowRouter.go '/admin/import' diff --git a/packages/rocketchat-importer/client/admin/adminImportPrepare.html b/packages/rocketchat-importer/client/admin/adminImportPrepare.html index 5c0504c70f467a3abac04e8e533854ca22a26fd0..cac461eb89e7b7c7002d66a9a5f598eb0e06f62c 100644 --- a/packages/rocketchat-importer/client/admin/adminImportPrepare.html +++ b/packages/rocketchat-importer/client/admin/adminImportPrepare.html @@ -1,7 +1,7 @@ <template name="adminImportPrepare"> <section class="page-container page-home page-static page-settings"> {{#with importer}} - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{name}}</span> @@ -70,7 +70,7 @@ <div class="section"> <h1>{{_ "Importer_Source_File"}}</h1> <div class="section-content"> - <input type="file" class="import-file-input" accept="{{ fileType }}"> + <input type="file" class="import-file-input"> </div> </div> {{/if}} diff --git a/packages/rocketchat-importer/client/admin/adminImportProgress.html b/packages/rocketchat-importer/client/admin/adminImportProgress.html index 1ff4725ffc5e2dedd64ea18c0c19a50c8e1b2730..6f8d95c00e66192f4c28bbac9bc7ce8017827c30 100644 --- a/packages/rocketchat-importer/client/admin/adminImportProgress.html +++ b/packages/rocketchat-importer/client/admin/adminImportProgress.html @@ -1,28 +1,5 @@ <template name="adminImportProgress"> - <svg class="rocket-loader" version="1.1" x="0px" y="0px" width="200" height="275" viewBox="0 0 200 275" enable-background="new 0 0 200 275" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - <g> - <g> - <path class="outer" fill="#CC3333" d="m188.146,99.881c0,-8.852 -2.647,-17.341 -7.873,-25.23199c-4.69098,-7.083 -11.263,-13.354 -19.53299, - -18.637c-15.96701,-10.199 -36.953,-15.817 -59.089,-15.817c-7.395,0 -14.68201,0.625 -21.75102,1.863c-4.38699,-4.105 -9.521,-7.798 -14.953, - -10.72c-29.024,-14.066 -53.09401,-0.331 -53.09401,-0.331s22.379,18.383 18.739,34.499c-10.012,9.931 -15.438,21.90601 -15.438,34.375c0, - 0.04 0.002,0.08 0.003,0.119c-0.001,0.04 -0.003,0.08 -0.003,0.119c0,12.46899 5.426,24.44299 15.438,34.37499c3.64,16.11401 -18.739, - 34.498 -18.739,34.498s24.069,13.735 53.09401,-0.33c5.433,-2.922 10.566,-6.61501 14.953,-10.72101c7.06902,1.239 14.356,1.86302 21.75102, - 1.86302c22.13599,0 43.12199,-5.617 59.089,-15.81599c8.27098,-5.28201 14.84201,-11.554 19.53299,-18.63701c5.226,-7.89201 7.873,-16.38 7.873, - -25.23201c0,-0.04 -0.002,-0.08 -0.002,-0.119s0.002,-0.07999 0.002,-0.119l0,-0.00002l0,0.00001l0,0.00001z" /> - <path class="inner" fill="#FFFFFF" d="m101.686,51.726c41.84301,0 75.76501,21.667 75.76501,48.395c0,26.728 -33.922,48.396 -75.76501,48.396c-9.31699, - 0 -18.239,-1.076 -26.48299,-3.04199c-8.37801,10.07999 -26.809,24.09201 -44.713,19.562c5.823,-6.25499 14.452,-16.825 12.604,-34.23299c-10.731, - -8.35101 -17.173,-19.03699 -17.173,-30.68301c0,-26.728 33.921,-48.395 75.76499,-48.395" /> - <g> - <circle fill="#CC3333" cx="136.68" cy="100.121" r="10.063" /> - <circle fill="#CC3333" cx="101.685" cy="100.121" r="10.064" /> - <circle fill="#CC3333" cx="66.691" cy="100.121" r="10.064" /> - </g> - <path class="inner" fill="#CCCCCC" d="m101.686,142.149c-9.31699,0 -18.239,-0.933 -26.48299,-2.636c-7.397,7.713 -22.634,18.08099 -38.425, - 17.69901c-2.079,3.153 -4.34,5.73199 -6.288,7.82399c17.904,4.53 36.335,-9.481 44.713,-19.562c8.244,1.966 17.166,3.04201 26.48299, - 3.04201c41.507,0 75.214,-21.32401 75.75201,-47.755c-0.53899,22.909 -34.24599,41.38801 -75.75201,41.38801l0,-0.00002z" /> - </g> - <text xml:space="preserve" text-anchor="middle" font-family="serif" font-size="24" y="245" x="100" stroke-width="0" stroke="#000000" fill="#044974">{{step}}</text> - <text fill="#044974" stroke="#000000" stroke-width="0" x="100.01563" y="270" font-size="24" font-family="serif" text-anchor="middle" xml:space="preserve">{{completed}} / {{total}}</text> - </g> - </svg> + {{> loading}} + <p>{{step}}</p> + <p>{{completed}} / {{total}}</p> </template> diff --git a/packages/rocketchat-importer/lib/importTool.coffee b/packages/rocketchat-importer/lib/importTool.coffee index 861b7ce8af55dc6a2e347b6faa7f97cc39df5144..99514e045e9a059092fd7e887da717a296eb65e8 100644 --- a/packages/rocketchat-importer/lib/importTool.coffee +++ b/packages/rocketchat-importer/lib/importTool.coffee @@ -5,5 +5,5 @@ Importer.addImporter = (name, importer, options) -> Importer.Importers[name] = name: options.name importer: importer - fileTypeRegex: options.fileTypeRegex + mimeType: options.mimeType warnings: options.warnings diff --git a/packages/rocketchat-importer/package.js b/packages/rocketchat-importer/package.js index bb33f0da6125e1cc5fd096a0b22e9760fa29ad5d..5820b21e2ac7b13a34e21357e2a19518f824cb23 100644 --- a/packages/rocketchat-importer/package.js +++ b/packages/rocketchat-importer/package.js @@ -34,7 +34,7 @@ Package.onUse(function(api) { //Server methods api.addFiles('server/methods/getImportProgress.coffee', 'server'); api.addFiles('server/methods/getSelectionData.coffee', 'server'); - api.addFiles('server/methods/prepareImport.coffee', 'server'); + api.addFiles('server/methods/prepareImport.js', 'server'); api.addFiles('server/methods/restartImport.coffee', 'server'); api.addFiles('server/methods/setupImporter.coffee', 'server'); api.addFiles('server/methods/startImport.coffee', 'server'); diff --git a/packages/rocketchat-importer/server/classes/ImporterBase.coffee b/packages/rocketchat-importer/server/classes/ImporterBase.coffee index 2e1a3771dd97eb751f8576f36aa09bf5bc184f5a..197e76d48ee86a6b76bdd5a7c22ba17bb3b1bf26 100644 --- a/packages/rocketchat-importer/server/classes/ImporterBase.coffee +++ b/packages/rocketchat-importer/server/classes/ImporterBase.coffee @@ -38,13 +38,14 @@ Importer.Base = class Importer.Base # # @param [String] name the name of the Importer # @param [String] description the i18n string which describes the importer - # @param [RegExp] fileTypeRegex the regexp to validate the uploaded file type against + # @param [String] mimeType the of the expected file type # - constructor: (@name, @description, @fileTypeRegex) -> + constructor: (@name, @description, @mimeType) -> @logger = new Logger("#{@name} Importer", {}); @progress = new Importer.Progress @name @collection = Importer.RawImports @AdmZip = Npm.require 'adm-zip' + @getFileType = Npm.require 'file-type' importId = Importer.Imports.insert { 'type': @name, 'ts': Date.now(), 'status': @progress.step, 'valid': true, 'user': Meteor.user()._id } @importRecord = Importer.Imports.findOne importId @users = {} @@ -60,8 +61,13 @@ Importer.Base = class Importer.Base # @return [Importer.Selection] Contains two properties which are arrays of objects, `channels` and `users`. # prepare: (dataURI, sentContentType, fileName) => - if not @fileTypeRegex.test sentContentType - throw new Error "Invalid file uploaded to import #{@name} data from." #TODO: Make translatable + fileType = @getFileType(new Buffer(dataURI.split(',')[1], 'base64')) + @logger.debug 'Uploaded file information is:', fileType + @logger.debug 'Expected file type is:', @mimeType + + if not fileType or fileType.mime isnt @mimeType + @logger.warn "Invalid file uploaded for the #{@name} importer." + throw new Meteor.Error('error-invalid-file-uploaded', "Invalid file uploaded to import #{@name} data from.", { step: 'prepare' }) @updateProgress Importer.ProgressStep.PREPARING_STARTED @updateRecord { 'file': fileName } diff --git a/packages/rocketchat-importer/server/methods/prepareImport.coffee b/packages/rocketchat-importer/server/methods/prepareImport.coffee deleted file mode 100644 index a1bab14a5fa096f3728b8f4ebfe7399db075db02..0000000000000000000000000000000000000000 --- a/packages/rocketchat-importer/server/methods/prepareImport.coffee +++ /dev/null @@ -1,9 +0,0 @@ -Meteor.methods - prepareImport: (name, dataURI, contentType, fileName) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'prepareImport' } - - if Importer.Importers[name]?.importerInstance? - Importer.Importers[name].importerInstance.prepare dataURI, contentType, fileName - else - throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'prepareImport' } diff --git a/packages/rocketchat-importer/server/methods/prepareImport.js b/packages/rocketchat-importer/server/methods/prepareImport.js new file mode 100644 index 0000000000000000000000000000000000000000..88b5ded9a7b4353d3e1e41c6f9ec103e07288287 --- /dev/null +++ b/packages/rocketchat-importer/server/methods/prepareImport.js @@ -0,0 +1,29 @@ +/* globals Importer */ + +Meteor.methods({ + prepareImport(name, dataURI, contentType, fileName) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'prepareImport' }); + } + + check(name, String); + check(dataURI, String); + check(fileName, String); + + if (name && Importer.Importers[name] && Importer.Importers[name].importerInstance) { + const results = Importer.Importers[name].importerInstance.prepare(dataURI, contentType, fileName); + + if (typeof results === 'object') { + if (results instanceof Promise) { + return results.catch(e => { throw new Meteor.Error(e); }); + } else { + return results; + } + } + } else if (!name) { + throw new Meteor.Error('error-importer-not-defined', `No Importer Found: "${name}"`, { method: 'prepareImport' }); + } else { + throw new Meteor.Error('error-importer-not-defined', `The importer, "${name}", was not defined correctly, it is missing the Import class.`, { method: 'prepareImport' }); + } + } +}); diff --git a/packages/rocketchat-importer/server/methods/restartImport.coffee b/packages/rocketchat-importer/server/methods/restartImport.coffee index 97b14c33e4b1bf116b4db96527f8c71603706deb..7df99cb95fee582ce0c1ce0249060937b6c5c18b 100644 --- a/packages/rocketchat-importer/server/methods/restartImport.coffee +++ b/packages/rocketchat-importer/server/methods/restartImport.coffee @@ -8,7 +8,7 @@ Meteor.methods importer.importerInstance.updateProgress Importer.ProgressStep.CANCELLED importer.importerInstance.updateRecord { valid: false } importer.importerInstance = undefined - importer.importerInstance = new importer.importer importer.name, importer.description, importer.fileTypeRegex + importer.importerInstance = new importer.importer importer.name, importer.description, importer.mimeType return importer.importerInstance.getProgress() else throw new Meteor.Error 'error-importer-not-defined', 'The importer was not defined correctly, it is missing the Import class.', { method: 'restartImport' } diff --git a/packages/rocketchat-importer/server/methods/setupImporter.coffee b/packages/rocketchat-importer/server/methods/setupImporter.coffee index b74f2f34c485c94f731c6c02c2761c1234d771cd..7e9e89e353a16e1342987419707da9a48c8a3561 100644 --- a/packages/rocketchat-importer/server/methods/setupImporter.coffee +++ b/packages/rocketchat-importer/server/methods/setupImporter.coffee @@ -9,7 +9,7 @@ Meteor.methods if importer.importerInstance return importer.importerInstance.getProgress() else - importer.importerInstance = new importer.importer importer.name, importer.description, importer.fileTypeRegex + importer.importerInstance = new importer.importer importer.name, importer.description, importer.mimeType return importer.importerInstance.getProgress() else console.warn "Tried to setup #{name} as an importer." diff --git a/packages/rocketchat-integrations/client/stylesheets/integrations.less b/packages/rocketchat-integrations/client/stylesheets/integrations.less index 04e51fba4147ca476d46d19543868a8f8c117399..d2a41bc010071cd5dc066b1d077be690fc631be6 100644 --- a/packages/rocketchat-integrations/client/stylesheets/integrations.less +++ b/packages/rocketchat-integrations/client/stylesheets/integrations.less @@ -3,17 +3,17 @@ display: flex; align-items: center; padding: 20px 10px; - color: #444; - border-bottom: 1px solid #ddd; + color: #444444; + border-bottom: 1px solid #dddddd; cursor: pointer; &:hover { background-color: #fafafa; } - >i { + > i { font-size: 2rem; - color: #aaa; + color: #aaaaaa; } .admin-integrations-new-item-body { @@ -32,7 +32,7 @@ .admin-integrations-new-item-description { font-size: 1rem; line-height: 1.5rem; - color: #aaa; + color: #aaaaaa; } } diff --git a/packages/rocketchat-integrations/client/stylesheets/load.coffee b/packages/rocketchat-integrations/client/stylesheets/load.coffee deleted file mode 100644 index 88d4bb35ad139307960ae22ec551881e3ae329ff..0000000000000000000000000000000000000000 --- a/packages/rocketchat-integrations/client/stylesheets/load.coffee +++ /dev/null @@ -1,2 +0,0 @@ -RocketChat.theme.addPackageAsset -> - return Assets.getText 'client/stylesheets/integrations.less' diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee b/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee index 68f912fc07eeb80d7dd6571569e1ea230e9708af..06655c651879466940003bf2422c6e50e1f7085f 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.coffee @@ -154,12 +154,12 @@ Template.integrationsIncoming.events "click .button-fullscreen": -> codeMirrorBox = $('.code-mirror-box') - codeMirrorBox.addClass('code-mirror-box-fullscreen') + codeMirrorBox.addClass('code-mirror-box-fullscreen content-background-color') codeMirrorBox.find('.CodeMirror')[0].CodeMirror.refresh() "click .button-restore": -> codeMirrorBox = $('.code-mirror-box') - codeMirrorBox.removeClass('code-mirror-box-fullscreen') + codeMirrorBox.removeClass('code-mirror-box-fullscreen content-background-color') codeMirrorBox.find('.CodeMirror')[0].CodeMirror.refresh() "click .submit > .save": -> diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.html b/packages/rocketchat-integrations/client/views/integrationsIncoming.html index 51f2ec9d52a8f3a93dec0011b8dfb5298f635ac3..68955e653b5d4bb836e8350e1762d611f79de1eb 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.html +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.html @@ -16,15 +16,15 @@ <label>{{_ "Name"}} ({{_ "optional"}})</label> <div> <input type="text" name="name" value="{{data.name}}" placeholder="{{_ 'Optional'}}" /> - <div class="settings-description">{{_ "You_should_name_it_to_easily_manage_your_integrations"}}</div> + <div class="settings-description secondary-font-color">{{_ "You_should_name_it_to_easily_manage_your_integrations"}}</div> </div> </div> <div class="input-line double-col"> <label>{{_ "Post_to_Channel"}}</label> <div> <input type="text" name="channel" value="{{data.channel}}" placeholder="{{_ 'User_or_channel_name'}}" /> - <div class="settings-description">{{_ "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here"}}</div> - <div class="settings-description">{{{_ "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s" "@" "#" "@john" "#general"}}}</div> + <div class="settings-description secondary-font-color">{{_ "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here"}}</div> + <div class="settings-description secondary-font-color">{{{_ "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s" "@" "#" "@john" "#general"}}}</div> </div> </div> <div class="input-line double-col"> @@ -35,31 +35,31 @@ {{else}} <input type="text" name="username" value="{{data.username}}" /> {{/if}} - <div class="settings-description">{{_ "Choose_the_username_that_this_integration_will_post_as"}}</div> - <div class="settings-description">{{_ "Should_exists_a_user_with_this_username"}}</div> + <div class="settings-description secondary-font-color">{{_ "Choose_the_username_that_this_integration_will_post_as"}}</div> + <div class="settings-description secondary-font-color">{{_ "Should_exists_a_user_with_this_username"}}</div> </div> </div> <div class="input-line double-col"> <label>{{_ "Alias"}} ({{_ "optional"}})</label> <div> <input type="text" name="alias" value="{{data.alias}}" placeholder="{{_ 'Optional'}}" /> - <div class="settings-description">{{_ "Choose_the_alias_that_will_appear_before_the_username_in_messages"}}</div> + <div class="settings-description secondary-font-color">{{_ "Choose_the_alias_that_will_appear_before_the_username_in_messages"}}</div> </div> </div> <div class="input-line double-col"> <label>{{_ "Avatar_URL"}} ({{_ "optional"}})</label> <div> <input type="url" name="avatar" value="{{data.avatar}}" placeholder="{{_ 'Optional'}}" /> - <div class="settings-description">{{_ "You_can_change_a_different_avatar_too"}}</div> - <div class="settings-description">{{_ "Should_be_a_URL_of_an_image"}}</div> + <div class="settings-description secondary-font-color">{{_ "You_can_change_a_different_avatar_too"}}</div> + <div class="settings-description secondary-font-color">{{_ "Should_be_a_URL_of_an_image"}}</div> </div> </div> <div class="input-line double-col"> <label>{{_ "Emoji"}} ({{_ "optional"}})</label> <div> <input type="text" name="emoji" value="{{data.emoji}}" placeholder="{{_ 'Optional'}}" /> - <div class="settings-description">{{_ "You_can_use_an_emoji_as_avatar"}}</div> - <div class="settings-description">{{{_ "Example_s" ":ghost:"}}}</div> + <div class="settings-description secondary-font-color">{{_ "You_can_use_an_emoji_as_avatar"}}</div> + <div class="settings-description secondary-font-color">{{{_ "Example_s" ":ghost:"}}}</div> </div> </div> <div class="input-line double-col"> @@ -84,10 +84,10 @@ </div> {{#if data.scriptError}} <div class="code-error-box"> - <div class="title"> + <div class="title color-content-background-color background-error-color"> {{data.scriptError.name}} </div> - <pre>{{data.scriptError.message}}</pre> + <pre class="script-error background-transparent-lightest error-color error-border">{{data.scriptError.message}}</pre> </div> {{/if}} </div> @@ -97,25 +97,25 @@ <label>Webhook URL</label> <div> <input type="text" name="webhookurl" value="{{data.url}}" readonly="readonly" /> - <div class="settings-description">{{_ "Send_your_JSON_payloads_to_this_URL"}}</div> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=webhookurl]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color">{{_ "Send_your_JSON_payloads_to_this_URL"}}</div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=webhookurl]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> </div> </div> <div class="input-line double-col"> <label>Token</label> <div> <input type="text" name="completeToken" value="{{data.completeToken}}" readonly="readonly" /> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=completeToken]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=completeToken]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> </div> </div> {{/if}} <div class="input-line double-col"> <label>{{_ "Example"}}</label> <div> - <pre><code class="hljs json json-example">{{{exampleJson}}}</code></pre> + <pre><code class="code-colors hljs json json-example">{{{exampleJson}}}</code></pre> {{#if curl}} <input type="text" name="curl" value="{{curl}}" readonly="readonly" /> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=curl]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=curl]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> {{/if}} </div> </div> diff --git a/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee b/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee index 7b78f0cc0e7d6eb59eb4f8cafb0c8c8124b84417..bb13783bfefd7502c88bf76ca8b0e581f1d306ad 100644 --- a/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee +++ b/packages/rocketchat-integrations/client/views/integrationsOutgoing.coffee @@ -136,11 +136,11 @@ Template.integrationsOutgoing.events FlowRouter.go "admin-integrations" "click .button-fullscreen": -> - $('.code-mirror-box').addClass('code-mirror-box-fullscreen'); + $('.code-mirror-box').addClass('code-mirror-box-fullscreen content-background-color'); $('.CodeMirror')[0].CodeMirror.refresh() "click .button-restore": -> - $('.code-mirror-box').removeClass('code-mirror-box-fullscreen'); + $('.code-mirror-box').removeClass('code-mirror-box-fullscreen content-background-color'); $('.CodeMirror')[0].CodeMirror.refresh() "click .submit > .save": -> diff --git a/packages/rocketchat-integrations/client/views/integrationsOutgoing.html b/packages/rocketchat-integrations/client/views/integrationsOutgoing.html index ef06ec00ecef62be459b92c92a66d6003b849979..ed690600e5e0c78a3f694885ba17c817f780d03d 100644 --- a/packages/rocketchat-integrations/client/views/integrationsOutgoing.html +++ b/packages/rocketchat-integrations/client/views/integrationsOutgoing.html @@ -110,10 +110,10 @@ </div> {{#if data.scriptError}} <div class="code-error-box"> - <div class="title"> + <div class="title color-content-background-color background-error-color"> {{data.scriptError.name}} </div> - <pre>{{data.scriptError.message}}</pre> + <pre class="script-error background-transparent-lightest error-color error-border">{{data.scriptError.message}}</pre> </div> {{/if}} </div> @@ -122,7 +122,7 @@ <label>{{_ "Responding"}}</label> <div> <div class="settings-description">{{{_ "If the handler wishes to post a response back into the channel, the following JSON should be returned as the body of the response:"}}}</div> - <pre><code class="hljs json json-example">{{{exampleJson}}}</code></pre> + <pre><code class="code-colors hljs json json-example">{{{exampleJson}}}</code></pre> <div class="settings-description">{{{_ "Empty bodies or bodies with an empty text property will simply be ignored. Non-200 responses will be retried a reasonable number of times. A response will be posted using the alias and avatar specified above. You can override these informations as in the example above."}}}</div> </div> </div> diff --git a/packages/rocketchat-integrations/package.js b/packages/rocketchat-integrations/package.js index eee59622436d549c17a82c331d680c989c58f984..d339476abdc5d1b5ccc0f5ef19da29c49e4e4e62 100644 --- a/packages/rocketchat-integrations/package.js +++ b/packages/rocketchat-integrations/package.js @@ -17,6 +17,7 @@ Package.onUse(function(api) { api.use('rocketchat:api'); api.use('rocketchat:theme'); api.use('rocketchat:logger'); + api.use('less'); api.use('kadira:flow-router', 'client'); api.use('templating', 'client'); @@ -37,8 +38,7 @@ Package.onUse(function(api) { api.addFiles('client/views/integrationsOutgoing.coffee', 'client'); // stylesheets - api.addAssets('client/stylesheets/integrations.less', 'server'); - api.addFiles('client/stylesheets/load.coffee', 'server'); + api.addFiles('client/stylesheets/integrations.less', 'client'); api.addFiles('server/logger.js', 'server'); api.addFiles('server/lib/validation.coffee', 'server'); diff --git a/packages/rocketchat-integrations/server/api/api.coffee b/packages/rocketchat-integrations/server/api/api.coffee index 1786e0ce2fe348f1167090bbd600bce3a1f4bb45..ff2a99e96da47912e779a152a81bf6c243d4f9bb 100644 --- a/packages/rocketchat-integrations/server/api/api.coffee +++ b/packages/rocketchat-integrations/server/api/api.coffee @@ -50,7 +50,10 @@ Api = new Restivus apiPath: 'hooks/' auth: user: -> - if @bodyParams?.payload? + payloadKeys = Object.keys @bodyParams + payloadIsWrapped = @bodyParams?.payload? and payloadKeys.length == 1 + + if payloadIsWrapped and @request.headers['content-type'] is 'application/x-www-form-urlencoded' @bodyParams = JSON.parse @bodyParams.payload @integration = RocketChat.models.Integrations.findOne @@ -237,17 +240,6 @@ integrationInfoRest = -> body: success: true - -RocketChat.API.v1.addRoute 'integrations.create', authRequired: true, - post: -> - return createIntegration @bodyParams, @user - - -RocketChat.API.v1.addRoute 'integrations.remove', authRequired: true, - post: -> - return removeIntegration @bodyParams, @user - - Api.addRoute ':integrationId/:userId/:token', authRequired: true, {post: executeIntegrationRest, get: executeIntegrationRest} Api.addRoute ':integrationId/:token', authRequired: true, {post: executeIntegrationRest, get: executeIntegrationRest} diff --git a/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee index bcf6823ba0824df5d3f5a1090d02b85bc420c797..66335efef3e46405c5ed5b310d7810729cafaabb 100644 --- a/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee +++ b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.coffee @@ -69,6 +69,7 @@ Meteor.methods integration.type = 'webhook-incoming' integration.token = token + integration.channel = channels integration.userId = user._id integration._createdAt = new Date integration._createdBy = RocketChat.models.Users.findOne @userId, {fields: {username: 1}} diff --git a/packages/rocketchat-integrations/server/processWebhookMessage.js b/packages/rocketchat-integrations/server/processWebhookMessage.js index 56730a50d55c120b9163b3333c3855a51a167b94..ad4e301bf674d07f19939aed88de06a418e4c78c 100644 --- a/packages/rocketchat-integrations/server/processWebhookMessage.js +++ b/packages/rocketchat-integrations/server/processWebhookMessage.js @@ -1,5 +1,48 @@ +function retrieveRoomInfo({ currentUserId, channel, ignoreEmpty=false }) { + const room = RocketChat.models.Rooms.findOneByIdOrName(channel); + if (!_.isObject(room) && !ignoreEmpty) { + throw new Meteor.Error('invalid-channel'); + } + + if (room && room.t === 'c') { + Meteor.runAsUser(currentUserId, function() { + return Meteor.call('joinRoom', room._id); + }); + } + + return room; +} + +function retrieveDirectMessageInfo({ currentUserId, channel, findByUserIdOnly=false }) { + let roomUser = undefined; + + if (findByUserIdOnly) { + roomUser = RocketChat.models.Users.findOneById(channel); + } else { + roomUser = RocketChat.models.Users.findOne({ + $or: [{ _id: channel }, { username: channel }] + }); + } + + if (!_.isObject(roomUser)) { + throw new Meteor.Error('invalid-channel'); + } + + const rid = [currentUserId, roomUser._id].sort().join(''); + let room = RocketChat.models.Rooms.findOneById({ $in: [rid, channel] }); + + if (!room) { + Meteor.runAsUser(currentUserId, function() { + Meteor.call('createDirectMessage', roomUser.username); + room = RocketChat.models.Rooms.findOneById(rid); + }); + } + + return room; +} + this.processWebhookMessage = function(messageObj, user, defaultValues) { - var attachment, channel, channels, channelType, i, len, message, ref, rid, room, roomUser, ret; + var attachment, channel, channels, channelType, i, len, message, ref, room, ret; ret = []; if (!defaultValues) { @@ -11,7 +54,7 @@ this.processWebhookMessage = function(messageObj, user, defaultValues) { }; } - channel = messageObj.channel || defaultValues.channel; + channel = messageObj.channel || messageObj.roomId || defaultValues.channel; channels = [].concat(channel); @@ -22,41 +65,26 @@ this.processWebhookMessage = function(messageObj, user, defaultValues) { switch (channelType) { case '#': - room = RocketChat.models.Rooms.findOneByIdOrName(channel); - if (!_.isObject(room)) { - throw new Meteor.Error('invalid-channel'); - } - rid = room._id; - if (room.t === 'c') { - Meteor.runAsUser(user._id, function() { - return Meteor.call('joinRoom', room._id); - }); - } + room = retrieveRoomInfo({ currentUserId: user._id, channel }); break; case '@': - roomUser = RocketChat.models.Users.findOne({ - $or: [ - { - _id: channel - }, { - username: channel - } - ] - }) || {}; - rid = [user._id, roomUser._id].sort().join(''); - room = RocketChat.models.Rooms.findOneById({$in: [rid, channel]}); - if (!_.isObject(roomUser) && !_.isObject(room)) { - throw new Meteor.Error('invalid-channel'); - } - if (!room) { - Meteor.runAsUser(user._id, function() { - Meteor.call('createDirectMessage', roomUser.username); - room = RocketChat.models.Rooms.findOneById(rid); - }); - } + room = retrieveDirectMessageInfo({ currentUserId: user._id, channel }); break; default: - throw new Meteor.Error('invalid-channel-type'); + //Try to find the room by id or name if they didn't include the prefix. + room = retrieveRoomInfo({ currentUserId: user._id, channel: channelType + channel, ignoreEmpty: true }); + if (room) { + break; + } + + //We didn't get a room, let's try finding direct messages + room = retrieveDirectMessageInfo({ currentUserId: user._id, channel: channelType + channel, findByUserIdOnly: true }); + if (room) { + break; + } + + //No room, so throw an error + throw new Meteor.Error('invalid-channel'); } if (messageObj.attachments && !_.isArray(messageObj.attachments)) { diff --git a/packages/rocketchat-integrations/server/triggers.coffee b/packages/rocketchat-integrations/server/triggers.coffee index 324f4040eb56b48d36b5d4bd841890c1a6073627..3835d5de61f9ce8ceaa480b2cf1be6545563ad29 100644 --- a/packages/rocketchat-integrations/server/triggers.coffee +++ b/packages/rocketchat-integrations/server/triggers.coffee @@ -132,6 +132,7 @@ ExecuteTriggerUrl = (url, trigger, message, room, tries=0) -> return data = + message_id: message._id token: trigger.token channel_id: room._id channel_name: room.name diff --git a/packages/rocketchat-irc/server/server.coffee b/packages/rocketchat-irc/server/server.coffee index 032635abd7fba691effb395ad1dc30fa27d1f70b..4f753e1683eb1e101e5192f5d8ca3ce911aa436c 100644 --- a/packages/rocketchat-irc/server/server.coffee +++ b/packages/rocketchat-irc/server/server.coffee @@ -1,6 +1,6 @@ # # # # Assign values -# +# # Package availability IRC_AVAILABILITY = RocketChat.settings.get('IRC_Enabled'); @@ -19,7 +19,7 @@ IRC_HOST = RocketChat.settings.get('IRC_Host'); ircClientMap = {} -# # # +# # # # Core functionality # @@ -237,7 +237,7 @@ class IrcClient @sendRawMessage msg initRoomList: -> - roomsCursor = RocketChat.models.Rooms.findByTypeContainigUsername 'c', @user.username, + roomsCursor = RocketChat.models.Rooms.findByTypeContainingUsername 'c', @user.username, fields: name: 1 t: 1 @@ -396,7 +396,7 @@ class IrcLogoutCleanUper # # # -# Make magic happen +# Make magic happen # # Only proceed if the package has been enabled diff --git a/packages/rocketchat-katex/katex.coffee b/packages/rocketchat-katex/katex.coffee index 6b07134a39ca99b51f095d47020c259e7338d05a..f108ab9b6ea4972bee05aa0f0c043dd8b29ebc64 100644 --- a/packages/rocketchat-katex/katex.coffee +++ b/packages/rocketchat-katex/katex.coffee @@ -2,6 +2,9 @@ # KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web. # https://github.com/Khan/KaTeX ### + +import katex from './client/katex/katex.min.js' + class Katex constructor: -> @delimiters_map = [ diff --git a/packages/rocketchat-katex/package.js b/packages/rocketchat-katex/package.js index c7f561b190deb26fc646353f0d1beeab2491ab13..300f7f86b12a7786a2d1eb064f918efc9062d464 100644 --- a/packages/rocketchat-katex/package.js +++ b/packages/rocketchat-katex/package.js @@ -15,7 +15,6 @@ Package.onUse(function(api) { api.addFiles('settings.coffee', 'server'); api.addFiles('katex.coffee'); - api.addFiles('client/katex/katex.min.js', 'client'); api.addFiles('client/katex/katex.min.css', 'client'); api.addFiles('client/style.css', 'client'); diff --git a/packages/rocketchat-ldap/server/loginHandler.js b/packages/rocketchat-ldap/server/loginHandler.js index 9c396c2eae638a793d8612b2a9c51f4c59b7b032..b142dfd2c49df155b682c7999ea3bf58c1751436 100644 --- a/packages/rocketchat-ldap/server/loginHandler.js +++ b/packages/rocketchat-ldap/server/loginHandler.js @@ -12,7 +12,7 @@ function fallbackDefaultAccountSystem(bind, username, password) { } } - logger.info('Fallback to default account systen', username); + logger.info('Fallback to default account system', username); const loginRequest = { user: username, diff --git a/packages/rocketchat-ldap/server/sync.js b/packages/rocketchat-ldap/server/sync.js index bce6c278f7ac4ddf43e08ae0c35a683d5037eb90..d235b7cc49daadf7f9f93325a4c792295a830368 100644 --- a/packages/rocketchat-ldap/server/sync.js +++ b/packages/rocketchat-ldap/server/sync.js @@ -187,11 +187,6 @@ addLdapUser = function addLdapUser(ldapUser, username, password) { syncUserData(userObject, ldapUser); - logger.info('Joining user to default channels'); - Meteor.runAsUser(userObject._id, function() { - Meteor.call('joinDefaultChannels'); - }); - return { userId: userObject._id }; @@ -226,6 +221,8 @@ sync = function sync() { if (!user) { addLdapUser(ldapUser, username); + } else if (user.ldap !== true && RocketChat.settings.get('LDAP_Merge_Existing_Users') === true) { + syncUserData(user, ldapUser); } }); } diff --git a/packages/rocketchat-lib/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-lib/.npm/package/npm-shrinkwrap.json index d31e308b77e624019bc0e7abeed7883ae3230348..d2ac7e3c6b8c266d560b99c4069a68147fded02c 100644 --- a/packages/rocketchat-lib/.npm/package/npm-shrinkwrap.json +++ b/packages/rocketchat-lib/.npm/package/npm-shrinkwrap.json @@ -324,6 +324,11 @@ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.4.2.tgz", "from": "localforage@1.4.2" }, + "lokijs": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.4.1.tgz", + "from": "lokijs@1.4.1" + }, "mime-db": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz", @@ -354,6 +359,11 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", "from": "object-keys@>=1.0.6 <2.0.0" }, + "object-path": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.9.2.tgz", + "from": "object-path@0.9.2" + }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", diff --git a/packages/rocketchat-lib/client/MessageAction.coffee b/packages/rocketchat-lib/client/MessageAction.coffee index 5f066302dd271f5b2fe4ea8b8c7be993febb602e..80837c3a0bebe9e4dd587f2dcc35e2d4d9b1251c 100644 --- a/packages/rocketchat-lib/client/MessageAction.coffee +++ b/packages/rocketchat-lib/client/MessageAction.coffee @@ -183,8 +183,12 @@ Meteor.startup -> ] action: (event, instance) -> message = @_arguments[1] + permalink = RocketChat.MessageAction.getPermaLink(message._id) RocketChat.MessageAction.hideDropDown() - $(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id)); + if Meteor.isCordova + cordova.plugins.clipboard.copy(permalink); + else + $(event.currentTarget).attr('data-clipboard-text', permalink); toastr.success(TAPi18n.__('Copied')) validation: (message) -> if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? @@ -205,7 +209,10 @@ Meteor.startup -> action: (event, instance) -> message = @_arguments[1].msg RocketChat.MessageAction.hideDropDown() - $(event.currentTarget).attr('data-clipboard-text', message) + if Meteor.isCordova + cordova.plugins.clipboard.copy(message); + else + $(event.currentTarget).attr('data-clipboard-text', message) toastr.success(TAPi18n.__('Copied')) validation: (message) -> if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? diff --git a/packages/rocketchat-lib/client/TabBar.coffee b/packages/rocketchat-lib/client/TabBar.coffee index db93cc82c402eba586a7783442dcd26a63dd55d0..0a3fd17b37ddd4aab3436bed9f95c2fc426e5e1f 100644 --- a/packages/rocketchat-lib/client/TabBar.coffee +++ b/packages/rocketchat-lib/client/TabBar.coffee @@ -12,6 +12,9 @@ RocketChat.TabBar = new class visibleGroup = new ReactiveVar '' + _setTemplate = (t) -> + template.set t + setTemplate = (t, callback) -> template.set t openFlex(callback) @@ -110,6 +113,8 @@ RocketChat.TabBar = new class extraGroups[id] ?= [] extraGroups[id] = _.union extraGroups[id], groups + _setTemplate: _setTemplate + removeGroup = (id, groups) -> Tracker.nonreactive -> btns = buttons.get() diff --git a/packages/rocketchat-lib/client/lib/cachedCollection.js b/packages/rocketchat-lib/client/lib/cachedCollection.js index 175725ed775210f0603c88f22eca357862e860fd..d5c54a69e14828d9fb29d8b8fc1db32a3f02010b 100644 --- a/packages/rocketchat-lib/client/lib/cachedCollection.js +++ b/packages/rocketchat-lib/client/lib/cachedCollection.js @@ -98,7 +98,7 @@ class CachedCollection { useSync = true, useCache = true, debug = true, - version = 3, + version = 5, maxCacheTime = 60*60*24*30 }) { this.collection = collection || new Meteor.Collection(null); @@ -195,6 +195,7 @@ class CachedCollection { Meteor.call(this.methodName, (error, data) => { this.log(`${data.length} records loaded from server`); data.forEach((record) => { + delete record.$loki; this.collection.upsert({ _id: record._id }, _.omit(record, '_id')); if (record._updatedAt && record._updatedAt > this.updatedAt) { @@ -310,9 +311,10 @@ class CachedCollection { setupListener(eventType, eventName) { RocketChat.Notifications[eventType || this.eventType](eventName || this.eventName, (t, record) => { this.log('record received', t, record); - if (t === 'remove') { + if (t === 'removed') { this.collection.remove(record._id); } else { + delete record.$loki; this.collection.upsert({ _id: record._id }, _.omit(record, '_id')); } diff --git a/packages/rocketchat-lib/client/lib/openRoom.coffee b/packages/rocketchat-lib/client/lib/openRoom.coffee index 0734abde72e7562357e59834e0b7953e4f164539..ecb6e411c98585e827e53b1a4980571863f9ed9a 100644 --- a/packages/rocketchat-lib/client/lib/openRoom.coffee +++ b/packages/rocketchat-lib/client/lib/openRoom.coffee @@ -28,11 +28,18 @@ currentTracker = undefined BlazeLayout.render 'main', {center: 'roomNotFound'} return else - Session.set 'roomNotFound', {type: type, name: name} - BlazeLayout.render 'main', {center: 'roomNotFound'} + Meteor.call 'getRoomByTypeAndName', type, name, (err, record) -> + if err? + Session.set 'roomNotFound', {type: type, name: name} + BlazeLayout.render 'main', {center: 'roomNotFound'} + else + delete record.$loki + RocketChat.models.Rooms.upsert({ _id: record._id }, _.omit(record, '_id')) + RoomManager.close(type + name) + openRoom(type, name) + return - $('.rocket-loader').remove(); mainNode = document.querySelector('.main-content') if mainNode? for child in mainNode.children diff --git a/packages/rocketchat-lib/client/lib/roomTypes.coffee b/packages/rocketchat-lib/client/lib/roomTypes.coffee index d77a095640dc56d51454a10008d53e15b0cc8d2d..53689c00fff491978a1bc403c4927ab429b825e7 100644 --- a/packages/rocketchat-lib/client/lib/roomTypes.coffee +++ b/packages/rocketchat-lib/client/lib/roomTypes.coffee @@ -21,6 +21,9 @@ RocketChat.roomTypes = new class roomTypesClient extends roomTypesCommon list = _.reject @roomTypesOrder, (t) -> return except.indexOf(t.identifier) isnt -1 return _.map list, (t) -> return t.identifier + getUserStatus: (roomType, roomId) -> + return @roomTypes[roomType]?.getUserStatus?(roomId) + findRoom: (roomType, identifier, user) -> return @roomTypes[roomType]?.findRoom identifier, user diff --git a/packages/rocketchat-lib/client/views/customFieldsForm.html b/packages/rocketchat-lib/client/views/customFieldsForm.html new file mode 100644 index 0000000000000000000000000000000000000000..5b067a372a0727d57db98835796110d29b8c50ac --- /dev/null +++ b/packages/rocketchat-lib/client/views/customFieldsForm.html @@ -0,0 +1,26 @@ +<template name="customFieldsForm"> + {{#each customFields}} + {{#if $eq field.type 'select'}} + <div class="input-line"> + <label for="{{fieldName}}">{{_ fieldName}}</label> + <div> + <select name="{{fieldName}}" data-customfield="true"> + {{#each field.options}} + <option value="{{.}}" selected="{{selectedField . ..}}">{{_ .}}</option> + {{/each}} + </select> + <div class="input-error"></div> + </div> + </div> + {{/if}} + {{#if $eq field.type 'text'}} + <div class="input-line"> + <label for="{{fieldName}}">{{_ fieldName}}</label> + <div> + <input type="text" name="{{fieldName}}" id="{{fieldName}}" data-customfield="true" value="{{fieldValue}}" maxlength="{{field.maxLength}}" /> + <div class="input-error"></div> + </div> + </div> + {{/if}} + {{/each}} +</template> diff --git a/packages/rocketchat-lib/client/views/customFieldsForm.js b/packages/rocketchat-lib/client/views/customFieldsForm.js new file mode 100644 index 0000000000000000000000000000000000000000..56d5ae7e22724347515afaf6971c255fd461f877 --- /dev/null +++ b/packages/rocketchat-lib/client/views/customFieldsForm.js @@ -0,0 +1,59 @@ +Template.customFieldsForm.helpers({ + customFields() { + const customFields = Template.instance().customFields.get(); + + if (!customFields) { + return []; + } + + const customFieldsArray = []; + + Object.keys(customFields).forEach((key) => { + const value = customFields[key]; + if (value.hideFromForm === true && Template.instance().hideFromForm === true) { + return; + } + customFieldsArray.push({ + fieldName: key, + field: value + }); + }); + + return customFieldsArray; + }, + selectedField(current, field) { + const formData = Template.instance().formData; + + if (typeof formData[field.fieldName] !== 'undefined') { + return formData[field.fieldName] === current; + } else if (typeof field.defaultValue !== 'undefined') { + return field.defaultValue === current; + } + }, + fieldValue() { + const formData = Template.instance().formData; + + return formData[this.fieldName]; + } +}); + +Template.customFieldsForm.onCreated(function() { + this.customFields = new ReactiveVar(); + + const currentData = Template.currentData(); + this.hideFromForm = currentData && currentData.hideFromForm; + this.formData = (currentData && currentData.formData) || {}; + + Tracker.autorun(() => { + const Accounts_CustomFields = RocketChat.settings.get('Accounts_CustomFields'); + if (typeof Accounts_CustomFields === 'string' && Accounts_CustomFields.trim() !== '') { + try { + this.customFields.set(JSON.parse(RocketChat.settings.get('Accounts_CustomFields'))); + } catch (e) { + console.error('Invalid JSON for Accounts_CustomFields'); + } + } else { + this.customFields.set(undefined); + } + }); +}); diff --git a/packages/rocketchat-lib/lib/roomTypesCommon.coffee b/packages/rocketchat-lib/lib/roomTypesCommon.coffee index f18efd87c7783189958dda0a88bb4718e745731b..a351dc258922d7c20994afec0a29b84a833b54a4 100644 --- a/packages/rocketchat-lib/lib/roomTypesCommon.coffee +++ b/packages/rocketchat-lib/lib/roomTypesCommon.coffee @@ -60,3 +60,16 @@ class @roomTypesCommon return FlowRouter.path @roomTypes[roomType].route.name, routeData + openRouteLink: (roomType, subData, queryParams) -> + unless @roomTypes[roomType]? + return false + + routeData = {} + + if @roomTypes[roomType]?.route?.link? + routeData = @roomTypes[roomType].route.link(subData) + else if subData?.name? + routeData = { name: subData.name } + + return FlowRouter.go @roomTypes[roomType].route.name, routeData, queryParams + diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index b5ca3dfe3e2ed6465f363b79ecbe2fb6221f20ad..3b0612fabb9c560fd0437ea58b7bc67a4ee324e1 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -7,8 +7,10 @@ Package.describe({ Npm.depends({ 'bad-words': '1.3.1', + 'object-path': '0.9.2', 'node-dogstatsd': '0.0.6', 'localforage': '1.4.2', + 'lokijs': '1.4.1', 'bugsnag': '1.8.0' }); @@ -63,6 +65,7 @@ Package.onUse(function(api) { api.addFiles('server/lib/RateLimiter.coffee', 'server'); // SERVER FUNCTIONS + api.addFiles('server/functions/isDocker.js', 'server'); api.addFiles('server/functions/addUserToDefaultChannels.js', 'server'); api.addFiles('server/functions/addUserToRoom.js', 'server'); api.addFiles('server/functions/archiveRoom.js', 'server'); @@ -71,6 +74,7 @@ Package.onUse(function(api) { api.addFiles('server/functions/createRoom.js', 'server'); api.addFiles('server/functions/deleteMessage.js', 'server'); api.addFiles('server/functions/deleteUser.js', 'server'); + api.addFiles('server/functions/getFullUserData.js', 'server'); api.addFiles('server/functions/removeUserFromRoom.js', 'server'); api.addFiles('server/functions/saveUser.js', 'server'); api.addFiles('server/functions/saveCustomFields.js', 'server'); @@ -103,19 +107,26 @@ Package.onUse(function(api) { api.addFiles('server/startup/statsTracker.js', 'server'); + // CACHE + api.addFiles('server/startup/cache/CacheLoad.js', 'server'); + // SERVER PUBLICATIONS api.addFiles('server/publications/settings.coffee', 'server'); // SERVER METHODS api.addFiles('server/methods/addOAuthService.coffee', 'server'); + api.addFiles('server/methods/refreshOAuthService.js', 'server'); api.addFiles('server/methods/addUserToRoom.coffee', 'server'); api.addFiles('server/methods/archiveRoom.coffee', 'server'); + api.addFiles('server/methods/blockUser.js', 'server'); api.addFiles('server/methods/checkRegistrationSecretURL.coffee', 'server'); api.addFiles('server/methods/createChannel.coffee', 'server'); api.addFiles('server/methods/createPrivateGroup.coffee', 'server'); api.addFiles('server/methods/deleteMessage.coffee', 'server'); api.addFiles('server/methods/deleteUserOwnAccount.js', 'server'); + api.addFiles('server/methods/getFullUserData.js', 'server'); api.addFiles('server/methods/getRoomRoles.js', 'server'); + api.addFiles('server/methods/getServerInfo.js', 'server'); api.addFiles('server/methods/getUserRoles.js', 'server'); api.addFiles('server/methods/joinRoom.coffee', 'server'); api.addFiles('server/methods/joinDefaultChannels.coffee', 'server'); @@ -133,9 +144,12 @@ Package.onUse(function(api) { api.addFiles('server/methods/setEmail.js', 'server'); api.addFiles('server/methods/restartServer.coffee', 'server'); api.addFiles('server/methods/unarchiveRoom.coffee', 'server'); + api.addFiles('server/methods/unblockUser.js', 'server'); api.addFiles('server/methods/updateMessage.coffee', 'server'); api.addFiles('server/methods/filterBadWords.js', ['server']); api.addFiles('server/methods/filterATAllTag.js', 'server'); + api.addFiles('server/methods/getChannelHistory.js', 'server'); + api.addFiles('server/methods/cleanChannelHistory.js', 'server'); // SERVER STARTUP api.addFiles('server/startup/settingsOnLoadCdnPrefix.coffee', 'server'); @@ -169,6 +183,10 @@ Package.onUse(function(api) { api.addFiles('client/models/_Base.coffee', 'client'); api.addFiles('client/models/Uploads.coffee', 'client'); + // CLIENT VIEWS + api.addFiles('client/views/customFieldsForm.html', 'client'); + api.addFiles('client/views/customFieldsForm.js', 'client'); + api.addFiles('startup/defaultRoomTypes.coffee'); // VERSION diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-lib/rocketchat.info index e56b5d6f0a09a3c0f1c200bcff514da1b80c6d6c..b276990b92a8de47ed536c9895c39f37e4aaf44a 100644 --- a/packages/rocketchat-lib/rocketchat.info +++ b/packages/rocketchat-lib/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "0.47.0-develop" + "version": "0.50.0-develop" } diff --git a/packages/rocketchat-lib/server/functions/Notifications.coffee b/packages/rocketchat-lib/server/functions/Notifications.coffee index b8c76b744c471142bdbb25233ad4afd5a207c19c..fc81ede6be950cf1059054165a80e8e84d9f005c 100644 --- a/packages/rocketchat-lib/server/functions/Notifications.coffee +++ b/packages/rocketchat-lib/server/functions/Notifications.coffee @@ -33,7 +33,11 @@ RocketChat.Notifications = new class roomId = eventName.split('/')[0] user = Meteor.users.findOne @userId, {fields: {username: 1}} - return RocketChat.models.Rooms.findOneByIdContainigUsername(roomId, user.username, {fields: {_id: 1}})? + room = RocketChat.models.Rooms.findOneById(roomId) + if room.t is 'l' and room.v._id is user._id + return true + + return room.usernames.indexOf(user.username) > -1 @streamRoomUsers.allowRead('none'); diff --git a/packages/rocketchat-lib/server/functions/createRoom.js b/packages/rocketchat-lib/server/functions/createRoom.js index e2f325eda6ee2fbd874e2787d110e333e9f58693..bb86253791b0cdddd638635422cd71f02c340368 100644 --- a/packages/rocketchat-lib/server/functions/createRoom.js +++ b/packages/rocketchat-lib/server/functions/createRoom.js @@ -54,7 +54,7 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly) { }); } - room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames(type, name, owner.username, members, { + room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames(type, name, owner, members, { ts: now, ro: readOnly === true, sysMes: readOnly !== true diff --git a/packages/rocketchat-lib/server/functions/deleteMessage.js b/packages/rocketchat-lib/server/functions/deleteMessage.js index 54ac4dd917589bf9965905af321a6570f103ee27..a193308fb2ebb80da91d1a9c793f283e85e7b05d 100644 --- a/packages/rocketchat-lib/server/functions/deleteMessage.js +++ b/packages/rocketchat-lib/server/functions/deleteMessage.js @@ -2,6 +2,7 @@ RocketChat.deleteMessage = function(message, user) { let keepHistory = RocketChat.settings.get('Message_KeepHistory'); let showDeletedStatus = RocketChat.settings.get('Message_ShowDeletedStatus'); + let deletedMsg; if (keepHistory) { if (showDeletedStatus) { @@ -15,12 +16,17 @@ RocketChat.deleteMessage = function(message, user) { } } else { if (!showDeletedStatus) { + deletedMsg = RocketChat.models.Messages.findOneById(message._id); RocketChat.models.Messages.removeById(message._id); } if (message.file && message.file._id) { FileUpload.delete(message.file._id); } + + Meteor.defer(function() { + RocketChat.callbacks.run('afterDeleteMessage', deletedMsg); + }); } if (showDeletedStatus) { diff --git a/packages/rocketchat-lib/server/functions/getFullUserData.js b/packages/rocketchat-lib/server/functions/getFullUserData.js new file mode 100644 index 0000000000000000000000000000000000000000..7e77af13e3603047d7877b279b79d92b432df18f --- /dev/null +++ b/packages/rocketchat-lib/server/functions/getFullUserData.js @@ -0,0 +1,51 @@ +/* globals RocketChat */ +RocketChat.getFullUserData = function({userId, filter, limit}) { + let fields = { + name: 1, + username: 1, + status: 1, + utcOffset: 1, + type: 1, + active: 1 + }; + + if (RocketChat.authz.hasPermission(userId, 'view-full-other-user-info')) { + fields = _.extend(fields, { + emails: 1, + phone: 1, + statusConnection: 1, + createdAt: 1, + lastLogin: 1, + services: 1, + requirePasswordChange: 1, + requirePasswordChangeReason: 1, + roles: 1, + customFields: 1 + }); + } else if (limit !== 0) { + limit = 1; + } + + filter = s.trim(filter); + + if (!filter && limit === 1) { + return undefined; + } + + const options = { + fields: fields, + limit: limit, + sort: { username: 1 } + }; + + if (filter) { + if (limit === 1) { + return RocketChat.models.Users.findByUsername(filter, options); + } else { + const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); + return RocketChat.models.Users.findByUsernameNameOrEmailAddress(filterReg, options); + } + } + + return RocketChat.models.Users.find({}, options); +}; diff --git a/packages/rocketchat-lib/server/functions/isDocker.js b/packages/rocketchat-lib/server/functions/isDocker.js new file mode 100644 index 0000000000000000000000000000000000000000..3ec908de64e9c0a41856ab5cc5ddeda51bc8c0ce --- /dev/null +++ b/packages/rocketchat-lib/server/functions/isDocker.js @@ -0,0 +1,31 @@ +import fs from 'fs'; + +function hasDockerEnv() { + try { + fs.statSync('/.dockerenv'); + return true; + } catch (err) { + return false; + } +} + +function hasDockerCGroup() { + try { + return fs.readFileSync('/proc/self/cgroup', 'utf8').indexOf('docker') !== -1; + } catch (err) { + return false; + } +} + +function check() { + return hasDockerEnv() || hasDockerCGroup(); +} + +let isDocker; +RocketChat.isDocker = function() { + if (isDocker === undefined) { + isDocker = check(); + } + + return isDocker; +}; diff --git a/packages/rocketchat-lib/server/functions/saveUser.js b/packages/rocketchat-lib/server/functions/saveUser.js index 4ba9882871e9ae0c9e65d6f387f0355ab545bbe9..ac322efd231045752e76d253c1176101bcd490f1 100644 --- a/packages/rocketchat-lib/server/functions/saveUser.js +++ b/packages/rocketchat-lib/server/functions/saveUser.js @@ -1,5 +1,7 @@ +/* globals Gravatar */ RocketChat.saveUser = function(userId, userData) { const user = RocketChat.models.Users.findOneById(userId); + let existingRoles = _.pluck(RocketChat.authz.getRoles(), '_id'); if (userData._id && userId !== userData._id && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) { throw new Meteor.Error('error-action-not-allowed', 'Editing user is not allowed', { method: 'insertOrUpdateUser', action: 'Editing_user' }); @@ -9,7 +11,11 @@ RocketChat.saveUser = function(userId, userData) { throw new Meteor.Error('error-action-not-allowed', 'Adding user is not allowed', { method: 'insertOrUpdateUser', action: 'Adding_user' }); } - if (userData.role === 'admin' && !RocketChat.authz.hasPermission(userId, 'assign-admin-role')) { + if (userData.roles && _.difference(userData.roles, existingRoles).length > 0) { + throw new Meteor.Error('error-action-not-allowed', 'The field Roles consist invalid role name', { method: 'insertOrUpdateUser', action: 'Assign_role' }); + } + + if (userData.roles && _.indexOf(userData.roles, 'admin') >= 0 && !RocketChat.authz.hasPermission(userId, 'assign-admin-role')) { throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', { method: 'insertOrUpdateUser', action: 'Assign_admin' }); } @@ -51,7 +57,8 @@ RocketChat.saveUser = function(userId, userData) { // insert user const createUser = { username: userData.username, - password: userData.password + password: userData.password, + joinDefaultChannels: userData.joinDefaultChannels }; if (userData.email) { createUser.email = userData.email; @@ -62,7 +69,7 @@ RocketChat.saveUser = function(userId, userData) { const updateUser = { $set: { name: userData.name, - roles: [ (userData.role || 'user') ] + roles: userData.roles || ['user'] } }; @@ -76,12 +83,6 @@ RocketChat.saveUser = function(userId, userData) { Meteor.users.update({ _id: _id }, updateUser); - if (userData.joinDefaultChannels) { - Meteor.runAsUser(_id, () => { - Meteor.call('joinDefaultChannels'); - }); - } - if (userData.sendWelcomeEmail) { const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); @@ -119,9 +120,33 @@ RocketChat.saveUser = function(userId, userData) { }); } + userData._id = _id; + + if (RocketChat.settings.get('Accounts_SetDefaultAvatar') === true && userData.email) { + let gravatarUrl = Gravatar.imageUrl(userData.email, {default: '404', size: 200, secure: true}); + + try { + RocketChat.setUserAvatar(userData, gravatarUrl, '', 'url'); + } catch (e) { + //Ignore this error for now, as it not being successful isn't bad + } + } + return _id; } else { // update user + if (userData.username) { + RocketChat.setUsername(userData._id, userData.username); + } + + if (userData.email) { + RocketChat.setEmail(userData._id, userData.email); + } + + if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) { + Accounts.setPassword(userData._id, userData.password.trim()); + } + const updateUser = { $set: {} }; @@ -130,30 +155,20 @@ RocketChat.saveUser = function(userId, userData) { updateUser.$set.name = userData.name; } + if (userData.roles) { + updateUser.$set.roles = userData.roles; + } + if (userData.requirePasswordChange) { updateUser.$set.requirePasswordChange = userData.requirePasswordChange; } if (userData.verified) { - updateUser.$set['emails.0.verified'] = true; - } else { - updateUser.$set['emails.0.verified'] = false; + updateUser.$set['emails.0.verified'] = userData.verified; } Meteor.users.update({ _id: userData._id }, updateUser); - if (userData.username) { - RocketChat.setUsername(userData._id, userData.username); - } - - if (userData.email) { - RocketChat.setEmail(userData._id, userData.email); - } - - if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) { - Accounts.setPassword(userData._id, userData.password.trim()); - } - return true; } }; diff --git a/packages/rocketchat-lib/server/functions/sendMessage.coffee b/packages/rocketchat-lib/server/functions/sendMessage.coffee index 4532d965983013a390e5a3b76d2f253d88d4c48f..bdce87faba4d40f69610db48e1f17fe269834568 100644 --- a/packages/rocketchat-lib/server/functions/sendMessage.coffee +++ b/packages/rocketchat-lib/server/functions/sendMessage.coffee @@ -13,7 +13,11 @@ RocketChat.sendMessage = (user, message, room, upsert = false) -> message.rid = room._id if not room.usernames? || room.usernames.length is 0 - room = RocketChat.models.Rooms.findOneById(room._id) + updated_room = RocketChat.models.Rooms.findOneById(room._id) + if updated_room? + room = updated_room + else + room.usernames = [] if message.parseUrls isnt false if urls = message.msg.match /([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g diff --git a/packages/rocketchat-lib/server/functions/setUsername.coffee b/packages/rocketchat-lib/server/functions/setUsername.coffee index 3d38c88edbf1a9b9d15feadbe454ca7f1e6521f2..66163088f331feffe3ba814cbb7895782dbee0b3 100644 --- a/packages/rocketchat-lib/server/functions/setUsername.coffee +++ b/packages/rocketchat-lib/server/functions/setUsername.coffee @@ -24,14 +24,27 @@ RocketChat._setUsername = (userId, username) -> unless RocketChat.checkUsernameAvailability username return false - - # If first time setting username, send Enrollment Email try if not previousUsername and user.emails?.length > 0 and RocketChat.settings.get 'Accounts_Enrollment_Email' Accounts.sendEnrollmentEmail(user._id) catch error + user.username = username + + # If first time setting username, check if should set default avatar + if not previousUsername and RocketChat.settings.get('Accounts_SetDefaultAvatar') is true + avatarSuggestions = getAvatarSuggestionForUser user + for service, avatarData of avatarSuggestions + if service isnt 'gravatar' + RocketChat.setUserAvatar(user, avatarData.blob, avatarData.contentType, service) + gravatar = null + break + else + gravatar = avatarData + if gravatar? + RocketChat.setUserAvatar(user, gravatar.blob, gravatar.contentType, 'gravatar') + # Username is available; if coming from old username, update all references if previousUsername RocketChat.models.Messages.updateAllUsernamesByUserId user._id, username @@ -58,7 +71,6 @@ RocketChat._setUsername = (userId, username) -> # Set new username RocketChat.models.Users.setUsername user._id, username - user.username = username return user RocketChat.setUsername = RocketChat.RateLimiter.limitFunction RocketChat._setUsername, 1, 60000, diff --git a/packages/rocketchat-lib/server/functions/settings.coffee b/packages/rocketchat-lib/server/functions/settings.coffee index e5197b6f20f446bf521ec152f215e0125108d497..f8348261c5f009408eba5a9077d1b8c4e8b45dc1 100644 --- a/packages/rocketchat-lib/server/functions/settings.coffee +++ b/packages/rocketchat-lib/server/functions/settings.coffee @@ -92,9 +92,16 @@ RocketChat.settings.add = (_id, value, options = {}) -> updateOperations.$unset = { section: 1 } query.section = { $exists: false } - if not RocketChat.models.Settings.findOne(query)? + existantSetting = RocketChat.models.Settings.db.findOne(query) + + if existantSetting? + if not existantSetting.editor? and updateOperations.$setOnInsert.editor? + updateOperations.$set.editor = updateOperations.$setOnInsert.editor + delete updateOperations.$setOnInsert.editor + else updateOperations.$set.ts = new Date - return RocketChat.models.Settings.upsert { _id: _id }, updateOperations + + return RocketChat.models.Settings.upsert { _id: _id }, updateOperations diff --git a/packages/rocketchat-lib/server/lib/bugsnag.js b/packages/rocketchat-lib/server/lib/bugsnag.js index d250eacedddd2340bc3066cb84767e9f9cde0b7a..cfe1fa9853be56b6b611a7a7296996e1f81d28d4 100644 --- a/packages/rocketchat-lib/server/lib/bugsnag.js +++ b/packages/rocketchat-lib/server/lib/bugsnag.js @@ -12,7 +12,10 @@ const notify = function(message, stack) { if (typeof stack === 'string') { message += ' ' + stack; } - const options = { app: { version: RocketChat.Info.version, info: RocketChat.Info } }; + let options = {}; + if (RocketChat.Info) { + options = { app: { version: RocketChat.Info.version, info: RocketChat.Info } }; + } const error = new Error(message); error.stack = stack; RocketChat.bugsnag.notify(error, options); @@ -20,6 +23,7 @@ const notify = function(message, stack) { process.on('uncaughtException', Meteor.bindEnvironment((error) => { notify(error.message, error.stack); + throw error; })); let originalMeteorDebug = Meteor._debug; diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index d2500c4ef0159ca87e4a743759c03518a7bb16b8..714d96d9262469bb98aa3604787f5b7a8360d765 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -74,7 +74,9 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { settings.alwaysNotifyMobileUsers = []; settings.dontNotifyMobileUsers = []; settings.desktopNotificationDurations = {}; - RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id).forEach(function(subscription) { + + const notificationPreferencesByRoom = RocketChat.models.Subscriptions.findNotificationPreferencesByRoom(room._id); + notificationPreferencesByRoom.forEach(function(subscription) { if (subscription.desktopNotifications === 'all') { settings.alwaysNotifyDesktopUsers.push(subscription.u._id); } else if (subscription.desktopNotifications === 'nothing') { @@ -91,10 +93,11 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { userIdsToNotify = []; userIdsToPushNotify = []; usersWithHighlights = []; + highlights = RocketChat.models.Users.findUsersByUsernamesWithHighlights(room.usernames, { fields: { '_id': 1, 'settings.preferences.highlights': 1 }}).fetch(); highlights.forEach(function(user) { - if (user && user.settings && user.settings.preferences && messageContainsHighlight(message, user.settings.preferences.highlights)) { + if (messageContainsHighlight(message, user.settings.preferences.highlights)) { usersWithHighlights.push(user); } }); @@ -148,6 +151,7 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { } }); } + if ((userOfMention != null) && canBeNotified(userOfMentionId, 'desktop')) { if (Push.enabled === true && userOfMention.statusConnection !== 'online') { Push.send({ @@ -174,6 +178,7 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { return message; } } + } else { mentionIds = []; if ((ref = message.mentions) != null) { diff --git a/packages/rocketchat-lib/server/methods/addOAuthService.coffee b/packages/rocketchat-lib/server/methods/addOAuthService.coffee index b3ff589bc14a925714f18e5b3376cb6709b43267..1fcd37b69729e2bc3dd0b13fcbf86fa2acd34d1e 100644 --- a/packages/rocketchat-lib/server/methods/addOAuthService.coffee +++ b/packages/rocketchat-lib/server/methods/addOAuthService.coffee @@ -9,18 +9,20 @@ Meteor.methods unless RocketChat.authz.hasPermission( Meteor.userId(), 'add-oauth-service') is true throw new Meteor.Error 'error-action-not-allowed', 'Adding OAuth Services is not allowed', { method: 'addOAuthService', action: 'Adding_OAuth_Services' } - name = name.toLowerCase().replace(/[^a-z0-9]/g, '') + name = name.toLowerCase().replace(/[^a-z0-9_]/g, '') name = s.capitalize(name) - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}" , false , { type: 'boolean', group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Enable', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_url" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'URL', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_token_path" , '/oauth/token' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Token_Path', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_identity_path" , '/me' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Identity_Path', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_authorize_path" , '/oauth/authorize', { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Authorize_Path', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_scope" , 'openid' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Scope', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_token_sent_via" , 'payload' , { type: 'select' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Token_Sent_Via', persistent: true, values: [ { key: 'header', i18nLabel: 'Header' }, { key: 'payload', i18nLabel: 'Payload' } ] } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_id" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_id', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_secret" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Secret', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_login_style" , 'popup' , { type: 'select' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Login_Style', persistent: true, values: [ { key: 'redirect', i18nLabel: 'Redirect' }, { key: 'popup', i18nLabel: 'Popup' }, { key: '', i18nLabel: 'Default' } ] } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_button_label_text" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_button_label_color" , '#FFFFFF' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true } - RocketChat.settings.add "Accounts_OAuth_Custom_#{name}_button_color" , '#13679A' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}" , false , { type: 'boolean', group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Enable', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-url" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'URL', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-token_path" , '/oauth/token' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Token_Path', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-identity_path" , '/me' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Identity_Path', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-authorize_path" , '/oauth/authorize', { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Authorize_Path', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-scope" , 'openid' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Scope', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-token_sent_via" , 'payload' , { type: 'select' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Token_Sent_Via', persistent: true, values: [ { key: 'header', i18nLabel: 'Header' }, { key: 'payload', i18nLabel: 'Payload' } ] } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-id" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_id', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-secret" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Secret', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-login_style" , 'popup' , { type: 'select' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Login_Style', persistent: true, values: [ { key: 'redirect', i18nLabel: 'Redirect' }, { key: 'popup', i18nLabel: 'Popup' }, { key: '', i18nLabel: 'Default' } ] } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-button_label_text" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Text', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-button_label_color" , '#FFFFFF' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-button_color" , '#13679A' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Button_Color', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-username_field" , '' , { type: 'string' , group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Username_Field', persistent: true } + RocketChat.settings.add "Accounts_OAuth_Custom-#{name}-merge_users" , false , { type: 'boolean', group: 'OAuth', section: "Custom OAuth: #{name}", i18nLabel: 'Accounts_OAuth_Custom_Merge_Users', persistent: true } diff --git a/packages/rocketchat-lib/server/methods/blockUser.js b/packages/rocketchat-lib/server/methods/blockUser.js new file mode 100644 index 0000000000000000000000000000000000000000..27d2a8d65912dbf3a9004b82616589001b9ccc25 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/blockUser.js @@ -0,0 +1,22 @@ +Meteor.methods({ + blockUser({rid, blocked}) { + + check(rid, String); + check(blocked, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'blockUser' }); + } + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, Meteor.userId()); + const subscription2 = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, blocked); + + if (!subscription || !subscription2) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); + } + + RocketChat.models.Subscriptions.setBlockedByRoomId(rid, blocked, Meteor.userId()); + + return true; + } +}); diff --git a/packages/rocketchat-lib/server/methods/cleanChannelHistory.js b/packages/rocketchat-lib/server/methods/cleanChannelHistory.js new file mode 100644 index 0000000000000000000000000000000000000000..8c96388c7fb481de602de4fcaeae302735e66def --- /dev/null +++ b/packages/rocketchat-lib/server/methods/cleanChannelHistory.js @@ -0,0 +1,34 @@ +Meteor.methods({ + cleanChannelHistory({roomId, latest, oldest, inclusive}) { + check(roomId, String); + check(latest, Date); + check(oldest, Date); + check(inclusive, Boolean); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cleanChannelHistory' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'clean-channel-history')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'cleanChannelHistory' }); + } + + if (inclusive) { + RocketChat.models.Messages.remove({ + rid: roomId, + ts: { + $gte: oldest, + $lte: latest + } + }); + } else { + RocketChat.models.Messages.remove({ + rid: roomId, + ts: { + $gt: oldest, + $lt: latest + } + }); + } + } +}); diff --git a/packages/rocketchat-lib/server/methods/getChannelHistory.js b/packages/rocketchat-lib/server/methods/getChannelHistory.js new file mode 100644 index 0000000000000000000000000000000000000000..03958a40fae564bf82c8a748e241ef713ab78039 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/getChannelHistory.js @@ -0,0 +1,81 @@ +Meteor.methods({ + getChannelHistory({rid, latest, oldest, inclusive, count = 20, unreads}) { + check(rid, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getChannelHistory' }); + } + + const fromUserId = Meteor.userId(); + const room = Meteor.call('canAccessRoom', rid, fromUserId); + if (!room) { + return false; + } + + //Make sure they can access the room + if (room.t === 'c' && !RocketChat.authz.hasPermission(fromUserId, 'preview-c-room') && room.usernames.indexOf(room.username) === -1) { + return false; + } + + //Ensure latest is always defined. + if (_.isUndefined(latest)) { + latest = new Date(); + } + + //Verify oldest is a date if it exists + if (!_.isUndefined(oldest) && !_.isDate(oldest)) { + throw new Meteor.Error('error-invalid-date', 'Invalid date', { method: 'getChannelHistory' }); + } + + const options = { + sort: { + ts: -1 + }, + limit: count + }; + + if (!RocketChat.settings.get('Message_ShowEditedStatus')) { + options.fields = { 'editedAt': 0 }; + } + + let records = []; + if (_.isUndefined(oldest) && inclusive) { + records = RocketChat.models.Messages.findVisibleByRoomIdBeforeTimestampInclusive(rid, latest, options).fetch(); + } else if (_.isUndefined(oldest) && !inclusive) { + records = RocketChat.models.Messages.findVisibleByRoomIdBeforeTimestamp(rid, latest, options).fetch(); + } else if (!_.isUndefined(oldest) && inclusive) { + records = RocketChat.models.Messages.findVisibleByRoomIdBetweenTimestampsInclusive(rid, oldest, latest, options).fetch(); + } else { + records = RocketChat.models.Messages.findVisibleByRoomIdBetweenTimestamps(rid, oldest, latest, options).fetch(); + } + + const messages = _.map(records, (message) => { + message.starred = _.findWhere(message.starred, { _id: fromUserId }); + return message; + }); + + if (unreads) { + let unreadNotLoaded = 0; + let firstUnread = undefined; + + if (!_.isUndefined(oldest)) { + const firstMsg = messages[messages.length - 1]; + if (!_.isUndefined(firstMsg) && firstMsg.ts > oldest) { + const unreadMessages = RocketChat.models.Messages.findVisibleByRoomIdBetweenTimestamps(rid, oldest, firstMsg.ts, { limit: 1, sort: { ts: 1 } }); + firstUnread = unreadMessages.fetch()[0]; + unreadNotLoaded = unreadMessages.count(); + } + } + + return { + messages, + firstUnread, + unreadNotLoaded + }; + } + + return { + messages + }; + } +}); diff --git a/packages/rocketchat-lib/server/methods/getFullUserData.js b/packages/rocketchat-lib/server/methods/getFullUserData.js new file mode 100644 index 0000000000000000000000000000000000000000..f9caf8b23d771ed83e6e5aae5da384b6af0d7f1e --- /dev/null +++ b/packages/rocketchat-lib/server/methods/getFullUserData.js @@ -0,0 +1,11 @@ +Meteor.methods({ + getFullUserData({ filter = '', limit }) { + const result = RocketChat.getFullUserData({ userId: Meteor.userId(), filter, limit }); + + if (!result) { + return result; + } + + return result.fetch(); + } +}); diff --git a/packages/rocketchat-lib/server/methods/getServerInfo.js b/packages/rocketchat-lib/server/methods/getServerInfo.js new file mode 100644 index 0000000000000000000000000000000000000000..8db017a1528f86176404133ff13803bac7540d17 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/getServerInfo.js @@ -0,0 +1,5 @@ +Meteor.methods({ + getServerInfo() { + return RocketChat.Info; + } +}); diff --git a/packages/rocketchat-lib/server/methods/leaveRoom.coffee b/packages/rocketchat-lib/server/methods/leaveRoom.coffee index 7e66f405bc2348dc676ca84695ebad25243b5857..462b10cb715cb7d7c758f3a766aa49009b50e49f 100644 --- a/packages/rocketchat-lib/server/methods/leaveRoom.coffee +++ b/packages/rocketchat-lib/server/methods/leaveRoom.coffee @@ -11,9 +11,12 @@ Meteor.methods fromId = Meteor.userId() room = RocketChat.models.Rooms.findOneById rid user = Meteor.user() - + if room.t is 'd' - throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'leaveRoom' } + throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'leaveRoom' } + + if user.username not in (room?.usernames or []) + throw new Meteor.Error 'error-user-not-in-room', 'You are not in this room', { method: 'leaveRoom' } # If user is room owner, check if there are other owners. If there isn't anyone else, warn user to set a new owner. if RocketChat.authz.hasRole(user._id, 'owner', room._id) diff --git a/packages/rocketchat-lib/server/methods/refreshOAuthService.js b/packages/rocketchat-lib/server/methods/refreshOAuthService.js new file mode 100644 index 0000000000000000000000000000000000000000..513090b6ecd6a59d46163c34f61a1f6706eb2f67 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/refreshOAuthService.js @@ -0,0 +1,15 @@ +Meteor.methods({ + refreshOAuthService() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'refreshOAuthService' }); + } + + if (RocketChat.authz.hasPermission(Meteor.userId(), 'add-oauth-service') !== true) { + throw new Meteor.Error('error-action-not-allowed', 'Refresh OAuth Services is not allowed', { method: 'refreshOAuthService', action: 'Refreshing_OAuth_Services' }); + } + + ServiceConfiguration.configurations.remove({}); + + RocketChat.models.Settings.update({_id: /^Accounts_OAuth_.+/}, {$set: {_updatedAt: new Date}}, {multi: true}); + } +}); diff --git a/packages/rocketchat-lib/server/methods/removeOAuthService.coffee b/packages/rocketchat-lib/server/methods/removeOAuthService.coffee index 689609246ecfd52809a0220f3ad14755b9de0a92..13ba3a2afb81d114707fc819453d46c173efdf45 100644 --- a/packages/rocketchat-lib/server/methods/removeOAuthService.coffee +++ b/packages/rocketchat-lib/server/methods/removeOAuthService.coffee @@ -9,18 +9,20 @@ Meteor.methods unless RocketChat.authz.hasPermission( Meteor.userId(), 'add-oauth-service') is true throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'removeOAuthService' } - name = name.toLowerCase().replace(/[^a-z0-9]/g, '') + name = name.toLowerCase().replace(/[^a-z0-9_]/g, '') name = s.capitalize(name) - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_url" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_token_path" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_identity_path" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_authorize_path" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_scope" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_token_sent_via" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_id" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_secret" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_button_label_text" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_button_label_color" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_button_color" - RocketChat.settings.removeById "Accounts_OAuth_Custom_#{name}_login_style" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-url" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-token_path" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-identity_path" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-authorize_path" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-scope" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-token_sent_via" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-id" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-secret" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-button_label_text" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-button_label_color" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-button_color" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-login_style" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-username_field" + RocketChat.settings.removeById "Accounts_OAuth_Custom-#{name}-merge_users" diff --git a/packages/rocketchat-lib/server/methods/saveSetting.js b/packages/rocketchat-lib/server/methods/saveSetting.js index 029732e9909a6554fe0151cba0bccbb31a5f5aab..f9380922607016de99567a9c3c741d34046b37c8 100644 --- a/packages/rocketchat-lib/server/methods/saveSetting.js +++ b/packages/rocketchat-lib/server/methods/saveSetting.js @@ -15,7 +15,7 @@ Meteor.methods({ //Verify the _id passed in is a string. check(_id, String); - const setting = RocketChat.models.Settings.findOneById(_id); + const setting = RocketChat.models.Settings.db.findOneById(_id); //Verify the value is what it should be switch (setting.type) { diff --git a/packages/rocketchat-lib/server/methods/sendMessage.coffee b/packages/rocketchat-lib/server/methods/sendMessage.coffee index c787be5f359ad82a407124ad0dbaa7272f884b42..e8defe8ed2e8e1fdc0436ba02f10459927932e2d 100644 --- a/packages/rocketchat-lib/server/methods/sendMessage.coffee +++ b/packages/rocketchat-lib/server/methods/sendMessage.coffee @@ -5,6 +5,9 @@ Meteor.methods check message, Object + if not Meteor.userId() + throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'sendMessage' }) + if message.ts tsDiff = Math.abs(moment(message.ts).diff()) if tsDiff > 60000 @@ -17,9 +20,6 @@ Meteor.methods if message.msg?.length > RocketChat.settings.get('Message_MaxAllowedSize') throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', { method: 'sendMessage' }) - if not Meteor.userId() - throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'sendMessage' }) - user = RocketChat.models.Users.findOneById Meteor.userId(), fields: username: 1, name: 1 room = Meteor.call 'canAccessRoom', message.rid, user._id @@ -27,6 +27,16 @@ Meteor.methods if not room return false + subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId()); + if subscription and (subscription.blocked or subscription.blocker) + RocketChat.Notifications.notifyUser Meteor.userId(), 'message', { + _id: Random.id() + rid: room._id + ts: new Date + msg: TAPi18n.__('room_is_blocked', {}, user.language) + } + return false + if user.username in (room.muted or []) RocketChat.Notifications.notifyUser Meteor.userId(), 'message', { _id: Random.id() @@ -42,10 +52,12 @@ Meteor.methods RocketChat.sendMessage user, message, room -# Limit a user to sending 5 msgs/second +# Limit a user, who does not have the "bot" role, to sending 5 msgs/second DDPRateLimiter.addRule type: 'method' name: 'sendMessage' userId: (userId) -> - return RocketChat.models.Users.findOneById(userId)?.username isnt RocketChat.settings.get('InternalHubot_Username') + user = RocketChat.models.Users.findOneById(userId) + return true if not user?.roles + return 'bot' not in user.roles , 5, 1000 diff --git a/packages/rocketchat-lib/server/methods/setUsername.coffee b/packages/rocketchat-lib/server/methods/setUsername.coffee index 3438c74b2f1506e10af61997d18fe56eaff176d9..af260d1eac33930c9b3df8624f9d0f7d482074e4 100644 --- a/packages/rocketchat-lib/server/methods/setUsername.coffee +++ b/packages/rocketchat-lib/server/methods/setUsername.coffee @@ -1,5 +1,5 @@ Meteor.methods - setUsername: (username) -> + setUsername: (username, {joinDefaultChannelsSilenced}={}) -> check username, String @@ -33,6 +33,10 @@ Meteor.methods unless RocketChat.setUsername user._id, username throw new Meteor.Error 'error-could-not-change-username', "Could not change username", { method: 'setUsername' } + if not user.username? + Meteor.runAsUser user._id, -> + Meteor.call('joinDefaultChannels', joinDefaultChannelsSilenced) + return username RocketChat.RateLimiter.limitMethod 'setUsername', 1, 1000, diff --git a/packages/rocketchat-lib/server/methods/unblockUser.js b/packages/rocketchat-lib/server/methods/unblockUser.js new file mode 100644 index 0000000000000000000000000000000000000000..d91d2f3780c6a6c363e8fd8a95f008f55e86ba12 --- /dev/null +++ b/packages/rocketchat-lib/server/methods/unblockUser.js @@ -0,0 +1,22 @@ +Meteor.methods({ + unblockUser({rid, blocked}) { + + check(rid, String); + check(blocked, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'blockUser' }); + } + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, Meteor.userId()); + const subscription2 = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, blocked); + + if (!subscription || !subscription2) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); + } + + RocketChat.models.Subscriptions.unsetBlockedByRoomId(rid, blocked, Meteor.userId()); + + return true; + } +}); diff --git a/packages/rocketchat-lib/server/models/Messages.coffee b/packages/rocketchat-lib/server/models/Messages.coffee index 02cc0f77e56dc4849771d6ed1d6f5d8f1db53d9f..dbaee48242a5759fdfb08514f6564c92fbbca40c 100644 --- a/packages/rocketchat-lib/server/models/Messages.coffee +++ b/packages/rocketchat-lib/server/models/Messages.coffee @@ -89,6 +89,16 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base return @find query, options + findVisibleByRoomIdBeforeTimestampInclusive: (roomId, timestamp, options) -> + query = + _hidden: + $ne: true + rid: roomId + ts: + $lte: timestamp + + return @find query, options + findVisibleByRoomIdBetweenTimestamps: (roomId, afterTimestamp, beforeTimestamp, options) -> query = _hidden: @@ -100,6 +110,17 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base return @find query, options + findVisibleByRoomIdBetweenTimestampsInclusive: (roomId, afterTimestamp, beforeTimestamp, options) -> + query = + _hidden: + $ne: true + rid: roomId + ts: + $gte: afterTimestamp + $lte: beforeTimestamp + + return @find query, options + findVisibleByRoomIdBeforeTimestampNotContainingTypes: (roomId, timestamp, types, options) -> query = _hidden: @@ -189,6 +210,12 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base return @findOne query + findOneBySlackTs: (slackTs) -> + query = + slackTs: slackTs + + return @findOne query + cloneAndSaveAsHistoryById: (_id) -> me = RocketChat.models.Users.findOneById Meteor.userId() record = @findOneById _id @@ -223,6 +250,7 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base urls: [] mentions: [] attachments: [] + reactions: [] editedAt: new Date() editedBy: _id: user._id diff --git a/packages/rocketchat-lib/server/models/Rooms.coffee b/packages/rocketchat-lib/server/models/Rooms.coffee index 415a6b3850d6cd17b9fdfb39f8921aa89d3a810b..32707524b6734b1a1f18e170b8a127d44a40a385 100644 --- a/packages/rocketchat-lib/server/models/Rooms.coffee +++ b/packages/rocketchat-lib/server/models/Rooms.coffee @@ -8,8 +8,15 @@ class ModelRooms extends RocketChat.models._Base @tryEnsureIndex { 't': 1 } @tryEnsureIndex { 'u._id': 1 } + this.cache.ignoreUpdatedFields.push('msgs', 'lm') + this.cache.ensureIndex(['t', 'name'], 'unique') + this.cache.options = {fields: {usernames: 0}} + # FIND ONE findOneById: (_id, options) -> + if this.useCache + return this.cache.findByIndex('_id', _id, options).fetch() + query = _id: _id @@ -45,14 +52,14 @@ class ModelRooms extends RocketChat.models._Base return @findOne query, options - findOneByIdContainigUsername: (_id, username, options) -> + findOneByIdContainingUsername: (_id, username, options) -> query = _id: _id usernames: username return @findOne query, options - findOneByNameAndTypeNotContainigUsername: (name, type, username, options) -> + findOneByNameAndTypeNotContainingUsername: (name, type, username, options) -> query = name: name t: type @@ -89,6 +96,47 @@ class ModelRooms extends RocketChat.models._Base return @find query, options + findBySubscriptionUserId: (userId, options) -> + if this.useCache + data = RocketChat.models.Subscriptions.findByUserId(userId).fetch() + data = data.map (item) -> + if item._room + return item._room + console.log('Empty Room for Subscription', item); + return {} + return this.arrayToCursor this.processQueryOptionsOnResult(data, options) + + data = RocketChat.models.Subscriptions.findByUserId(userId, {fields: {rid: 1}}).fetch() + data = data.map (item) -> item.rid + + query = + _id: + $in: data + + this.find query, options + + findBySubscriptionUserIdUpdatedAfter: (userId, _updatedAt, options) -> + if this.useCache + data = RocketChat.models.Subscriptions.findByUserId(userId).fetch() + data = data.map (item) -> + if item._room + return item._room + console.log('Empty Room for Subscription', item); + return {} + data = data.filter (item) -> item._updatedAt > _updatedAt + return this.arrayToCursor this.processQueryOptionsOnResult(data, options) + + ids = RocketChat.models.Subscriptions.findByUserId(userId, {fields: {rid: 1}}).fetch() + ids = ids.map (item) -> item.rid + + query = + _id: + $in: ids + _updatedAt: + $gt: _updatedAt + + this.find query, options + findByNameContaining: (name, options) -> nameRegex = new RegExp s.trim(s.escapeRegExp(name)), "i" @@ -182,14 +230,14 @@ class ModelRooms extends RocketChat.models._Base return @find query, options - findByTypeContainigUsername: (type, username, options) -> + findByTypeContainingUsername: (type, username, options) -> query = t: type usernames: username return @find query, options - findByTypeContainigUsernames: (type, username, options) -> + findByTypeContainingUsernames: (type, username, options) -> query = t: type usernames: { $all: [].concat(username) } @@ -206,13 +254,16 @@ class ModelRooms extends RocketChat.models._Base return @find query, options - findByContainigUsername: (username, options) -> + findByContainingUsername: (username, options) -> query = usernames: username return @find query, options findByTypeAndName: (type, name, options) -> + if this.useCache + return this.cache.findByIndex('t,name', [type, name], options) + query = name: name t: type @@ -549,4 +600,4 @@ class ModelRooms extends RocketChat.models._Base return @remove query -RocketChat.models.Rooms = new ModelRooms('room') +RocketChat.models.Rooms = new ModelRooms('room', true) diff --git a/packages/rocketchat-lib/server/models/Settings.coffee b/packages/rocketchat-lib/server/models/Settings.coffee index eb2769bc65c2236a54998f2e4eca3d0b1185b55a..05cb9946db279a04c463e68475d6108f37db4dd9 100644 --- a/packages/rocketchat-lib/server/models/Settings.coffee +++ b/packages/rocketchat-lib/server/models/Settings.coffee @@ -148,4 +148,4 @@ class ModelSettings extends RocketChat.models._Base return @remove query -RocketChat.models.Settings = new ModelSettings('settings') +RocketChat.models.Settings = new ModelSettings('settings', true) diff --git a/packages/rocketchat-lib/server/models/Subscriptions.coffee b/packages/rocketchat-lib/server/models/Subscriptions.coffee index df71930731c3ed6120a0e65f3ccd9551b437ea6b..ddeb5e4e357a46fe49543aa908c822a69245325e 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.coffee +++ b/packages/rocketchat-lib/server/models/Subscriptions.coffee @@ -16,8 +16,16 @@ class ModelSubscriptions extends RocketChat.models._Base @tryEnsureIndex { 'mobilePushNotifications': 1 }, { sparse: 1 } @tryEnsureIndex { 'emailNotifications': 1 }, { sparse: 1 } + this.cache.ensureIndex('rid', 'array') + this.cache.ensureIndex('u._id', 'array') + this.cache.ensureIndex(['rid', 'u._id'], 'unique') + + # FIND ONE findOneByRoomIdAndUserId: (roomId, userId) -> + if this.useCache + return this.cache.findByIndex('rid,u._id', [roomId, userId]).fetch() + query = rid: roomId "u._id": userId @@ -26,6 +34,9 @@ class ModelSubscriptions extends RocketChat.models._Base # FIND findByUserId: (userId, options) -> + if this.useCache + return this.cache.findByIndex('u._id', userId, options) + query = "u._id": userId @@ -71,6 +82,9 @@ class ModelSubscriptions extends RocketChat.models._Base return @find query, options findByRoomId: (roomId, options) -> + if this.useCache + return this.cache.findByIndex('rid', roomId, options) + query = rid: roomId @@ -290,6 +304,44 @@ class ModelSubscriptions extends RocketChat.models._Base return @update query, update, { multi: true } + setBlockedByRoomId: (rid, blocked, blocker) -> + query = + rid: rid + 'u._id': blocked + + update = + $set: + blocked: true + + query2 = + rid: rid + 'u._id': blocker + + update2 = + $set: + blocker: true + + return @update(query, update) and @update(query2, update2) + + unsetBlockedByRoomId: (rid, blocked, blocker) -> + query = + rid: rid + 'u._id': blocked + + update = + $unset: + blocked: 1 + + query2 = + rid: rid + 'u._id': blocker + + update2 = + $unset: + blocker: 1 + + return @update(query, update) and @update(query2, update2) + updateTypeByRoomId: (roomId, type) -> query = rid: roomId @@ -370,4 +422,4 @@ class ModelSubscriptions extends RocketChat.models._Base return @remove query -RocketChat.models.Subscriptions = new ModelSubscriptions('subscription') +RocketChat.models.Subscriptions = new ModelSubscriptions('subscription', true) diff --git a/packages/rocketchat-lib/server/models/Uploads.coffee b/packages/rocketchat-lib/server/models/Uploads.coffee index 01cbeedf3d2ca2e7f68831b2011db1cb81696817..dc41e6939cdd42cc58015b97dd53bf30411e74d8 100644 --- a/packages/rocketchat-lib/server/models/Uploads.coffee +++ b/packages/rocketchat-lib/server/models/Uploads.coffee @@ -25,6 +25,7 @@ RocketChat.models.Uploads = new class extends RocketChat.models._Base userId: 1 rid: 1 name: 1 + description: 1 type: 1 url: 1 uploadedAt: 1 diff --git a/packages/rocketchat-lib/server/models/Users.coffee b/packages/rocketchat-lib/server/models/Users.coffee index 3e343b39b325242ddc528eed8e374dde522255ba..6c4df1f5212acc80116a072fad672f1e6007f34a 100644 --- a/packages/rocketchat-lib/server/models/Users.coffee +++ b/packages/rocketchat-lib/server/models/Users.coffee @@ -68,6 +68,17 @@ class ModelUsers extends RocketChat.models._Base return @find query, options findUsersByUsernamesWithHighlights: (usernames, options) -> + if this.useCache + result = + fetch: () -> + return RocketChat.models.Users.getDynamicView('highlights').data().filter (record) -> + return usernames.indexOf(record.username) > -1 + count: () -> + return result.fetch().length + forEach: (fn) -> + return result.fetch().forEach(fn) + return result + query = username: { $in: usernames } 'settings.preferences.highlights.0': @@ -353,7 +364,7 @@ class ModelUsers extends RocketChat.models._Base address: s.trim(data.email) ] else - unsetData.name = 1 + unsetData.emails = 1 if data.phone? if not _.isEmpty(s.trim(data.phone)) @@ -410,4 +421,4 @@ class ModelUsers extends RocketChat.models._Base return @find query, { fields: { name: 1, username: 1, emails: 1, 'settings.preferences.emailNotificationMode': 1 } } -RocketChat.models.Users = new ModelUsers(Meteor.users) +RocketChat.models.Users = new ModelUsers(Meteor.users, true) diff --git a/packages/rocketchat-lib/server/models/_Base.js b/packages/rocketchat-lib/server/models/_Base.js index d2ac1f12a4fdfe5d4574a5c68b0407f7d2835245..940b98d9c4690eade7eae177d95860970606f1a1 100644 --- a/packages/rocketchat-lib/server/models/_Base.js +++ b/packages/rocketchat-lib/server/models/_Base.js @@ -1,252 +1,168 @@ -const {EventEmitter} = Npm.require('events'); +import ModelsBaseDb from './_BaseDb'; +import ModelsBaseCache from './_BaseCache'; -const baseName = 'rocketchat_'; +RocketChat.models._CacheControl = new Meteor.EnvironmentVariable(); -const trash = new Mongo.Collection(baseName + '_trash'); -try { - trash._ensureIndex({ collection: 1 }); - trash._ensureIndex({ _deletedAt: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 30 }); -} catch (e) { - console.log(e); -} +class ModelsBase { + constructor(nameOrModel, useCache) { + this._db = new ModelsBaseDb(nameOrModel, this); + this.model = this._db.model; + this.collectionName = this._db.collectionName; + this.name = this._db.name; -class ModelsBase extends EventEmitter { - constructor(model) { - super(); - - if (Match.test(model, String)) { - this.name = model; - this.collectionName = this.baseName + this.name; - this.model = new Mongo.Collection(this.collectionName); - } else { - this.name = model._name; - this.collectionName = this.name; - this.model = model; - } + this._useCache = useCache === true; - this.tryEnsureIndex({ '_updatedAt': 1 }); - } + this.cache = new ModelsBaseCache(this); + // TODO_CACHE: remove + this.on = this.cache.on.bind(this.cache); + this.emit = this.cache.emit.bind(this.cache); + this.getDynamicView = this.cache.getDynamicView.bind(this.cache); + this.processQueryOptionsOnResult = this.cache.processQueryOptionsOnResult.bind(this.cache); + // END_TODO_CACHE - get baseName() { - return baseName; - } + this.db = this; - setUpdatedAt(record = {}, checkQuery = false, query) { - if (checkQuery === true) { - if (!query || Object.keys(query).length === 0) { - throw new Meteor.Error('Models._Base: Empty query'); - } + if (this._useCache) { + this.db = new this.constructor(this.model, false); } + } - if (/(^|,)\$/.test(Object.keys(record).join(','))) { - record.$set = record.$set || {}; - record.$set._updatedAt = new Date; - } else { - record._updatedAt = new Date; + get useCache() { + if (RocketChat.models._CacheControl.get() === false) { + return false; } - return record; + return this._useCache; } - find() { - return this.model.find(...arguments); + get origin() { + return this.useCache === true ? 'cache' : '_db'; } - findOne() { - return this.model.findOne(...arguments); + arrayToCursor(data) { + return { + fetch() { + return data; + }, + count() { + return data.length; + }, + forEach(fn) { + return data.forEach(fn); + } + }; } - insert(record) { - this.setUpdatedAt(record); - - const result = this.model.insert(...arguments); - record._id = result; - this.emit('insert', record); - this.emit('change', 'insert', record); - return result; + setUpdatedAt(/*record, checkQuery, query*/) { + return this._db.setUpdatedAt(...arguments); } - update(query, update, options = {}) { - this.setUpdatedAt(update, true, query); - - if (options.upsert) { - return this.upsert(query, update); - } - - let ids = []; - if (options.multi === true) { - const updated = this.model.find(query, { fields: { _id: 1 } }).fetch(); - if (updated) { - ids = ids.concat(updated); - } - } else { - const updated = this.model.findOne(query, { fields: { _id: 1 } }); - if (updated) { - ids.push(updated); - } + find() { + try { + return this[this.origin].find(...arguments); + } catch (e) { + console.error('Exception on find', e, ...arguments); } - - const result = this.model.update(query, update, options); - const idQuery = { _id: { $in: _.pluck(ids, '_id') } }; - this.emit('update', idQuery, update); - this.emit('change', 'update', idQuery, update); - return result; } - upsert(query, update) { - this.setUpdatedAt(update, true, query); - - const id = this.model.findOne(query, { fields: { _id: 1 } }); - const result = this.model.upsert(...arguments); - - let record = id; - if (result.insertedId) { - record = { _id: result.insertedId }; + findOne() { + try { + return this[this.origin].findOne(...arguments); + } catch (e) { + console.error('Exception on find', e, ...arguments); } - - this.emit('update', record); - this.emit('change', 'update', record); - return result; } - remove(query) { - const records = this.model.find(query).fetch(); - - const ids = []; - for (const record of records) { - ids.push(record._id); - - record._deletedAt = new Date; - record.__collection__ = this.name; - - trash.upsert({_id: record._id}, _.omit(record, '_id')); - } + insert(/*record*/) { + return this._db.insert(...arguments); + } - query = { _id: { $in: ids } }; + update(/*query, update, options*/) { + return this._db.update(...arguments); + } - this.emit('remove', records); - this.emit('change', 'remove', records); - return this.model.remove(query); + upsert(/*query, update*/) { + return this._db.upsert(...arguments); } - insertOrUpsert(...args) { - if (args[0] && args[0]._id) { - const _id = args[0]._id; - delete args[0]._id; - args.unshift({ - _id: _id - }); + remove(/*query*/) { + return this._db.remove(...arguments); + } - this.upsert(...args); - return _id; - } else { - return this.insert(...args); - } + insertOrUpsert() { + return this._db.insertOrUpsert(...arguments); } allow() { - return this.model.allow(...arguments); + return this._db.allow(...arguments); } deny() { - return this.model.deny(...arguments); + return this._db.deny(...arguments); } ensureIndex() { - return this.model._ensureIndex(...arguments); + return this._db.ensureIndex(...arguments); } dropIndex() { - return this.model._dropIndex(...arguments); + return this._db.dropIndex(...arguments); } tryEnsureIndex() { - try { - return this.ensureIndex(...arguments); - } catch (e) { - console.log(e); - } + return this._db.tryEnsureIndex(...arguments); } tryDropIndex() { - try { - return this.dropIndex(...arguments); - } catch (e) { - console.log(e); - } + return this._db.tryDropIndex(...arguments); } - getChangedRecords(type, recordOrQuery, fields) { - if (type === 'insert') { - return [recordOrQuery]; - } - - if (type === 'remove') { - return recordOrQuery; - } - - if (type === 'update') { - const options = {}; - if (fields) { - options.fields = fields; - } - return this.find(recordOrQuery, options).fetch(); - } + trashFind(/*query, options*/) { + return this._db.trashFind(...arguments); } - trashFind(query, options) { - query.__collection__ = this.name; - - return trash.find(query, options); + trashFindDeletedAfter(/*deletedAt, query, options*/) { + return this._db.trashFindDeletedAfter(...arguments); } - trashFindDeletedAfter(deletedAt, query = {}, options) { - query.__collection__ = this.name; - query._deletedAt = { - $gt: deletedAt - }; + // dinamicTrashFindAfter(method, deletedAt, ...args) { + // const scope = { + // find: (query={}) => { + // return this.trashFindDeletedAfter(deletedAt, query, { fields: {_id: 1, _deletedAt: 1} }); + // } + // }; - return trash.find(query, options); - } + // scope.model = { + // find: scope.find + // }; - dinamicTrashFindAfter(method, deletedAt, ...args) { - const scope = { - find: (query={}) => { - return this.trashFindDeletedAfter(deletedAt, query, { fields: {_id: 1, _deletedAt: 1} }); - } - }; + // return this[method].apply(scope, args); + // } - scope.model = { - find: scope.find - }; + // dinamicFindAfter(method, updatedAt, ...args) { + // const scope = { + // find: (query={}, options) => { + // query._updatedAt = { + // $gt: updatedAt + // }; - return this[method].apply(scope, args); - } + // return this.find(query, options); + // } + // }; - dinamicFindAfter(method, updatedAt, ...args) { - const scope = { - find: (query={}, options) => { - query._updatedAt = { - $gt: updatedAt - }; + // scope.model = { + // find: scope.find + // }; - return this.find(query, options); - } - }; + // return this[method].apply(scope, args); + // } - scope.model = { - find: scope.find - }; - - return this[method].apply(scope, args); - } - - dinamicFindChangesAfter(method, updatedAt, ...args) { - return { - update: this.dinamicFindAfter(method, updatedAt, ...args).fetch(), - remove: this.dinamicTrashFindAfter(method, updatedAt, ...args).fetch() - }; - } + // dinamicFindChangesAfter(method, updatedAt, ...args) { + // return { + // update: this.dinamicFindAfter(method, updatedAt, ...args).fetch(), + // remove: this.dinamicTrashFindAfter(method, updatedAt, ...args).fetch() + // }; + // } } diff --git a/packages/rocketchat-lib/server/models/_BaseCache.js b/packages/rocketchat-lib/server/models/_BaseCache.js new file mode 100644 index 0000000000000000000000000000000000000000..0e2aaab6299755368d670c4d60270e69e744d8de --- /dev/null +++ b/packages/rocketchat-lib/server/models/_BaseCache.js @@ -0,0 +1,892 @@ +/* eslint new-cap: 0 */ + +import loki from 'lokijs'; +import {EventEmitter} from 'events'; +import objectPath from 'object-path'; + +const logger = new Logger('BaseCache'); + +const lokiEq = loki.LokiOps.$eq; + +loki.LokiOps.$eq = function(a, b) { + if (Array.isArray(a)) { + return loki.LokiOps.$containsAny(a, b); + } + return lokiEq(a, b); +}; + +loki.LokiOps.$in = loki.LokiOps.$containsAny; +loki.LokiOps.$nin = loki.LokiOps.$containsNone; + +loki.LokiOps.$exists = function(a, b) { + if (b) { + return loki.LokiOps.$ne(a, undefined); + } + + return loki.LokiOps.$eq(a, undefined); +}; + + +const ignore = [ + 'emit', + 'load', + 'on', + 'addToAllIndexes' +]; + +function traceMethodCalls(target) { + target._stats = {}; + + for (const property in target) { + if (typeof target[property] === 'function' && ignore.indexOf(property) === -1) { + target._stats[property] = { + calls: 0, + time: 0, + avg: 0 + }; + const origMethod = target[property]; + target[property] = function(...args) { + + if (target.loaded !== true) { + return origMethod.apply(target, args); + } + + const startTime = RocketChat.statsTracker.now(); + const result = origMethod.apply(target, args); + const time = Math.round(RocketChat.statsTracker.now() - startTime) / 1000; + target._stats[property].time += time; + target._stats[property].calls++; + target._stats[property].avg = target._stats[property].time / target._stats[property].calls; + + return result; + }; + } + } + + setInterval(function() { + for (const property in target._stats) { + if (target._stats.hasOwnProperty(property) && target._stats[property].time > 0) { + const tags = [`property:${property}`, `collection:${target.collectionName}`]; + RocketChat.statsTracker.timing('cache.methods.time', target._stats[property].avg, tags); + RocketChat.statsTracker.increment('cache.methods.totalTime', target._stats[property].time, tags); + RocketChat.statsTracker.increment('cache.methods.count', target._stats[property].calls, tags); + target._stats[property].avg = 0; + target._stats[property].time = 0; + target._stats[property].calls = 0; + } + } + }, 10000); + + target._getStatsAvg = function() { + const stats = []; + for (const property in target._stats) { + if (target._stats.hasOwnProperty(property)) { + stats.push([Math.round(target._stats[property].avg*100)/100, property]); + } + } + return _.sortBy(stats, function(record) { + return record[0]; + }); + }; +} + +class Adapter { + loadDatabase(/*dbname, callback*/) {} + saveDatabase(/*dbname, dbstring, callback*/) {} + deleteDatabase(/*dbname, callback*/) {} +} + +const db = new loki('rocket.chat.json', {adapter: Adapter}); + +class ModelsBaseCache extends EventEmitter { + constructor(model) { + super(); + + traceMethodCalls(this); + + this.indexes = {}; + this.ignoreUpdatedFields = ['_updatedAt']; + + this.query = {}; + this.options = {}; + + this.ensureIndex('_id', 'unique'); + + this.joins = {}; + + this.on('inserted', (...args) => { this.emit('changed', 'inserted', ...args); }); + this.on('removed', (...args) => { this.emit('changed', 'removed', ...args); }); + this.on('updated', (...args) => { this.emit('changed', 'updated', ...args); }); + + this.on('beforeinsert', (...args) => { this.emit('beforechange', 'inserted', ...args); }); + this.on('beforeremove', (...args) => { this.emit('beforechange', 'removed', ...args); }); + this.on('beforeupdate', (...args) => { this.emit('beforechange', 'updated', ...args); }); + + this.on('inserted', (...args) => { this.emit('sync', 'inserted', ...args); }); + this.on('updated', (...args) => { this.emit('sync', 'updated', ...args); }); + this.on('beforeremove', (...args) => { this.emit('sync', 'removed', ...args); }); + + this.db = db; + + this.model = model; + + this.collectionName = this.model._db.collectionName; + this.collection = this.db.addCollection(this.collectionName); + } + + hasOne(join, {field, link}) { + this.join({join, field, link, multi: false}); + } + + hasMany(join, {field, link}) { + this.join({join, field, link, multi: true}); + } + + join({join, field, link, multi}) { + if (!RocketChat.models[join]) { + console.log(`Invalid cache model ${join}`); + return; + } + + RocketChat.models[join].cache.on('inserted', (record) => { + this.processRemoteJoinInserted({join, field, link, multi, record: record}); + }); + + RocketChat.models[join].cache.on('beforeupdate', (record, diff) => { + if (diff[link.remote]) { + this.processRemoteJoinRemoved({join, field, link, multi, record: record}); + } + }); + + RocketChat.models[join].cache.on('updated', (record, diff) => { + if (diff[link.remote]) { + this.processRemoteJoinInserted({join, field, link, multi, record: record}); + } + }); + + RocketChat.models[join].cache.on('removed', (record) => { + this.processRemoteJoinRemoved({join, field, link, multi, record: record}); + }); + + this.on('inserted', (localRecord) => { + this.processLocalJoinInserted({join, field, link, multi, localRecord: localRecord}); + }); + + this.on('beforeupdate', (localRecord, diff) => { + if (diff[link.local]) { + if (multi === true) { + localRecord[field] = []; + } else { + localRecord[field] = undefined; + } + } + }); + + this.on('updated', (localRecord, diff) => { + if (diff[link.local]) { + this.processLocalJoinInserted({join, field, link, multi, localRecord: localRecord}); + } + }); + } + + processRemoteJoinInserted({field, link, multi, record}) { + let localRecords = this._findByIndex(link.local, objectPath.get(record, link.remote)); + + if (!localRecords) { + return; + } + + if (!Array.isArray(localRecords)) { + localRecords = [localRecords]; + } + + for (var i = 0; i < localRecords.length; i++) { + const localRecord = localRecords[i]; + if (multi === true && !localRecord[field]) { + localRecord[field] = []; + } + + if (typeof link.transform === 'function') { + record = link.transform(localRecord, record); + } + + if (multi === true) { + localRecord[field].push(record); + } else { + localRecord[field] = record; + } + + this.emit(`join:${field}:inserted`, localRecord, record); + this.emit(`join:${field}:changed`, 'inserted', localRecord, record); + } + } + + processLocalJoinInserted({join, field, link, multi, localRecord}) { + let records = RocketChat.models[join].cache._findByIndex(link.remote, objectPath.get(localRecord, link.local)); + + if (!Array.isArray(records)) { + records = [records]; + } + + for (let i = 0; i < records.length; i++) { + let record = records[i]; + + if (typeof link.transform === 'function') { + record = link.transform(localRecord, record); + } + + if (multi === true) { + localRecord[field].push(record); + } else { + localRecord[field] = record; + } + + this.emit(`join:${field}:inserted`, localRecord, record); + this.emit(`join:${field}:changed`, 'inserted', localRecord, record); + } + } + + processRemoteJoinRemoved({field, link, multi, record}) { + let localRecords = this._findByIndex(link.local, objectPath.get(record, link.remote)); + + if (!localRecords) { + return; + } + + if (!Array.isArray(localRecords)) { + localRecords = [localRecords]; + } + + for (var i = 0; i < localRecords.length; i++) { + const localRecord = localRecords[i]; + + if (multi === true) { + if (Array.isArray(localRecord[field])) { + if (typeof link.remove === 'function') { + link.remove(localRecord[field], record); + } else if (localRecord[field].indexOf(record) > -1) { + localRecord[field].splice(localRecord[field].indexOf(record), 1); + } + } + } else { + localRecord[field] = undefined; + } + + this.emit(`join:${field}:removed`, localRecord, record); + this.emit(`join:${field}:changed`, 'removed', localRecord, record); + } + } + + ensureIndex(fields, type='array') { + if (!Array.isArray(fields)) { + fields = [fields]; + } + + this.indexes[fields.join(',')] = { + type: type, + fields: fields, + data: {} + }; + } + + addToAllIndexes(record) { + for (const indexName in this.indexes) { + if (this.indexes.hasOwnProperty(indexName)) { + this.addToIndex(indexName, record); + } + } + } + + addToIndex(indexName, record) { + const index = this.indexes[indexName]; + if (!index) { + console.error(`Index not defined ${indexName}`); + return; + } + + const keys = []; + for (const field of index.fields) { + keys.push(objectPath.get(record, field)); + } + const key = keys.join('|'); + + if (index.type === 'unique') { + index.data[key] = record; + return; + } + + if (index.type === 'array') { + if (!index.data[key]) { + index.data[key] = []; + } + index.data[key].push(record); + return; + } + } + + removeFromAllIndexes(record) { + for (const indexName in this.indexes) { + if (this.indexes.hasOwnProperty(indexName)) { + this.removeFromIndex(indexName, record); + } + } + } + + removeFromIndex(indexName, record) { + const index = this.indexes[indexName]; + if (!this.indexes[indexName]) { + console.error(`Index not defined ${indexName}`); + return; + } + + if (!index.data) { + return; + } + + let key = []; + for (const field of index.fields) { + key.push(objectPath.get(record, field)); + } + key = key.join('|'); + + if (index.type === 'unique') { + index.data[key] = undefined; + return; + } + + if (index.type === 'array') { + if (!index.data[key]) { + return; + } + const i = index.data[key].indexOf(record); + if (i > -1) { + index.data[key].splice(i, 1); + } + return; + } + } + + _findByIndex(index, keys) { + const key = [].concat(keys).join('|'); + if (!this.indexes[index]) { + return; + } + + if (this.indexes[index].data) { + const result = this.indexes[index].data[key]; + if (result) { + return result; + } + } + + if (this.indexes[index].type === 'array') { + return []; + } + } + + findByIndex(index, keys, options={}) { + return { + fetch: () => { + return this.processQueryOptionsOnResult(this._findByIndex(index, keys), options); + }, + + count: () => { + const records = this.findByIndex(index, keys, options).fetch(); + if (Array.isArray(records)) { + return records.length; + } + return !records ? 0 : 1; + }, + + forEach: (fn) => { + const records = this.findByIndex(index, keys, options).fetch(); + if (Array.isArray(records)) { + return records.forEach(fn); + } + if (records) { + return fn(records); + } + } + }; + } + + load() { + if (this.model._useCache === false) { + return; + } + + console.log('Will load cache for', this.collectionName); + this.emit('beforeload'); + this.loaded = false; + const time = RocketChat.statsTracker.now(); + const data = this.model.db.find(this.query, this.options).fetch(); + for (let i=0; i < data.length; i++) { + this.insert(data[i]); + } + console.log(String(data.length), 'records load from', this.collectionName); + RocketChat.statsTracker.timing('cache.load', RocketChat.statsTracker.now() - time, [`collection:${this.collectionName}`]); + + this.startSync(); + this.loaded = true; + this.emit('afterload'); + } + + startSync() { + if (this.model._useCache === false) { + return; + } + + this.model._db.on('change', ({action, id, data/*, oplog*/}) => { + switch (action) { + case 'insert': + data._id = id; + this.insert(data); + break; + + case 'remove': + this.removeById(id); + break; + + case 'update:record': + this.updateDiffById(id, data); + break; + + case 'update:diff': + this.updateDiffById(id, data); + break; + + case 'update:query': + this.update(data.query, data.update, data.options); + break; + } + }); + } + + processQueryOptionsOnResult(result, options={}) { + if (result === undefined || result === null) { + return undefined; + } + + if (Array.isArray(result)) { + if (options.sort) { + result = result.sort((a, b) => { + let r = 0; + for (const field in options.sort) { + if (options.sort.hasOwnProperty(field)) { + const direction = options.sort[field]; + let valueA; + let valueB; + if (field.indexOf('.') > -1) { + valueA = objectPath.get(a, field); + valueB = objectPath.get(b, field); + } else { + valueA = a[field]; + valueB = b[field]; + } + if (valueA > valueB) { + r = direction; + break; + } + if (valueA < valueB) { + r = -direction; + break; + } + } + } + return r; + }); + } + + if (typeof options.skip === 'number') { + result.splice(0, options.skip); + } + + if (typeof options.limit === 'number' && options.limit !== 0) { + result.splice(options.limit); + } + } + + if (!options.fields) { + options.fields = {}; + } + + const fieldsToRemove = []; + const fieldsToGet = []; + + for (const field in options.fields) { + if (options.fields.hasOwnProperty(field)) { + if (options.fields[field] === 0) { + fieldsToRemove.push(field); + } else if (options.fields[field] === 1) { + fieldsToGet.push(field); + } + } + } + + if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) { + console.warn('Can\'t mix remove and get fields'); + fieldsToRemove.splice(0, fieldsToRemove.length); + } + + if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) { + fieldsToGet.push('_id'); + } + + let pickFields = (obj, fields) => { + let picked = {}; + fields.forEach((field) => { + if (field.indexOf('.') !== -1) { + objectPath.set(picked, field, objectPath.get(obj, field)); + } else { + picked[field] = obj[field]; + } + }); + return picked; + }; + + if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) { + if (Array.isArray(result)) { + result = result.map((record) => { + if (fieldsToRemove.length > 0) { + return _.omit(record, ...fieldsToRemove); + } + + if (fieldsToGet.length > 0) { + return pickFields(record, fieldsToGet); + } + }); + } else { + if (fieldsToRemove.length > 0) { + return _.omit(result, ...fieldsToRemove); + } + + if (fieldsToGet.length > 0) { + return pickFields(result, fieldsToGet); + } + } + } + + return result; + } + + processQuery(query) { + if (!query) { + return query; + } + + if (Match.test(query, String)) { + return { + _id: query + }; + } + + if (Object.keys(query).length > 1) { + const and = []; + for (const field in query) { + if (query.hasOwnProperty(field)) { + and.push({ + [field]: query[field] + }); + } + } + query = {$and: and}; + } + + for (const field in query) { + if (query.hasOwnProperty(field)) { + const value = query[field]; + if (value instanceof RegExp && field !== '$regex') { + query[field] = { + $regex: value + }; + } + + if (field === '$and' || field === '$or') { + query[field] = value.map((subValue) => { + return this.processQuery(subValue); + }); + } + + if (Match.test(value, Object) && Object.keys(value).length > 0) { + query[field] = this.processQuery(value); + } + } + } + + return query; + } + + find(query, options={}) { + return { + fetch: () => { + try { + query = this.processQuery(query); + return this.processQueryOptionsOnResult(this.collection.find(query), options); + } catch (e) { + console.error('Exception on cache find for', this.collectionName, ...arguments); + console.error(e.stack); + } + }, + + count: () => { + try { + query = this.processQuery(query); + const { limit, skip } = options; + return this.processQueryOptionsOnResult(this.collection.find(query), { limit, skip }).length; + } catch (e) { + console.error('Exception on cache find for', this.collectionName, ...arguments); + console.error(e.stack); + } + }, + + forEach: (fn) => { + return this.find(query, options).fetch().forEach(fn); + }, + + observe: (obj) => { + logger.debug(this.collectionName, 'Falling back observe to model with query:', query); + return this.model.db.find(...arguments).observe(obj); + }, + + observeChanges: (obj) => { + logger.debug(this.collectionName, 'Falling back observeChanges to model with query:', query); + return this.model.db.find(...arguments).observeChanges(obj); + }, + + _publishCursor: (cursor, sub, collection) => { + logger.debug(this.collectionName, 'Falling back _publishCursor to model with query:', query); + return this.model.db.find(...arguments)._publishCursor(cursor, sub, collection); + } + }; + } + + findOne(query, options) { + query = this.processQuery(query); + return this.processQueryOptionsOnResult(this.collection.findOne(query), options); + } + + findOneById(_id, options) { + return this.findByIndex('_id', _id, options).fetch(); + } + + findWhere(query, options) { + query = this.processQuery(query); + return this.processQueryOptionsOnResult(this.collection.findWhere(query), options); + } + + addDynamicView() { + return this.collection.addDynamicView(...arguments); + } + + getDynamicView() { + return this.collection.getDynamicView(...arguments); + } + + insert(record) { + if (Array.isArray(record)) { + for (const item of record) { + this.insert(item); + } + } else { + // TODO remove - ignore updates in room.usernames + if (this.collectionName === 'rocketchat_room' && record.usernames) { + delete record.usernames; + } + this.emit('beforeinsert', record); + this.addToAllIndexes(record); + this.collection.insert(record); + this.emit('inserted', record); + } + } + + updateDiffById(id, diff) { + // TODO remove - ignore updates in room.usernames + if (this.collectionName === 'rocketchat_room' && diff.usernames) { + delete diff.usernames; + } + + const record = this._findByIndex('_id', id); + if (!record) { + console.error('Cache.updateDiffById: No record', this.collectionName, id, diff); + return; + } + + const updatedFields = _.without(Object.keys(diff), ...this.ignoreUpdatedFields); + + if (updatedFields.length > 0) { + this.emit('beforeupdate', record, diff); + } + + for (let key in diff) { + if (diff.hasOwnProperty(key)) { + objectPath.set(record, key, diff[key]); + } + } + + this.collection.update(record); + + if (updatedFields.length > 0) { + this.emit('updated', record, diff); + } + } + + updateRecord(record, update) { + // TODO remove - ignore updates in room.usernames + if (this.collectionName === 'rocketchat_room' && (record.usernames || (record.$set && record.$set.usernames))) { + delete record.usernames; + if (record.$set && record.$set.usernames) { + delete record.$set.usernames; + } + } + + const topLevelFields = Object.keys(update).map(field => field.split('.')[0]); + const updatedFields = _.without(topLevelFields, ...this.ignoreUpdatedFields); + + if (updatedFields.length > 0) { + this.emit('beforeupdate', record, record); + } + + if (update.$set) { + _.each(update.$set, (value, field) => { + objectPath.set(record, field, value); + }); + } + + if (update.$unset) { + _.each(update.$unset, (value, field) => { + objectPath.del(record, field); + }); + } + + if (update.$min) { + _.each(update.$min, (value, field) => { + let curValue = objectPath.get(record, field); + if (curValue === undefined || value < curValue) { + objectPath.set(record, field, value); + } + }); + } + + if (update.$max) { + _.each(update.$max, (value, field) => { + let curValue = objectPath.get(record, field); + if (curValue === undefined || value > curValue) { + objectPath.set(record, field, value); + } + }); + } + + if (update.$inc) { + _.each(update.$inc, (value, field) => { + let curValue = objectPath.get(record, field); + if (curValue === undefined) { + curValue = value; + } else { + curValue += value; + } + objectPath.set(record, field, curValue); + }); + } + + if (update.$mul) { + _.each(update.$mul, (value, field) => { + let curValue = objectPath.get(record, field); + if (curValue === undefined) { + curValue = 0; + } else { + curValue *= value; + } + objectPath.set(record, field, curValue); + }); + } + + if (update.$rename) { + _.each(update.$rename, (value, field) => { + let curValue = objectPath.get(record, field); + if (curValue !== undefined) { + objectPath.set(record, value, curValue); + objectPath.del(record, field); + } + }); + } + + if (update.$pullAll) { + _.each(update.$pullAll, (value, field) => { + let curValue = objectPath.get(record, field); + if (Array.isArray(curValue)) { + curValue = _.difference(curValue, value); + objectPath.set(record, field, curValue); + } + }); + } + + if (update.$pop) { + _.each(update.$pop, (value, field) => { + let curValue = objectPath.get(record, field); + if (Array.isArray(curValue)) { + if (value === -1) { + curValue.shift(); + } else { + curValue.pop(); + } + objectPath.set(record, field, curValue); + } + }); + } + + if (update.$addToSet) { + _.each(update.$addToSet, (value, field) => { + let curValue = objectPath.get(record, field); + if (curValue === undefined) { + curValue = []; + } + if (Array.isArray(curValue)) { + const length = curValue.length; + + if (value && value.$each && Array.isArray(value.$each)) { + for (const valueItem of value.$each) { + if (curValue.indexOf(valueItem) === -1) { + curValue.push(valueItem); + } + } + } else if (curValue.indexOf(value) === -1) { + curValue.push(value); + } + + if (curValue.length > length) { + objectPath.set(record, field, curValue); + } + } + }); + } + + this.collection.update(record); + + if (updatedFields.length > 0) { + this.emit('updated', record, record); + } + } + + update(query, update, options = {}) { + let records = options.multi ? this.find(query).fetch() : this.findOne(query) || []; + if (!Array.isArray(records)) { + records = [records]; + } + + for (const record of records) { + this.updateRecord(record, update); + } + } + + removeById(id) { + const record = this._findByIndex('_id', id); + if (record) { + this.emit('beforeremove', record); + this.collection.removeWhere({_id: id}); + this.removeFromAllIndexes(record); + this.emit('removed', record); + } + } +} + +export default ModelsBaseCache; diff --git a/packages/rocketchat-lib/server/models/_BaseDb.js b/packages/rocketchat-lib/server/models/_BaseDb.js new file mode 100644 index 0000000000000000000000000000000000000000..a13c595032d3a3fb25e7875d2ad43c3ff8145153 --- /dev/null +++ b/packages/rocketchat-lib/server/models/_BaseDb.js @@ -0,0 +1,402 @@ +/* globals MongoInternals */ + +const baseName = 'rocketchat_'; +import {EventEmitter} from 'events'; + +const trash = new Mongo.Collection(baseName + '_trash'); +try { + trash._ensureIndex({ collection: 1 }); + trash._ensureIndex({ _deletedAt: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 30 }); +} catch (e) { + console.log(e); +} + +let isOplogAvailable = MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle && !!MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle.onOplogEntry; +let isOplogEnabled = isOplogAvailable; +RocketChat.settings.get('Force_Disable_OpLog_For_Cache', (key, value) => { + isOplogEnabled = isOplogAvailable && value === false; +}); + +class ModelsBaseDb extends EventEmitter { + constructor(model, baseModel) { + super(); + + if (Match.test(model, String)) { + this.name = model; + this.collectionName = this.baseName + this.name; + this.model = new Mongo.Collection(this.collectionName); + } else { + this.name = model._name; + this.collectionName = this.name; + this.model = model; + } + + this.baseModel = baseModel; + + this.wrapModel(); + + // When someone start listening for changes we start oplog if available + this.once('newListener', (event/*, listener*/) => { + if (event === 'change') { + if (isOplogEnabled) { + const query = { + collection: this.collectionName + }; + + MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle.onOplogEntry(query, this.processOplogRecord.bind(this)); + MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle._defineTooFarBehind(Number.MAX_SAFE_INTEGER); + } + } + }); + + this.tryEnsureIndex({ '_updatedAt': 1 }); + } + + get baseName() { + return baseName; + } + + setUpdatedAt(record = {}, checkQuery = false, query) { + if (checkQuery === true) { + if (!query || Object.keys(query).length === 0) { + throw new Meteor.Error('Models._Base: Empty query'); + } + } + + if (/(^|,)\$/.test(Object.keys(record).join(','))) { + record.$set = record.$set || {}; + record.$set._updatedAt = new Date; + } else { + record._updatedAt = new Date; + } + + return record; + } + + wrapModel() { + this.originals = { + insert: this.model.insert.bind(this.model), + update: this.model.update.bind(this.model), + remove: this.model.remove.bind(this.model) + }; + const self = this; + + this.model.insert = function() { + return self.insert(...arguments); + }; + + this.model.update = function() { + return self.update(...arguments); + }; + + this.model.remove = function() { + return self.remove(...arguments); + }; + } + + find() { + return this.model.find(...arguments); + } + + findOne() { + return this.model.findOne(...arguments); + } + + defineSyncStrategy(query, modifier, options) { + if (this.baseModel.useCache === false) { + return 'db'; + } + + if (options.upsert === true) { + return 'db'; + } + + // const dbModifiers = [ + // '$currentDate', + // '$bit', + // '$pull', + // '$pushAll', + // '$push', + // '$setOnInsert' + // ]; + + const cacheAllowedModifiers = [ + '$set', + '$unset', + '$min', + '$max', + '$inc', + '$mul', + '$rename', + '$pullAll', + '$pop', + '$addToSet' + ]; + + const notAllowedModifiers = Object.keys(modifier).filter(i => i.startsWith('$') && cacheAllowedModifiers.includes(i) === false); + + if (notAllowedModifiers.length > 0) { + return 'db'; + } + + const placeholderFields = Object.keys(query).filter(item => item.indexOf('$') > -1); + if (placeholderFields.length > 0) { + return 'db'; + } + + return 'cache'; + } + + processOplogRecord(action) { + if (isOplogEnabled === false) { + return; + } + + if (action.op.op === 'i') { + this.emit('change', { + action: 'insert', + id: action.op.o._id, + data: action.op.o, + oplog: true + }); + return; + } + + if (action.op.op === 'u') { + if (!action.op.o.$set && !action.op.o.$unset) { + this.emit('change', { + action: 'update:record', + id: action.id, + data: action.op.o, + oplog: true + }); + return; + } + + let diff = {}; + if (action.op.o.$set) { + for (let key in action.op.o.$set) { + if (action.op.o.$set.hasOwnProperty(key)) { + diff[key] = action.op.o.$set[key]; + } + } + } + + if (action.op.o.$unset) { + for (let key in action.op.o.$unset) { + if (action.op.o.$unset.hasOwnProperty(key)) { + diff[key] = undefined; + } + } + } + + this.emit('change', { + action: 'update:diff', + id: action.id, + data: diff, + oplog: true + }); + return; + } + + if (action.op.op === 'd') { + this.emit('change', { + action: 'remove', + id: action.id, + oplog: true + }); + return; + } + } + + insert(record) { + this.setUpdatedAt(record); + + const result = this.originals.insert(...arguments); + if (!isOplogEnabled && this.listenerCount('change') > 0) { + this.emit('change', { + action: 'insert', + id: result, + data: _.extend({}, record), + oplog: false + }); + } + + record._id = result; + + return result; + } + + update(query, update, options = {}) { + this.setUpdatedAt(update, true, query); + + let strategy = this.defineSyncStrategy(query, update, options); + let ids = []; + if (!isOplogEnabled && this.listenerCount('change') > 0 && strategy === 'db') { + const findOptions = {fields: {_id: 1}}; + let records = options.multi ? this.find(query, findOptions).fetch() : this.findOne(query, findOptions) || []; + if (!Array.isArray(records)) { + records = [records]; + } + + ids = records.map(item => item._id); + if (options.upsert !== true) { + query = { + _id: { + $in: ids + } + }; + } + } + + const result = this.originals.update(query, update, options); + + if (!isOplogEnabled && this.listenerCount('change') > 0) { + if (strategy === 'db') { + if (options.upsert === true) { + if (result.insertedId) { + this.emit('change', { + action: 'insert', + id: result.insertedId, + data: this.findOne({_id: result.insertedId}), + oplog: false + }); + return; + } + + query = { + _id: { + $in: ids + } + }; + } + + let records = options.multi ? this.find(query).fetch() : this.findOne(query) || []; + if (!Array.isArray(records)) { + records = [records]; + } + for (const record of records) { + this.emit('change', { + action: 'update:record', + id: record._id, + data: record, + oplog: false + }); + } + } else { + this.emit('change', { + action: 'update:query', + id: undefined, + data: { + query: query, + update: update, + options: options + }, + oplog: false + }); + } + } + return result; + } + + upsert(query, update, options = {}) { + options.upsert = true; + options._returnObject = true; + return this.update(query, update, options); + } + + remove(query) { + const records = this.model.find(query).fetch(); + + const ids = []; + for (const record of records) { + ids.push(record._id); + + record._deletedAt = new Date; + record.__collection__ = this.name; + + trash.upsert({_id: record._id}, _.omit(record, '_id')); + } + + query = { _id: { $in: ids } }; + + const result = this.originals.remove(query); + + if (!isOplogEnabled && this.listenerCount('change') > 0) { + for (const record of records) { + this.emit('change', { + action: 'remove', + id: record._id, + data: _.extend({}, record), + oplog: false + }); + } + } + + return result; + } + + insertOrUpsert(...args) { + if (args[0] && args[0]._id) { + const _id = args[0]._id; + delete args[0]._id; + args.unshift({ + _id: _id + }); + + this.upsert(...args); + return _id; + } else { + return this.insert(...args); + } + } + + allow() { + return this.model.allow(...arguments); + } + + deny() { + return this.model.deny(...arguments); + } + + ensureIndex() { + return this.model._ensureIndex(...arguments); + } + + dropIndex() { + return this.model._dropIndex(...arguments); + } + + tryEnsureIndex() { + try { + return this.ensureIndex(...arguments); + } catch (e) { + console.error('Error creating index:', this.name, '->', ...arguments, e); + } + } + + tryDropIndex() { + try { + return this.dropIndex(...arguments); + } catch (e) { + console.error('Error dropping index:', this.name, '->', ...arguments, e); + } + } + + trashFind(query, options) { + query.__collection__ = this.name; + + return trash.find(query, options); + } + + trashFindDeletedAfter(deletedAt, query = {}, options) { + query.__collection__ = this.name; + query._deletedAt = { + $gt: deletedAt + }; + + return trash.find(query, options); + } +} + +export default ModelsBaseDb; diff --git a/packages/rocketchat-lib/server/publications/settings.coffee b/packages/rocketchat-lib/server/publications/settings.coffee index 955b7844c9a7076c962f2bba8058d5d1e5cc4f0b..8532ae0d967954c8c22e7e3bee35060455cffb08 100644 --- a/packages/rocketchat-lib/server/publications/settings.coffee +++ b/packages/rocketchat-lib/server/publications/settings.coffee @@ -2,14 +2,18 @@ Meteor.methods 'public-settings/get': (updatedAt) -> this.unblock() + records = RocketChat.models.Settings.find().fetch().filter (record) -> + return record.hidden isnt true and record.public is true + if updatedAt instanceof Date result = - update: RocketChat.models.Settings.findNotHiddenPublicUpdatedAfter(updatedAt).fetch() + update: records.filter (record) -> + return record._updatedAt > updatedAt remove: RocketChat.models.Settings.trashFindDeletedAfter(updatedAt, {hidden: { $ne: true }, public: true}, {fields: {_id: 1, _deletedAt: 1}}).fetch() return result - return RocketChat.models.Settings.findNotHiddenPublic().fetch() + return records 'private-settings/get': (updatedAt) -> unless Meteor.userId() @@ -20,20 +24,24 @@ Meteor.methods if not RocketChat.authz.hasPermission Meteor.userId(), 'view-privileged-setting' return [] - if updatedAt instanceof Date - return RocketChat.models.Settings.dinamicFindChangesAfter('findNotHidden', updatedAt); + records = RocketChat.models.Settings.find().fetch().filter (record) -> + return record.hidden isnt true - return RocketChat.models.Settings.findNotHidden().fetch() + if updatedAt instanceof Date + return { + update: records.filter (record) -> + return record._updatedAt > updatedAt + remove: RocketChat.models.Settings.trashFindDeletedAfter(updatedAt, {hidden: { $ne: true }}, {fields: {_id: 1, _deletedAt: 1}}).fetch() + } + return records -RocketChat.models.Settings.on 'change', (type, args...) -> - records = RocketChat.models.Settings.getChangedRecords type, args[0] - for record in records - if record.public is true - RocketChat.Notifications.notifyAll 'public-settings-changed', type, _.pick(record, '_id', 'value') +RocketChat.models.Settings.cache.on 'changed', (type, setting) -> + if setting.public is true + RocketChat.Notifications.notifyAllInThisInstance 'public-settings-changed', type, _.pick(setting, '_id', 'value') - RocketChat.Notifications.notifyAll 'private-settings-changed', type, record + RocketChat.Notifications.notifyAllInThisInstance 'private-settings-changed', type, setting RocketChat.Notifications.streamAll.allowRead 'private-settings-changed', -> diff --git a/packages/rocketchat-lib/server/startup/cache/CacheLoad.js b/packages/rocketchat-lib/server/startup/cache/CacheLoad.js new file mode 100644 index 0000000000000000000000000000000000000000..ac56be599002e3eb3f8d3e778e9578d1ade0e60c --- /dev/null +++ b/packages/rocketchat-lib/server/startup/cache/CacheLoad.js @@ -0,0 +1,51 @@ +RocketChat.models.Rooms.cache.hasMany('Subscriptions', { + field: 'usernames', + link: { + local: '_id', + remote: 'rid', + transform(room, subscription) { + return subscription.u.username; + }, + remove(arr, subscription) { + if (arr.indexOf(subscription.u.username) > -1) { + arr.splice(arr.indexOf(subscription.u.username), 1); + } + } + } +}); + + +RocketChat.models.Subscriptions.cache.hasOne('Rooms', { + field: '_room', + link: { + local: 'rid', + remote: '_id' + } +}); + + +RocketChat.models.Subscriptions.cache.hasOne('Users', { + field: '_user', + link: { + local: 'u._id', + remote: '_id' + } +}); + + +RocketChat.models.Users.cache.load(); +RocketChat.models.Rooms.cache.load(); +RocketChat.models.Subscriptions.cache.load(); +RocketChat.models.Settings.cache.load(); + + +RocketChat.models.Users.cache.addDynamicView('highlights').applyFind({ + 'settings.preferences.highlights': {$size: {$gt: 0}} +}); + +RocketChat.models.Subscriptions.cache.addDynamicView('notifications').applyFind({ + $or: [ + {desktopNotifications: {$in: ['all', 'nothing']}}, + {mobilePushNotifications: {$in: ['all', 'nothing']}} + ] +}); diff --git a/packages/rocketchat-lib/server/startup/oAuthServicesUpdate.coffee b/packages/rocketchat-lib/server/startup/oAuthServicesUpdate.coffee index d59f2370fdd691fc566db9e3a0fe4572aa33c5c3..d5fb356dc4729a336e8c5a5f1b73ff3b16278689 100644 --- a/packages/rocketchat-lib/server/startup/oAuthServicesUpdate.coffee +++ b/packages/rocketchat-lib/server/startup/oAuthServicesUpdate.coffee @@ -8,7 +8,7 @@ OAuthServicesUpdate = -> Meteor.clearTimeout timer if timer? timer = Meteor.setTimeout -> - services = RocketChat.settings.get(/^(Accounts_OAuth_|Accounts_OAuth_Custom_)[a-z0-9_-]+$/i) + services = RocketChat.settings.get(/^(Accounts_OAuth_|Accounts_OAuth_Custom-)[a-z0-9_]+$/i) for service in services logger.oauth_updated service.key @@ -16,8 +16,8 @@ OAuthServicesUpdate = -> if serviceName is 'Meteor' serviceName = 'meteor-developer' - if /Accounts_OAuth_Custom_/.test service.key - serviceName = service.key.replace('Accounts_OAuth_Custom_', '') + if /Accounts_OAuth_Custom-/.test service.key + serviceName = service.key.replace('Accounts_OAuth_Custom-', '') if service.value is true data = @@ -25,18 +25,22 @@ OAuthServicesUpdate = -> secret: RocketChat.settings.get("#{service.key}_secret") - if /Accounts_OAuth_Custom_/.test service.key + if /Accounts_OAuth_Custom-/.test service.key data.custom = true - data.serverURL = RocketChat.settings.get("#{service.key}_url") - data.tokenPath = RocketChat.settings.get("#{service.key}_token_path") - data.identityPath = RocketChat.settings.get("#{service.key}_identity_path") - data.authorizePath = RocketChat.settings.get("#{service.key}_authorize_path") - data.scope = RocketChat.settings.get("#{service.key}_scope") - data.buttonLabelText = RocketChat.settings.get("#{service.key}_button_label_text") - data.buttonLabelColor = RocketChat.settings.get("#{service.key}_button_label_color") - data.loginStyle = RocketChat.settings.get("#{service.key}_login_style") - data.buttonColor = RocketChat.settings.get("#{service.key}_button_color") - data.tokenSentVia = RocketChat.settings.get("#{service.key}_token_sent_via") + data.clientId = RocketChat.settings.get("#{service.key}-id") + data.secret = RocketChat.settings.get("#{service.key}-secret") + data.serverURL = RocketChat.settings.get("#{service.key}-url") + data.tokenPath = RocketChat.settings.get("#{service.key}-token_path") + data.identityPath = RocketChat.settings.get("#{service.key}-identity_path") + data.authorizePath = RocketChat.settings.get("#{service.key}-authorize_path") + data.scope = RocketChat.settings.get("#{service.key}-scope") + data.buttonLabelText = RocketChat.settings.get("#{service.key}-button_label_text") + data.buttonLabelColor = RocketChat.settings.get("#{service.key}-button_label_color") + data.loginStyle = RocketChat.settings.get("#{service.key}-login_style") + data.buttonColor = RocketChat.settings.get("#{service.key}-button_color") + data.tokenSentVia = RocketChat.settings.get("#{service.key}-token_sent_via") + data.usernameField = RocketChat.settings.get("#{service.key}-username_field") + data.mergeUsers = RocketChat.settings.get("#{service.key}-merge_users") new CustomOAuth serviceName.toLowerCase(), serverURL: data.serverURL tokenPath: data.tokenPath @@ -45,6 +49,8 @@ OAuthServicesUpdate = -> scope: data.scope loginStyle: data.loginStyle tokenSentVia: data.tokenSentVia + usernameField: data.usernameField + mergeUsers: data.mergeUsers if serviceName is 'Facebook' data.appId = data.clientId @@ -60,13 +66,13 @@ OAuthServicesUpdate = -> OAuthServicesRemove = (_id) -> - serviceName = _id.replace('Accounts_OAuth_Custom_', '') + serviceName = _id.replace('Accounts_OAuth_Custom-', '') ServiceConfiguration.configurations.remove {service: serviceName.toLowerCase()} RocketChat.settings.get /^Accounts_OAuth_.+/, (key, value) -> OAuthServicesUpdate() -RocketChat.settings.get /^Accounts_OAuth_Custom.+/, (key, value) -> +RocketChat.settings.get /^Accounts_OAuth_Custom-[a-z0-9_]+/, (key, value) -> if not value OAuthServicesRemove key diff --git a/packages/rocketchat-lib/server/startup/settings.coffee b/packages/rocketchat-lib/server/startup/settings.coffee index b0044a5866701b736b2ea7e7e4ac858fe1ed6ae4..dae035b010e22b30439e6cc4ac2a0a6b1b93bfd5 100644 --- a/packages/rocketchat-lib/server/startup/settings.coffee +++ b/packages/rocketchat-lib/server/startup/settings.coffee @@ -41,6 +41,7 @@ RocketChat.settings.addGroup 'Accounts', -> @add 'Accounts_AvatarSize', 200, { type: 'int', enableQuery: {_id: 'Accounts_AvatarResize', value: true} } @add 'Accounts_AvatarStoreType', 'GridFS', { type: 'select', values: [ { key: 'GridFS', i18nLabel: 'GridFS' }, { key: 'FileSystem', i18nLabel: 'FileSystem' } ] } @add 'Accounts_AvatarStorePath', '', { type: 'string', enableQuery: {_id: 'Accounts_AvatarStoreType', value: 'FileSystem'} } + @add 'Accounts_SetDefaultAvatar', true, { type: 'boolean' } RocketChat.settings.addGroup 'OAuth', -> @@ -94,11 +95,11 @@ RocketChat.settings.addGroup 'General', -> @add 'Language', '', { type: 'language', public: true } @add 'Allow_Invalid_SelfSigned_Certs', false, { type: 'boolean' } @add 'Favorite_Rooms', true, { type: 'boolean', public: true } - @add 'CDN_PREFIX', '', { type: 'string' } + @add 'CDN_PREFIX', '', { type: 'string', public: true } @add 'Force_SSL', false, { type: 'boolean', public: true } @add 'GoogleTagManager_id', '', { type: 'string', public: true } - @add 'GoogleSiteVerification_id', '', { type: 'string', public: false } @add 'Bugsnag_api_key', '', { type: 'string', public: false } + @add 'Force_Disable_OpLog_For_Cache', false, { type: 'boolean', public: false } @add 'Restart', 'restart_server', { type: 'action', actionText: 'Restart_the_server' } @section 'UTF8', -> @@ -201,9 +202,10 @@ RocketChat.settings.addGroup 'Message', -> RocketChat.settings.addGroup 'Meta', -> @add 'Meta_language', '', { type: 'string' } @add 'Meta_fb_app_id', '', { type: 'string' } - @add 'Meta_robots', '', { type: 'string' } + @add 'Meta_robots', 'INDEX,FOLLOW', { type: 'string' } @add 'Meta_google-site-verification', '', { type: 'string' } @add 'Meta_msvalidate01', '', { type: 'string' } + @add 'Meta_custom', '', { type: 'code', code: 'text/html', multiline: true } RocketChat.settings.addGroup 'Push', -> diff --git a/packages/rocketchat-lib/startup/defaultRoomTypes.coffee b/packages/rocketchat-lib/startup/defaultRoomTypes.coffee index 05be736ce9b2b442178bf274b229cff588a76e07..1056e2afb76960df8bee1cb0be2a279a2a71d18b 100644 --- a/packages/rocketchat-lib/startup/defaultRoomTypes.coffee +++ b/packages/rocketchat-lib/startup/defaultRoomTypes.coffee @@ -38,16 +38,26 @@ RocketChat.roomTypes.add 'd', 20, RocketChat.TabBar.showGroup 'directmessage' link: (sub) -> return { username: sub.name } + findRoom: (identifier, user) -> query = t: 'd' - usernames: - $all: [identifier, user.username] - return ChatRoom.findOne(query) + name: identifier + + subscription = ChatSubscription.findOne(query) + if subscription?.rid + return ChatRoom.findOne(subscription.rid) + roomName: (roomData) -> return ChatSubscription.findOne({ rid: roomData._id }, { fields: { name: 1 } })?.name + condition: -> return RocketChat.authz.hasAtLeastOnePermission ['view-d-room', 'view-joined-room'] + getUserStatus: (roomId) -> + subscription = RocketChat.models.Subscriptions.findOne({rid: roomId}); + return if not subscription? + + return Session.get('user_' + subscription.name + '_status'); RocketChat.roomTypes.add 'p', 30, template: 'privateGroups' diff --git a/packages/rocketchat-livechat/app/.meteor/versions b/packages/rocketchat-livechat/app/.meteor/versions index a8d305f20bcf2977ecdd0d66cb74c1a63c5dfc1c..0f29e93176f00a88cd45f18668cb66bd206360dd 100644 --- a/packages/rocketchat-livechat/app/.meteor/versions +++ b/packages/rocketchat-livechat/app/.meteor/versions @@ -6,7 +6,7 @@ babel-compiler@6.13.0 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 -blaze@2.2.0 +blaze@2.2.1 blaze-tools@1.0.10 boilerplate-generator@1.0.11 caching-compiler@1.1.9 @@ -48,12 +48,12 @@ minifier-js@1.2.15 minimongo@1.0.19 mizzao:timesync@0.4.0 modules@0.7.7 -modules-runtime@0.7.7 -momentjs:moment@2.16.0 +modules-runtime@0.7.8 +momentjs:moment@2.17.1 mongo@1.1.14 mongo-id@1.0.6 npm-bcrypt@0.9.2 -npm-mongo@2.2.11_2 +npm-mongo@2.2.16_1 observe-sequence@1.0.14 ordered-dict@1.0.9 promise@0.8.8 diff --git a/packages/rocketchat-livechat/app/client/lib/_livechat.js b/packages/rocketchat-livechat/app/client/lib/_livechat.js index 46895402b43a8c0a9b29b63639d6ed68c0bd1988..0e84b0f7eaae5c2f9f607ec9efa180aba865ed6e 100644 --- a/packages/rocketchat-livechat/app/client/lib/_livechat.js +++ b/packages/rocketchat-livechat/app/client/lib/_livechat.js @@ -18,15 +18,35 @@ this.Livechat = new (class Livechat { this._offlineSuccessMessage = new ReactiveVar(TAPi18n.__('Thanks_We_ll_get_back_to_you_soon')); this._videoCall = new ReactiveVar(false); this._transcriptMessage = new ReactiveVar(''); - + this._connecting = new ReactiveVar(false); this._room = new ReactiveVar(null); + this._department = new ReactiveVar(null); + this._widgetOpened = new ReactiveVar(false); + this._ready = new ReactiveVar(false); + this._agent = new ReactiveVar(); + + this.stream = new Meteor.Streamer('livechat-room'); - Tracker.autorun((c) => { + Tracker.autorun(() => { if (this._room.get() && Meteor.userId()) { RoomHistoryManager.getMoreIfIsEmpty(this._room.get()); visitor.subscribeToRoom(this._room.get()); visitor.setRoom(this._room.get()); - c.stop(); + + Meteor.call('livechat:getAgentData', this._room.get(), (error, result) => { + if (!error) { + this._agent.set(result); + } + }); + this.stream.on(this._room.get(), (eventData) => { + if (!eventData || !eventData.type) { + return; + } + + if (eventData.type === 'agentData') { + this._agent.set(eventData.data); + } + }); } }); } @@ -70,6 +90,15 @@ this.Livechat = new (class Livechat { get transcriptMessage() { return this._transcriptMessage.get(); } + get department() { + return this._department.get(); + } + get connecting() { + return this._connecting.get(); + } + get agent() { + return this._agent.get(); + } set online(value) { this._online.set(value); @@ -92,7 +121,6 @@ this.Livechat = new (class Livechat { set offlineSuccessMessage(value) { this._offlineSuccessMessage.set(value); } - set customColor(value) { this._customColor.set(value); } @@ -102,7 +130,6 @@ this.Livechat = new (class Livechat { set offlineColor(value) { this._offlineColor.set(value); } - set customFontColor(value) { this._customFontColor.set(value); } @@ -118,8 +145,40 @@ this.Livechat = new (class Livechat { set transcriptMessage(value) { this._transcriptMessage.set(value); } - + set connecting(value) { + this._connecting.set(value); + } set room(roomId) { this._room.set(roomId); } + set department(departmentId) { + const dept = Department.findOne({ _id: departmentId }) || Department.findOne({ name: departmentId }); + + if (dept) { + this._department.set(dept._id); + } + } + set agent(agentData) { + this._agent.set(agentData); + } + + ready() { + this._ready.set(true); + } + + isReady() { + return this._ready.get(); + } + + setWidgetOpened() { + return this._widgetOpened.set(true); + } + + setWidgetClosed() { + return this._widgetOpened.set(false); + } + + isWidgetOpened() { + return this._widgetOpened.get(); + } })(); diff --git a/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee b/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee index f63f91e5ce26744a49ba0228da72dd152de4c101..4b6eecccf0e3524d5babf5f4641c4be0162d03bd 100644 --- a/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee +++ b/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee @@ -76,7 +76,12 @@ class @ChatMessages rid ?= visitor.getRoom(true) sendMessage = (callback) -> - msgObject = { _id: Random.id(), rid: rid, msg: msg, token: visitor.getToken() } + msgObject = { + _id: Random.id(), + rid: rid, + msg: msg, + token: visitor.getToken() + } MsgTyping.stop(rid) Meteor.call 'sendMessageLivechat', msgObject, (error, result) -> @@ -84,12 +89,20 @@ class @ChatMessages ChatMessage.update msgObject._id, { $set: { error: true } } showError error.reason - if result.rid? and not visitor.isSubscribed(result.rid) + if result?.rid? and not visitor.isSubscribed(result.rid) + Livechat.connecting = result.showConnecting ChatMessage.update result._id, _.omit(result, '_id') Livechat.room = result.rid if not Meteor.userId() - Meteor.call 'livechat:registerGuest', { token: visitor.getToken() }, (error, result) -> + guest = { + token: visitor.getToken() + } + + if Livechat.department + guest.department = Livechat.department + + Meteor.call 'livechat:registerGuest', guest, (error, result) -> if error? return showError error.reason diff --git a/packages/rocketchat-livechat/app/client/lib/commands.js b/packages/rocketchat-livechat/app/client/lib/commands.js index 8c8f60bbbddf8d979ed65e626b7e27393ed698ca..52d9b8801231eeb7ebff741f11d6871fa94fbdd7 100644 --- a/packages/rocketchat-livechat/app/client/lib/commands.js +++ b/packages/rocketchat-livechat/app/client/lib/commands.js @@ -14,7 +14,7 @@ this.Commands = { promptTranscript: function() { if (Livechat.transcript) { const user = Meteor.user(); - let email = user.emails && user.emails.length > 0 ? user.emails[0].address : ''; + let email = user.visitorEmails && user.visitorEmails.length > 0 ? user.visitorEmails[0].address : ''; swal({ title: t('Chat_ended'), @@ -60,5 +60,9 @@ this.Commands = { showConfirmButton: false }); } + }, + + connected: function() { + Livechat.connecting = false; } }; diff --git a/packages/rocketchat-livechat/app/client/lib/hooks.js b/packages/rocketchat-livechat/app/client/lib/hooks.js index 4a8fa387a3f93b50984d2ae0e49d1a35e4c38f72..2739bed6f9fe4f4c6bb1326d6849e37a2d4d51e8 100644 --- a/packages/rocketchat-livechat/app/client/lib/hooks.js +++ b/packages/rocketchat-livechat/app/client/lib/hooks.js @@ -19,6 +19,22 @@ var api = { if (theme.fontColor) { Livechat.customFontColor = theme.fontColor; } + }, + + setDepartment: function(department) { + Livechat.department = department; + }, + + clearDepartment: function() { + Livechat.department = null; + }, + + widgetOpened: function() { + Livechat.setWidgetOpened(); + }, + + widgetClosed: function() { + Livechat.setWidgetClosed(); } }; @@ -33,5 +49,10 @@ window.addEventListener('message', function(msg) { // tell parent window that we are ready Meteor.startup(function() { - parentCall('ready'); + Tracker.autorun((c) => { + if (Livechat.isReady()) { + parentCall('ready'); + c.stop(); + } + }); }); diff --git a/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee b/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee index 45c68aeb73e0ca50e455f2b50d564f4a62b633af..ac6bd5b72269e1b54a7447411c6a219795eb1343 100644 --- a/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee +++ b/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee @@ -28,7 +28,7 @@ dep.changed() Tracker.autorun -> - if visitor.getRoom() + if visitor.getRoom() and Meteor.userId() addStream visitor.getRoom() start = (room) -> diff --git a/packages/rocketchat-livechat/app/client/stylesheets/main.less b/packages/rocketchat-livechat/app/client/stylesheets/main.less index 3f567615d832b4db59955e66b55165c1da11205b..a92b5a0a4e029167563bd60290d9d4ea20e80fae 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/main.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/main.less @@ -1,16 +1,20 @@ -@import "_variables.less"; -@import "utils/_lesshat.import.less"; @import "utils/_reset.import.less"; +@import "utils/_lesshat.import.less"; +@import "utils/_variables.import.less"; @import "utils/_keyframes.import.less"; +@import "utils/_loading.import.less"; * { box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; } -html, body { + +html, +body { height: 100%; } + body { margin: 0; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif, "Meiryo UI"; @@ -23,8 +27,6 @@ body { padding: 0; overflow: hidden; position: relative; - // background-color: @primary-background-color; - border-top-right-radius: 5px; border-top-left-radius: 5px; } @@ -37,7 +39,7 @@ textarea { line-height: inherit; padding: 5px; margin: 5px 0; - border: 1px solid #E7E7E7; + border: 1px solid #e7e7e7; border-radius: 5px; outline: none; } @@ -45,7 +47,7 @@ textarea { button { background: none; border: none; - padding: 0px; + padding: 0; text-align: left; cursor: pointer; text-transform: inherit; @@ -72,14 +74,16 @@ input:focus { border-radius: 0; line-height: 16px; position: relative; - cursor: pointer;background-color: #FFF; + cursor: pointer; color: rgba(255, 255, 255, 0.85); background-color: lighten(desaturate(@primary-background-color, 15%), 12.5%); + span { position: relative; z-index: 2; } - &:before { + + &::before { background-color: rgba(0, 0, 0, 0.1); content: " "; position: absolute; @@ -89,29 +93,36 @@ input:focus { height: 100%; opacity: 0; z-index: 1; - .transition(opacity .1s ease-out); + transition: opacity 0.1s ease-out; } + &:hover { text-decoration: none; - color: #FFF; - &:before { + color: #ffffff; + + &::before { opacity: 1; } } + &.secondary { background-color: @tertiary-background-color; color: @primary-font-color; - &:before { + + &::before { background-color: rgba(0, 0, 0, 0.045); } } + &.clean { font-size: 14px; box-shadow: 0 0 3px rgba(0, 0, 0, 0.08); + &.primary { font-weight: 600; } } + &.button-block { display: block; width: 100%; @@ -126,53 +137,101 @@ input:focus { display: flex; flex-direction: column; height: 100%; - border-top-right-radius: inherit; border-top-left-radius: inherit; + &.popout { + border-top-right-radius: 0; + border-top-left-radius: 0; + + .title { + cursor: default; + } + } + .title { flex: 1 0 @header-min-height; - line-height: @header-min-height; - border-top-right-radius: inherit; border-top-left-radius: inherit; - color: #FFF; + color: #ffffff; z-index: 10; cursor: pointer; + padding: 0 10px; + h1 { margin: 0; - padding: 0 5px; - font-size: 9pt; + font-size: 10pt; display: inline-block; } .toolbar { display: inline-block; float: right; - padding-right: 5px; svg { + cursor: pointer; fill: currentColor; width: 14px; - margin: 0 2px; + margin: 0 5px; vertical-align: middle; + + &:last-of-type { + margin-right: 0; + } } } } + + .header { + flex: 1 0 60px; + display: flex; + box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.2); + background-color: #fcfcfc; + color: @secondary-font-color; + z-index: 2; + + .picture { + flex: 0 1 60px; + padding: 5px 10px; + + img { + width: 50px; + height: 50px; + border-radius: 6px; + border: 1px solid @window-border-color; + } + } + + .info { + flex: 1; + padding: 5px 0; + + h2 { + color: @primary-font-color; + font-size: 14px; + } + + li { + font-size: 11px; + } + } + } + .messages { flex: 1 1 100%; - - background-color: #FFF; + background-color: #ffffff; border-left: 1px solid @window-border-color; border-right: 1px solid @window-border-color; overflow-y: auto; + .wrapper { padding-bottom: 6px; ul { list-style-type: none; padding: 0; + li { padding: 0; } @@ -180,19 +239,48 @@ input:focus { .message { font-size: 12px; - padding-left: 40px; + padding: 8px 10px 0; position: relative; line-height: 18px; - margin: 12px 10px 0; min-height: 36px; + + &::after { + content: ''; + display: block; + clear: both; + } + + .content { + width: 75%; + background-color: #f3f3f3; + border: 1px solid desaturate(darken(#f3f3f3, 10%), 40%); + margin-left: 38px; + border-radius: 6px; + padding: 5px; + float: left; + + &::before { + border-style: solid; + border-color: transparent desaturate(darken(#f3f3f3, 10%), 40%) transparent transparent; + content: " "; + height: 0; + width: 0; + font-size: 0; + pointer-events: none; + border-width: 5px; + position: absolute; + left: 38px; + } + } + &:nth-child(1) { margin-top: 0; } + &.new-day { margin-top: 60px; - } - &.new-day { - &:before { + + &::before { content: attr(data-date); display: block; position: absolute; @@ -203,124 +291,150 @@ input:focus { text-align: center; .calc(left, ~'50% - 70px'); color: @secondary-font-color; - z-index: 10; + z-index: 1; padding: 0 10px; - background-color: #FFF; + background-color: #ffffff; min-width: 120px; } - &:after { - content: " "; - display: block; - position: absolute; - top: -20px; - left: 0; - width: 100%; - border-top: 1px solid #ddd; + + .content { + &::after { + content: " "; + display: block; + position: absolute; + top: -20px; + left: 0; + width: 100%; + border-top: 1px solid #dddddd; + } } } + .edit-message { display: none; cursor: pointer; } - &.own:hover:not(.system) .edit-message { - display: inline-block; + + &.own { + .content { + background-color: #feffd7; + border: 1px solid desaturate(darken(#feffd7, 10%), 40%); + float: right; + margin-right: 3px; + + &::before { + border-style: solid; + border-color: transparent transparent transparent desaturate(darken(#feffd7, 10%), 40%); + content: " "; + height: 0; + width: 0; + font-size: 0; + pointer-events: none; + border-width: 5px; + position: absolute; + right: 3px; + left: inherit; + } + } } + .delete-message { display: none; cursor: pointer; } - &.own:hover:not(.system) .delete-message { - display: inline-block; - } + .user { display: inline-block; font-weight: 600; color: #444444; margin-right: 5px; outline: none; + &:hover { - color: #333; + color: #333333; } } + .thumb { position: absolute; - left: 0; - top: 0; + left: 10px; + top: 6px; display: block; width: 30px; height: 30px; } + .info { - font-size: 10px; color: @info-font-color; + display: inline-block; + float: right; + margin: 9px -1px -5px 9px; + font-size: 9px; + text-align: right; + left: -10px; + width: 55px; + + .edited { + display: inline-block; + } + + .edit-message { + float: left; + margin-left: 1px; + } + + .delete-message { + float: left; + } } + &.sequential { - // margin-top: 5px; - padding-top: 5px; - margin-top: 0; - margin-bottom: 0; + padding-top: 2px; min-height: 20px; + .user { display: none; } + .thumb { display: none; } - .info { - position: absolute; - text-align: right; - left: -20px; - width: 55px; - .time { - display: none; - } - .edited { - display: inline-block; - } - .edit-message { - float: left; - margin-left: 1px; - } - .delete-message { - float: left; - } - } - &:hover { - .time { - display: inline-block; - } - .edited { - display: none; - } - } } + &.system { .body { color: @info-font-color; font-style: italic; + em { font-weight: 600; } } } + .avatar-initials { line-height: 40px; } + a { color: @link-font-color; font-weight: 400; + &:hover { color: darken(@link-font-color, 10%); text-decoration: underline; } } + .body { opacity: 1; - .transition(opacity 1s linear); + transition: opacity 1s linear; } + &.temp .body { - opacity: .5; + opacity: 0.5; } + &.msg-error .body { text-decoration: line-through; } @@ -340,6 +454,7 @@ input:focus { } } } + .new-message { margin: 0 -65px; position: absolute; @@ -348,15 +463,16 @@ input:focus { width: 130px; height: 30px; text-align: center; - color: #FFF; + color: #ffffff; line-height: 30px; font-size: 0.8em; cursor: pointer; bottom: 8px; left: 50%; z-index: 5; - .transition(transform 0.3s ease-out); + transition: transform 0.3s ease-out; .transform(translateY(-40px)); + &.not { .transform(translateY(100%)); } @@ -366,11 +482,10 @@ input:focus { bottom: @footer-min-height; position: fixed; width: 100%; - background-color: #F7D799; + background-color: #f7d799; padding: 5px; z-index: 8; - - .transition(transform 0.2s ease-out); + transition: transform 0.2s ease-out; .transform(translateY(100%)); &.show { @@ -378,13 +493,12 @@ input:focus { } } } + .footer { flex: 1 0 @footer-min-height; - z-index: 10; - - background-color: #FCFCFC; - border-top: 1px solid #E7E7E7; + background-color: #fcfcfc; + border-top: 1px solid @window-border-color; border-left: 1px solid @window-border-color; border-right: 1px solid @window-border-color; @@ -410,7 +524,7 @@ input:focus { -webkit-appearance: none; height: 28px; line-height: normal; - background-color: #fff; + background-color: #ffffff; position: relative; } } @@ -418,7 +532,6 @@ input:focus { .buttons { color: @secondary-font-color; fill: @secondary-font-color; - display: flex; align-items: center; padding: 0 5px; @@ -428,7 +541,7 @@ input:focus { height: 15px; margin: 0 4px; cursor: pointer; - .transition(fill .15s ease-out); + transition: fill 0.15s ease-out; &:hover { fill: @primary-font-color; @@ -436,6 +549,7 @@ input:focus { } } } + .toggle-options { clear: both; color: @secondary-font-color; @@ -445,22 +559,29 @@ input:focus { font-size: 0.65rem; } + .typing { + clear: both; + color: @secondary-font-color; + margin-left: 8px; + outline: none; + margin-top: 2px; + font-size: 0.65rem; + } + .options-menu { min-width: 100px; - // min-height: 40px; bottom: 21px; left: 6px; border-radius: 2px; padding: 6px 0; - background-color: #fff; + background-color: #ffffff; color: @secondary-font-color; - - box-shadow: 0px 1px 1px 0 rgba(0,0,0,0.2), 0 2px 10px 0 rgba(0,0,0,.16); + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); position: absolute; z-index: 200; - - .transition(transform 0.15s ease, visibility 0.15s ease, opacity 0.15s ease); - + transition: transform 0.15s ease, visibility 0.15s ease, opacity 0.15s ease; .transform(translateY(30px)); opacity: 0; visibility: hidden; @@ -475,9 +596,11 @@ input:focus { ul { li { padding: 0 13px 0 8px; + &:hover { - background-color: #EEE; + background-color: #eeeeee; } + button { display: block; padding: 4px 2px; @@ -487,6 +610,7 @@ input:focus { } } } + .offline { flex: 1 1 100%; background-color: white; @@ -503,7 +627,8 @@ input:focus { } form { - input, textarea { + input, + textarea { display: block; width: 100%; } @@ -514,7 +639,7 @@ input:focus { .error { display: none; - background-color: #F7D799; + background-color: #f7d799; padding: 5px; &.show { @@ -528,28 +653,24 @@ input:focus { .livechat-form { flex: 1 1 100%; display: block; - background-color: #FFF; + background-color: #ffffff; border-left: 1px solid @window-border-color; border-right: 1px solid @window-border-color; padding: 5px; - input, select { + input, + select { display: block; - background-color: #FFF; + background-color: #ffffff; } .error { display: none; - // width: 100%; - background-color: #F7D799; + background-color: #f7d799; padding: 5px; - // .transition(transform 0.3s ease-out); - // .transform(translateY(-100%)); - &.show { display: block; - // .transform(translateY(0px)); } } @@ -557,7 +678,8 @@ input:focus { padding: 0 1em; text-align: center; - input, select { + input, + select { width: 100%; } } @@ -570,7 +692,7 @@ input:focus { .overlay { border-top-right-radius: inherit; border-top-left-radius: inherit; - background-color: rgba(0,0,0,0.5); + background-color: rgba(0, 0, 0, 0.5); position: fixed; height: 100%; width: 100%; @@ -596,7 +718,6 @@ input:focus { padding: 0 15px; border-bottom: 1px solid rgba(0, 0, 0, 0.1); line-height: 40px; - white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -644,12 +765,11 @@ input:focus { text-align: right; font-size: 0.65rem; height: 20px; - color: #666; + color: #666666; padding: 0 1em; opacity: 0.5; align-self: flex-end; - - .transition(opacity .15s ease-out); + transition: opacity 0.15s ease-out; &:hover { opacity: 1; @@ -658,6 +778,7 @@ input:focus { a { text-decoration: none; margin-left: 1px; + img { height: 14px; vertical-align: middle; @@ -676,7 +797,7 @@ input:focus { bottom: 0; left: 0; right: 0; - background-color: #000; + background-color: #000000; z-index: 11; .video-overlay { @@ -695,8 +816,7 @@ input:focus { opacity: 0; visibility: hidden; .transform(translateY(50px)); - - .transition(opacity 0.175s ease-out, transform 0.175s ease-out, visibility 0.175s ease-out); + transition: opacity 0.175s ease-out, transform 0.175s ease-out, visibility 0.175s ease-out; &.visible { opacity: 1; @@ -731,12 +851,15 @@ input:focus { .title { height: 100%; } + .footer { display: none; } + .messages { display: none; } + .powered-by { display: none; } diff --git a/packages/rocketchat-livechat/app/client/stylesheets/utils/_keyframes.import.less b/packages/rocketchat-livechat/app/client/stylesheets/utils/_keyframes.import.less index cf3f1ab05356d32b4b96ff8af322d199c3e8a26e..8aa3df96f1da34e7f83d2bb7a7cc7c92b7a75e19 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/utils/_keyframes.import.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/utils/_keyframes.import.less @@ -1,30 +1,78 @@ // keyframes + @-webkit-keyframes fadeIn { - 0% { opacity: 0; visibility: hidden; } - 1% { opacity: 0; visibility: visible; } - 100% { opacity: 1; visibility: visible; } + 0% { + opacity: 0; + visibility: hidden; + } + + 1% { + opacity: 0; + visibility: visible; + } + + 100% { + opacity: 1; + visibility: visible; + } } + @keyframes fadeIn { - 0% { opacity: 0; visibility: hidden; } - 1% { opacity: 0; visibility: visible; } - 100% { opacity: 1; visibility: visible; } + 0% { + opacity: 0; + visibility: hidden; + } + + 1% { + opacity: 0; + visibility: visible; + } + + 100% { + opacity: 1; + visibility: visible; + } } @-webkit-keyframes fadeOut { - 0% { opacity: 1; visibility: visible; } - 99% { opacity: 0; visibility: visible; } - 100% { opacity: 0; visibility: hidden; } + 0% { + opacity: 1; + visibility: visible; + } + + 99% { + opacity: 0; + visibility: visible; + } + + 100% { + opacity: 0; + visibility: hidden; + } } + @keyframes fadeOut { - 0% { opacity: 1; visibility: visible; } - 99% { opacity: 0; visibility: visible; } - 100% { opacity: 0; visibility: hidden; } + 0% { + opacity: 1; + visibility: visible; + } + + 99% { + opacity: 0; + visibility: visible; + } + + 100% { + opacity: 0; + visibility: hidden; + } } @-webkit-keyframes highlight { 0% { background: #ffff99; } + 100% { background: none; } @@ -34,6 +82,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -43,6 +92,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -52,6 +102,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -62,11 +113,13 @@ opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; .transform(translateY(-150px)); } + 100% { opacity: 1; visibility: visible; @@ -79,11 +132,13 @@ opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; -webkit-transform: translateY(-150px); } + 100% { opacity: 1; visibility: visible; @@ -96,11 +151,13 @@ opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; .transform(translateY(150px)); } + 100% { opacity: 0; visibility: hidden; @@ -112,13 +169,15 @@ opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; -webkit-transform: translateY(150px); } + 100% { opacity: 0; visibility: hidden; } -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less b/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less index 1ce1fc07fded5e103b603921e82f78964e9790f5..0979dd5275e4c8ca63c69433f10d0dcaa5aac34f 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less @@ -2,123 +2,6 @@ // // version: v4.1.0 (2016-07-19) -// TABLE OF MIXINS: - // align-content - // align-items - // align-self - // animation - // animation-delay - // animation-direction - // animation-duration - // animation-fill-mode - // animation-iteration-count - // animation-name - // animation-play-state - // animation-timing-function - // appearance - // backface-visibility - // background-clip - // background-image - // background-origin - // background-size - // blur - // border-bottom-left-radius - // border-bottom-right-radius - // border-image - // border-radius - // border-top-left-radius - // border-top-right-radius - // box-shadow - // box-sizing - // brightness - // calc - // column-count - // column-gap - // column-rule - // column-width - // columns - // contrast - // display - // drop-shadow - // filter - // flex - // flex-basis - // flex-direction - // flex-grow - // flex-shrink - // flex-wrap - // font-face - // grayscale - // hue-rotate - // hyphens - // invert - // justify-content - // keyframes - // opacity - // order - // perspective - // perspective-origin - // placeholder - // rotate - // rotate3d - // rotateX - // rotateY - // rotateZ - // saturate - // scale - // scale3d - // scaleX - // scaleY - // scaleZ - // selection - // sepia - // size - // skew - // skewX - // skewY - // transform - // transform-origin - // transform-style - // transition - // transition-delay - // transition-duration - // transition-property - // transition-timing-function - // translate - // translate3d - // translateX - // translateY - // translateZ - // user-select - -.align-content(...) { - @process: ~`(function(t){return t=t||"stretch"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(e){return e=e||"stretch","flex-start"==e?e="start":"flex-end"==e?e="end":"space-between"==e?e="justify":"space-around"==e&&(e="distribute"),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-align-content: @process; - -ms-flex-line-pack: @process_ms; - align-content: @process; -} - -.align-items(...) { - @process_olderwebkit: ~`(function(t){return t=t||"stretch","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(t){return t=t||"stretch","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){return t=t||"stretch"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(t){return t=t||"stretch","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-align: @process_olderwebkit; - -moz-box-align: @process_moz; - -webkit-align-items: @process; - -ms-flex-align: @process_ms; - align-items: @process; -} - -.align-self(...) { - @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(t){return t=t||"auto","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-align-self: @process; - -ms-flex-item-align: @process_ms; - align-self: @process; -} - .animation(...) { @process: ~`(function(e){return e=e||"none",/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; -webkit-animation: @process; @@ -127,178 +10,6 @@ animation: @process; } -.animation-delay(...) { - @process: ~`(function(r){r=r||"0";var s=/(?:\d)(?:ms|s)/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-delay: @process; - -moz-animation-delay: @process; - -o-animation-delay: @process; - animation-delay: @process; -} - -.animation-direction(...) { - @process: ~`(function(n){return n||"normal"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-direction: @process; - -moz-animation-direction: @process; - -o-animation-direction: @process; - animation-direction: @process; -} - -.animation-duration(...) { - @process: ~`(function(r){r=r||"0";var s=/ms|s/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-duration: @process; - -moz-animation-duration: @process; - -o-animation-duration: @process; - animation-duration: @process; -} - -.animation-fill-mode(...) { - @process: ~`(function(n){return n||"none"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-fill-mode: @process; - -moz-animation-fill-mode: @process; - -o-animation-fill-mode: @process; - animation-fill-mode: @process; -} - -.animation-iteration-count(...) { - @process: ~`(function(n){return n||"0"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-iteration-count: @process; - -moz-animation-iteration-count: @process; - -o-animation-iteration-count: @process; - animation-iteration-count: @process; -} - -.animation-name(...) { - @process: ~`(function(n){return n||"none"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-name: @process; - -moz-animation-name: @process; - -o-animation-name: @process; - animation-name: @process; -} - -.animation-play-state(...) { - @process: ~`(function(n){return n||"running"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-play-state: @process; - -moz-animation-play-state: @process; - -o-animation-play-state: @process; - animation-play-state: @process; -} - -.animation-timing-function(...) { - @process: ~`(function(e){return e||"ease"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-timing-function: @process; - -moz-animation-timing-function: @process; - -o-animation-timing-function: @process; - animation-timing-function: @process; -} - -.appearance(...) { - @process: ~`(function(n){return n||"none"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-appearance: @process; - -moz-appearance: @process; - appearance: @process; -} - -.backface-visibility(...) { - @process: ~`(function(i){return i||"visible"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-backface-visibility: @process; - -moz-backface-visibility: @process; - -ms-backface-visibility: @process; - -o-backface-visibility: @process; - backface-visibility: @process; -} - -.background-clip(...) { - @process: ~`(function(r){return r||"border-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-background-clip: @process; - -moz-background-clip: @process; - background-clip: @process; -} - -.background-image(...) { - @process_ms: ~`(function(t){function e(t){var e,r,s,a,n,i,o,c,g="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",d=0,l=0,f="",h=[];if(!t)return t;do e=t.charCodeAt(d++),r=t.charCodeAt(d++),s=t.charCodeAt(d++),c=e<<16|r<<8|s,a=c>>18&63,n=c>>12&63,i=c>>6&63,o=63&c,h[l++]=g.charAt(a)+g.charAt(n)+g.charAt(i)+g.charAt(o);while(d<t.length);f=h.join("");var u=t.length%3;return(u?f.slice(0,u-3):f)+"===".slice(u||3)}if(t=t||8121991,8121991==t)return t;var r=/linear|radial/g.test(t)&&t.split(/,(?=\s*(?:linear|radial|url))/g),s=[],a={"to bottom":'x1="0%" y1="0%" x2="0%" y2="100%"',"to left":'x1="100%" y1="0%" x2="0%" y2="0%"',"to top":'x1="0%" y1="100%" x2="0%" y2="0%"',"to right":'x1="0%" y1="0%" x2="100%" y2="0%"',get"top"(){return this["to bottom"]},get"180deg"(){return this["to bottom"]},get"right"(){return this["to left"]},get"270deg"(){return this["to left"]},get"bottom"(){return this["to top"]},get"90deg"(){return this["to right"]},get"0deg"(){return this["to top"]},get"left"(){return this["to right"]},"-45deg":'x1="0%" y1="0%" x2="100%" y2="100%"',"45deg":'x1="0%" y1="100%" x2="100%" y2="0%"',"ellipse at center":'cx="50%" cy="50%" r="75%"',get"135deg"(){return this["-45deg"]}},n={uri_data:"url(data:image/svg+xml;base64,",xml:'<?xml version="1.0" ?>',svg_start:'<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">',linear_gradient_start:'<linearGradient id="lesshat-generated" gradientUnits="userSpaceOnUse"',radial_gradient_start:'<radialGradient id="lesshat-generated" gradientUnits="userSpaceOnUse"',linear_gradient_end:"</linearGradient>",radial_gradient_end:"</radialGradient>",rect_linear:'<rect x="0" y="0" width="1" height="1" fill="url(#lesshat-generated)" />',rect_radial:'<rect x="-50" y="-50" width="101" height="101" fill="url(#lesshat-generated)" />',svg_end:"</svg>"};if(r.length){r.forEach(function(t,e){var r={};if(Object.keys(a).some(function(e){return t.indexOf(e)>=0?(r.svg_direction=a[e],!0):void(r.svg_direction=!1)}),/linear/.test(t))r.svg_type="linear";else if(/radial/.test(t))r.svg_type="radial";else if(!/linear/.test(t)&&!/radial/.test(t))return r.url=t.trim(),r.svg_type="url",r.svg_direction=!0,s.push(r),!1;var n=t.match(/rgb|#[a-zA-Z0-9]|hsl/g).length;r.svg_stops=[],t=t.replace(/transparent/g,"rgba(0,0,0,0)"),t.match(/#[a-zA-Z0-9]/g)&&t.match(/(#[a-zA-Z0-9]+)\s*(\d+%)?/g).forEach(function(t){t=t.split(" "),r.svg_stops.push('<stop offset="'+(t[1]||!1)+'" stop-color="'+t[0]+'" stop-opacity="1"/>')}),t.match(/rgba?\(\d+,\s*\d+,\s*\d+(?:,\s*(0|1|\.\d+|0\.\d+))?\)/g)&&t.replace(/rgba?\((\d+,\s*\d+,\s*\d+)(?:,\s*(0|1|\.\d+|0\.\d+))?\)\s*(\d+%)?/g,function(t,e,s,a){r.svg_stops.push('<stop offset="'+(a||!1)+'" stop-color="rgb('+e+')" stop-opacity="'+(s||1)+'"/>')}),t.match(/hsla?\((\d+,\s*\d+%,\s*\d+%),\s*(0|1|\.\d+|0\.\d+)\)/g)&&t.replace(/hsla?\((\d+,\s*\d+%,\s*\d+%),\s*(0|1|\.\d+|0\.\d+)\)\s*(\d+%)?/g,function(t,e,s,a){r.svg_stops.push('<stop offset="'+(a||!1)+'" stop-color="hsl('+e+')" stop-opacity="'+(s||1)+'"/>')});var i=Math.floor(100/(n-1));r.svg_stops.forEach(function(t,e){/offset="false"/.test(t)&&(r.svg_stops[e]=t.replace(/offset="false"/,'offset="'+i*e+'%"'))}),r.svg_stops.sort(function(t,e){if(t=t.match(/offset="(\d+)%"/),e=e.match(/offset="(\d+)%"/),2==t.length&&2==e.length)return t[1]-e[1]}),s.push(r)});var i=[],o=s.every(function(t){for(var e in t)if(0==t[e]||0==t[e].length)return!1;return!0});if(!o)return 8121991;s.forEach(function(t,e){"linear"!=t.svg_type&&"radial"!=t.svg_type||(i[e]=n.xml+n.svg_start),"linear"==t.svg_type?(i[e]+=n.linear_gradient_start+" "+t.svg_direction+">",t.svg_stops.forEach(function(t){i[e]+=t}),i[e]+=n.linear_gradient_end,i[e]+=n.rect_linear,i[e]+=n.svg_end):"radial"==t.svg_type?(i[e]+=n.radial_gradient_start+" "+t.svg_direction+">",t.svg_stops.forEach(function(t){i[e]+=t}),i[e]+=n.radial_gradient_end,i[e]+=n.rect_radial,i[e]+=n.svg_end):"url"==t.svg_type&&(i[e]=t.url)}),i.forEach(function(t,r){/<\?xml version="1.0" \?>/g.test(t)&&(i[r]=n.uri_data+e(t)+")")}),t=i.join(",")}return t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_webkit: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r={"to bottom":"top","to left":"right","to top":"bottom","to right":"left","ellipse at center":"center, ellipse cover","circle closest-side":"center center, circle contain","circle farthest-corner":"center center, circle cover","circle farthest-side":"center center, circle cover","ellipse closest-side":"center center, ellipse contain","ellipse farthest-corner":"center center, ellipse cover","ellipse farthest-side":"center center, ellipse cover"},t=/(radial-gradient\()([a-z- ]+)at\s+(\w+%?)\s*(\w*%?)/g,c=Object.keys(r);return c.some(function(c){return e.indexOf(c)>=0?(e=e.replace(new RegExp(c+"(?![ a-z0-9])","g"),r[c]),!0):void(t.test(e)&&(e=e.replace(t,function(e,r,t,c,i){return r.trim()+c.trim()+" "+i.trim()+","+t.replace(/closest-side/g,"contain").replace(/farthest-corner/g,"cover").trim()})))}),e=e.replace(/(\d+)\s*deg/g,function(e,r){return 90-r+"deg"}).replace(/(linear|radial)-gradient/g,"-webkit-$1-gradient")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r={"to bottom":"top","to left":"right","to top":"bottom","to right":"left","ellipse at center":"center, ellipse cover","circle closest-side":"center center, circle contain","circle farthest-corner":"center center, circle cover","circle farthest-side":"center center, circle cover","ellipse closest-side":"center center, ellipse contain","ellipse farthest-corner":"center center, ellipse cover","ellipse farthest-side":"center center, ellipse cover"},t=/(radial-gradient\()([a-z- ]+)at\s+(\w+%?)\s*(\w*%?)/g,c=Object.keys(r);return c.some(function(c){return e.indexOf(c)>=0?(e=e.replace(new RegExp(c+"(?![ a-z0-9])","g"),r[c]),!0):void(t.test(e)&&(e=e.replace(t,function(e,r,t,c,n){return r.trim()+c.trim()+" "+n.trim()+","+t.replace(/closest-side/g,"contain").replace(/farthest-corner/g,"cover").trim()})))}),e=e.replace(/(\d+)\s*deg/g,function(e,r){return 90-r+"deg"}).replace(/(linear|radial)-gradient/g,"-moz-$1-gradient")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_opera: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r={"to bottom":"top","to left":"right","to top":"bottom","to right":"left","ellipse at center":"center, ellipse cover","circle closest-side":"center center, circle contain","circle farthest-corner":"center center, circle cover","circle farthest-side":"center center, circle cover","ellipse closest-side":"center center, ellipse contain","ellipse farthest-corner":"center center, ellipse cover","ellipse farthest-side":"center center, ellipse cover"},t=/(radial-gradient\()([a-z- ]+)at\s+(\w+%?)\s*(\w*%?)/g,c=Object.keys(r);return c.some(function(c){return e.indexOf(c)>=0?(e=e.replace(new RegExp(c+"(?![ a-z0-9])","g"),r[c]),!0):void(t.test(e)&&(e=e.replace(t,function(e,r,t,c,n){return r.trim()+c.trim()+" "+n.trim()+","+t.replace(/closest-side/g,"contain").replace(/farthest-corner/g,"cover").trim()})))}),e=e.replace(/(\d+)\s*deg/g,function(e,r){return 90-r+"deg"}).replace(/(linear|radial)-gradient/g,"-o-$1-gradient")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){if(t=t||8121991,8121991==t)return t;var e={top:"to bottom",right:"to left",bottom:"to top",left:"to right"},o=Object.keys(e);return o.some(function(o){if(t.indexOf(o)>=0&&!new RegExp("to\\s+"+o+"|at\\s+"+o,"g").test(t))return t=t.replace(new RegExp(o),e[o]),!0}),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - background-image: @process_ms; - background-image: @process_webkit; - background-image: @process_moz; - background-image: @process_opera; - background-image: @process; -} - -.background-origin(...) { - @process: ~`(function(n){return n||"padding-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-background-origin: @process; - -moz-background-origin: @process; - background-origin: @process; -} - -.background-size(...) { - @process: ~`(function(t){t=t||"auto auto";var e=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(t)&&(t=t.replace(/(?:,)(?![^(]*\))/g,"")),e.test(t)&&(t=t.replace(r,function(t){return 0==t&&t||t+"px"})),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-background-size: @process; - -moz-background-size: @process; - background-size: @process; -} - -.blur(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: blur(@process); - -moz-filter: blur(@process); - -ms-filter: blur(@process); - filter: blur(@process); -} - -.border-bottom-left-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-bottom-left-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-bottomleft: @process; -moz-background-clip: padding; - border-bottom-left-radius: @process; background-clip: padding-box; -} - -.border-bottom-right-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-bottom-right-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-bottomright: @process; -moz-background-clip: padding; - border-bottom-right-radius: @process; background-clip: padding-box; -} - -.border-image(...) { - @process: ~`(function(e){return e=e||8121991,/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-image: @process; - -moz-border-image: @process; - -o-border-image: @process; - border-image: @process; -} - -.border-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius: @process; -moz-background-clip: padding; - border-radius: @process; background-clip: padding-box; -} - -.border-top-left-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-top-left-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-topleft: @process; -moz-background-clip: padding; - border-top-left-radius: @process; background-clip: padding-box; -} - -.border-top-right-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-top-right-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-topright: @process; -moz-background-clip: padding; - border-top-right-radius: @process; background-clip: padding-box; -} - -.box-shadow(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-shadow: @process; - -moz-box-shadow: @process; - box-shadow: @process; -} - .box-sizing(...) { @process: ~`(function(n){return n=n||"content-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; -webkit-box-sizing: @process; @@ -306,455 +17,9 @@ box-sizing: @process; } -.brightness(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: brightness(@process); - -moz-filter: brightness(@process); - -ms-filter: brightness(@process); - filter: brightness(@process); -} - .calc(...) { @process: ~`(function(a){function c(c,t){var r=");\n",s=e.split(","),l=s[0]+":"+c+"("+(s[1].trim()||0)+r;"start"==t?a="0;\n"+l:a+=l}a=a||8121991;var t="@{state}",e=a;if(8121991==a)return a;switch(t){case"1":c("-webkit-calc","start"),c("-moz-calc"),c("calc");break;case"2":c("-webkit-calc","start"),c("-moz-calc");break;case"3":c("-webkit-calc","start"),c("calc");break;case"4":c("-webkit-calc","start");break;case"5":c("-moz-calc","start"),c("calc");break;case"6":c("-moz-calc","start");break;case"7":c("calc","start")}return a=a.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; @state: 1; -lh-property: @process; - -} - -.column-count(...) { - @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-count: @process; - -moz-column-count: @process; - column-count: @process; -} - -.column-gap(...) { - @process: ~`(function(n){n=n||"normal";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-gap: @process; - -moz-column-gap: @process; - column-gap: @process; -} - -.column-rule(...) { - @process: ~`(function(e){e=e||"medium none black";var n=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),n.test(e)&&(e=e.replace(t,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-rule: @process; - -moz-column-rule: @process; - column-rule: @process; -} - -.column-width(...) { - @process: ~`(function(t){t=t||"auto";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(t)&&(t=t.replace(r,function(t){return 0==t&&t||t+"px"})),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-width: @process; - -moz-column-width: @process; - column-width: @process; -} - -.columns(...) { - @process: ~`(function(t){t=t||"auto auto";var e=/^\d+$/;return/^[^, ]*,/.test(t)&&(t=t.replace(/(?:,)(?![^(]*\))/g,""),t=t.split(" ")),e.test(t[0])&&(t[0]=t[0]+"px"),t.join(" ")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-columns: @process; - -moz-columns: @process; - columns: @process; -} - -.contrast(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: ~"contrast(@{process})"; - -moz-filter: ~"contrast(@{process})"; - -ms-filter: ~"contrast(@{process})"; - filter: ~"contrast(@{process})"; -} - -.display(...) { - @process_oldwebkit: ~`(function(e){return e="flex"==e||"inline-flex"==e?"-webkit-box":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(n){return n="flex"==n||"inline-flex"==n?"-moz-box":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_webkit: ~`(function(e){return e="flex"==e||"inline-flex"==e?"-webkit-"+e:8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(e){return e="flex"==e?"-ms-flexbox":"inline-flex"==e?"-ms-inline-flexbox":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){return"flex"!=n&&"inline-flex"!=n&&(n=8121991),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - display: @process_oldwebkit; - display: @process_moz; - display: @process_webkit; - display: @process_ms; - display: @process; -} - -.drop-shadow(...) { - @process: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),r.test(e)&&(e=e.replace(t,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: drop-shadow(@process); - -moz-filter: drop-shadow(@process); - -ms-filter: drop-shadow(@process); - filter: drop-shadow(@process); -} - -.filter(...) { - @process: ~`(function(e){return e=e||"none",/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: @process; - -moz-filter: @process; - -ms-filter: @process; - filter: @process; -} - -.flex(...) { - @process_olderwebkit: ~`(function(t){return/^\d+/.test(t)?t=t.match(/^\d+/)[0]:""==t&&(t="0"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(t){return/^\d+/.test(t)?t=t.match(/^\d+/)[0]:""==t&&(t="0"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){return t=t||"0 1 auto",/^[^, ]*,/.test(t)&&(t=t.replace(/(?:,)(?![^(]*\))/g,"")),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-flex: @process_olderwebkit; - -moz-box-flex: @process_moz; - -webkit-flex: @process; - -ms-flex: @process; - flex: @process; -} - -.flex-basis(...) { - @process: ~`(function(t){t=t||"auto";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(t)&&(t=t.replace(r,function(t){return 0==t&&t||t+"px"})),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-basis: @process; - flex-basis: @process; -} - -.flex-direction(...) { - @process_oldestwebkit: ~`(function(r){return r="row"==r||"column"==r?"normal":"row-reverse"==r||"column-reverse"==r?"reverse":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_oldermoz: ~`(function(r){return r="row"==r||"column"==r?"normal":"row-reverse"==r||"column-reverse"==r?"reverse":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_olderwebkit: ~`(function(r){return r="row"==r||"row-reverse"==r?"horizontal":"column"==r||"column-reverse"==r?"vertical":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(r){return r="row"==r||"row-reverse"==r?"horizontal":"column"==r||"column-reverse"==r?"vertical":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){return n=n||"row"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-direction: @process_oldestwebkit; - -moz-box-direction: @process_oldermoz; - -webkit-box-orient: @process_olderwebkit; - -moz-box-orient: @process_moz; - -webkit-flex-direction: @process; - -ms-flex-direction: @process; - flex-direction: @process; -} - -.flex-grow(...) { - @process: ~`(function(n){return n=n||"0"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-grow: @process; - flex-grow: @process; -} - -.flex-shrink(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-shrink: @process; - flex-shrink: @process; -} - -.flex-wrap(...) { - @process: ~`(function(n){return n=n||"nowrap"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-wrap: @process; - -ms-flex-wrap: @process; - flex-wrap: @process; -} - -.font-face(@fontname, @fontfile, @fontweight:normal, @fontstyle:normal) { - font-family: "@{fontname}"; - src: url("@{fontfile}.eot"); - src: url("@{fontfile}.eot?#iefix") format("embedded-opentype"), - url("@{fontfile}.woff") format("woff"), - url("@{fontfile}.ttf") format("truetype"), - url("@{fontfile}.svg#@{fontname}") format("svg"); - font-weight: @fontweight; - font-style: @fontstyle; -} - -.grayscale(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: grayscale(@process); - -moz-filter: grayscale(@process); - -ms-filter: grayscale(@process); - filter: grayscale(@process); -} - -.hue-rotate(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: hue-rotate(@process); - -moz-filter: hue-rotate(@process); - -ms-filter: hue-rotate(@process); - filter: hue-rotate(@process); -} - -.hyphens(...) { - @process: ~`(function(n){return n=n||"manual"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-hyphens: @process; - -moz-hyphens: @process; - -ms-hyphens: @process; - hyphens: @process; -} - -.invert(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: invert(@process); - -moz-filter: invert(@process); - -ms-filter: invert(@process); - filter: invert(@process); -} - -.justify-content(...) { - @process_oldestWebkit: ~`(function(e){return e=e||"start","flex-start"==e?e="start":"flex-end"==e?e="end":"space-between"!=e&&"space-around"!=e||(e="justify"),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(e){return e=e||"start","flex-start"==e?e="start":"flex-end"==e?e="end":"space-between"!=e&&"space-around"!=e||(e="justify"),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(t){return t=t||"start","flex-start"==t?t="start":"flex-end"==t?t="end":"space-between"==t?t="justify":"space-around"==t&&(t="distribute"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){return t=t||"flex-start"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-pack: @process_oldestWebkit; - -moz-box-pack: @process_moz; - -ms-flex-pack: @process_ms; - -webkit-justify-content: @process; - justify-content: @process; -} - -.keyframes(...) { - @process: ~`(function(e){function a(a,r,k){var n="}\n",m=t.split(/(^[a-zA-Z0-9-]+),/g),o=r+" "+m[1]+"{",f=["-webkit-","-moz-","-ms-",""];k?s.forEach(function(a,r){e.indexOf(a)!==-1&&(m[2]=m[2].replace(new RegExp(a,"g"),function(e){return k+e}))}):m[2]=m[2].replace(/{([^}]+)}/g,function(e,a){var r=a.split(";");r.forEach(function(e,a){s.forEach(function(t){e.indexOf(t)!==-1&&(r[a]="",f.forEach(function(s){r[a]+=e.trim().replace(new RegExp(t,"g"),function(e){return s+e})+";"}))})});var t=r.join(";").replace(/;;/g,";");return e.replace(a,t)}),o+=m[2]+n,"start"==a?e="0; } \n"+o:"startend"==a?e="0; } \n"+o.replace(n,""):e+="end"==a?o.replace(n,""):o}e=e||8121991;var r="@{state}",t=e;if(8121991==e)return e;var s=["animation","transform","filter"];switch(r){case"1":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-moz-keyframes","-moz-"),a(null,"@-o-keyframes","-o-"),a("end","@keyframes");break;case"2":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-moz-keyframes","-moz-"),a("end","@keyframes");break;case"3":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-moz-keyframes","-moz-"),a("end","@-o-keyframes","-o-");break;case"4":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-o-keyframes","-o-"),a("end","@keyframes");break;case"5":a("start","@-webkit-keyframes","-webkit-"),a("end","@-moz-keyframes","-moz-");break;case"6":a("start","@-webkit-keyframes","-webkit-"),a("end","@-o-keyframes","-o-");break;case"7":a("start","@-webkit-keyframes","-webkit-"),a("end","@keyframes");break;case"8":a("startend","@-webkit-keyframes","-webkit-");break;case"9":a("start","@-moz-keyframes","-moz-"),a(null,"@-o-keyframes","-o-"),a("end","@keyframes");break;case"10":a("start","@-moz-keyframes","-moz-"),a("end","@-o-keyframes","-o-");break;case"11":a("start","@-moz-keyframes","-moz-"),a("end","@keyframes");break;case"12":a("startend","@-moz-keyframes","-moz-");break;case"13":a("start","@-o-keyframes","-o-"),a("end","@keyframes");break;case"14":a("startend","@-o-keyframes","-o-");break;case"15":a("startend","@keyframes")}return e+"}\n[not-existing] {\n zoom: 1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @state: 1; lesshat-selector { -lh-property: @process; } - - - -} - -.opacity(...) { - @process_ms: ~`(function(a){return a=a||"filter: alpha(opacity=100)","alpha(opacity="+Math.floor(100*a)+")"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - zoom: 1; filter: @process_ms; - -webkit-opacity: @process; - -moz-opacity: @process; - opacity: @process; -} - -.order(...) { - @process: ~`(function(n){return n=n||"0"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-ordinal-group: @process; - -moz-box-ordinal-group: @process; - -ms-flex-order: @process; - -webkit-order: @process; - order: @process; -} - -.perspective(...) { - @process: ~`(function(n){n=n||"none";var e=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return e.test(n)&&(n=n.replace(r,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-perspective: @process; - -moz-perspective: @process; - perspective: @process; -} - -.perspective-origin(...) { - @process: ~`(function(e){e=e||"50% 50%";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"%"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-perspective-origin: @process; - -moz-perspective-origin: @process; - perspective-origin: @process; -} - -.placeholder(@color:#aaa, @element: 08121991) { - .inception (@arguments) when not (@element = 08121991) { - @{element}::-webkit-input-placeholder { - color: @color; - } - @{element}:-moz-placeholder { - color: @color; - } - @{element}::-moz-placeholder { - color: @color; - } - @{element}:-ms-input-placeholder { - color: @color; - } - } - .inception (@arguments) when (@element = 08121991) { - &::-webkit-input-placeholder { - color: @color; - } - &:-moz-placeholder { - color: @color; - } - &::-moz-placeholder { - color: @color; - } - &:-ms-input-placeholder { - color: @color; - } - } - .inception(@arguments); -} - -.rotate(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotate(@process); - -moz-transform: rotate(@process); - -ms-transform: rotate(@process); - -o-transform: rotate(@process); - transform: rotate(@process); -} - -.rotate3d(...) { - @process: ~`(function(n){return n=n||"0, 0, 0, 0",n=n.replace(/,\s*\d+$/,function(n){return n+"deg"})})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotate3d(@process); - -moz-transform: rotate3d(@process); - -ms-transform: rotate3d(@process); - -o-transform: rotate3d(@process); - transform: rotate3d(@process); -} - -.rotateX(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotateX(@process); - -moz-transform: rotateX(@process); - -ms-transform: rotateX(@process); - -o-transform: rotateX(@process); - transform: rotateX(@process); -} - -.rotateY(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotateY(@process); - -moz-transform: rotateY(@process); - -ms-transform: rotateY(@process); - -o-transform: rotateY(@process); - transform: rotateY(@process); -} - -.rotateZ(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotateZ(@process); - -moz-transform: rotateZ(@process); - -ms-transform: rotateZ(@process); - -o-transform: rotateZ(@process); - transform: rotateZ(@process); -} - -.saturate(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: ~"saturate(@{process})"; - -moz-filter: ~"saturate(@{process})"; - -ms-filter: ~"saturate(@{process})"; - filter: ~"saturate(@{process})"; -} - -.scale(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scale(@process); - -moz-transform: scale(@process); - -ms-transform: scale(@process); - -o-transform: scale(@process); - transform: scale(@process); -} - -.scale3d(...) { - @process: ~`(function(n){return n=n||"1, 1, 1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scale3d(@process); - -moz-transform: scale3d(@process); - -ms-transform: scale3d(@process); - -o-transform: scale3d(@process); - transform: scale3d(@process); -} - -.scaleX(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scaleX(@process); - -moz-transform: scaleX(@process); - -ms-transform: scaleX(@process); - -o-transform: scaleX(@process); - transform: scaleX(@process); -} - -.scaleY(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scaleY(@process); - -moz-transform: scaleY(@process); - -ms-transform: scaleY(@process); - -o-transform: scaleY(@process); - transform: scaleY(@process); -} - -.scaleZ(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scaleZ(@process); - -moz-transform: scaleZ(@process); - -ms-transform: scaleZ(@process); - -o-transform: scaleZ(@process); - transform: scaleZ(@process); -} - -.selection(...) { - @process: ~`(function(e){function t(t,n){var r="}\n",s=a.split(","),c=(s[1]||"")+n+"{"+s[0]+r;"start"==t?e="0; } \n"+c:"startend"==t?e="0; } \n"+c.replace(r,""):e+="end"==t?c.replace(r,""):c}e=e||8121991;var n="@{state}",a=e;if(8121991==e)return e;switch(n){case"1":t("start","::selection"),t("end","::-moz-selection");break;case"2":t("startend","::selection");break;case"3":t("startend","::-moz-selection")}return e=e.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @state: 1; lesshat-selector { -lh-property: @process; } - -} - -.sepia(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: sepia(@process); - -moz-filter: sepia(@process); - -ms-filter: sepia(@process); - filter: sepia(@process); -} - -.size(@square) { - @unit: 'px'; - .process(@square) when (ispixel(@square)), (isem(@square)), (ispercentage(@square)), (iskeyword(@square)) { - width: @square; - height: @square; - } - - .process(@square) when not (ispixel(@square)) and not (isem(@square)) and not (ispercentage(@square)) and not (isstring(@square)) and not (iskeyword(@square)) { - width: ~`@{square} + @{unit}`; - height: ~`@{square} + @{unit}`; - } - - .process(@square); - -} - -.size(@width, @height) { - @unit: 'px'; - .process(@width, @height) when (ispixel(@width)), (isem(@width)), (ispercentage(@width)), (iskeyword(@width)) { - .kittens(@height) when (ispixel(@height)), (isem(@height)), (ispercentage(@height)), (iskeyword(@height)) { - width: @width; - height: @height; - } - .kittens(@height) when not (ispixel(@height)) and not (isem(@height)) and not (ispercentage(@height)) and not (iskeyword(@height)) { - width: @width; - height: ~`@{height} + @{unit}`; - } - .kittens(@height); - } - - .process(@width, @height) when (ispixel(@height)), (isem(@height)), (ispercentage(@height)), (iskeyword(@height)) { - .kittens(@width) when (ispixel(@width)), (isem(@width)), (ispercentage(@width)), (iskeyword(@width)) {} - .kittens(@width) when not (ispixel(@width)) and not (isem(@width)) and not (ispercentage(@width)) and not (iskeyword(@width)) { - width: ~`@{width} + @{unit}`; - height: @height; - } - .kittens(@width); - } - - .process(@width, @height) when not (ispixel(@width)) and not (isem(@width)) and not (ispercentage(@width)) and not (iskeyword(@width)) and not (ispixel(@height)) and not (isem(@height)) and not (ispercentage(@height)) and not (iskeyword(@height)) { - width: ~`@{width} + @{unit}`; - height: ~`@{height} + @{unit}`; - } - - .process(@width, @height); - -} - -.skew(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: skew(@process); - -moz-transform: skew(@process); - -ms-transform: skew(@process); - -o-transform: skew(@process); - transform: skew(@process); -} - -.skewX(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: skewX(@process); - -moz-transform: skewX(@process); - -ms-transform: skewX(@process); - -o-transform: skewX(@process); - transform: skewX(@process); -} - -.skewY(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: skewY(@process); - -moz-transform: skewY(@process); - -ms-transform: skewY(@process); - -o-transform: skewY(@process); - transform: skewY(@process); } .transform(...) { @@ -784,52 +49,6 @@ transform-style: @process; } -.transition(...) { - @process_webkit: ~`(function(r){r=r||"all 0 ease 0";var e=["background-size","border-radius","border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","box-shadow","column","transform","filter"],t="-webkit-",o=/(?:\d)(?:ms|s)/gi,a=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;return/^[^, ]*,/.test(r)&&(r=r.replace(/(?:,)(?![^(]*\))/g,"")),e.forEach(function(e,o){r.indexOf(e)!==-1&&(r=r.replace(new RegExp(e,"g"),function(r){return t+r}))}),o.test(r)||"0"===r||(r=r.replace(a,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(e){e=e||"all 0 ease 0";var n=["background-size","box-shadow","column","transform","filter"],r="-moz-",t=/(?:\d)(?:ms|s)/gi,a=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),n.forEach(function(n,t){e.indexOf(n)!==-1&&(e=e.replace(new RegExp(n,"g"),function(e){return r+e}))}),t.test(e)||"0"===e||(e=e.replace(a,function(e){return e+=parseFloat(e,10)>10?"ms":"s"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_opera: ~`(function(e){e=e||"all 0 ease 0";var n=["transform"],r="-o-",t=/(?:\d)(?:ms|s)/gi,a=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),n.forEach(function(n,t){e.indexOf(n)!==-1&&(e=e.replace(new RegExp(n,"g"),function(e){return r+e}))}),t.test(e)||"0"===e||(e=e.replace(a,function(e){return e+=parseFloat(e,10)>10?"ms":"s"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){n=n||"all 0 ease 0";var e=["-webkit-","-moz-","-o-",""],t=["column","transform","filter"],r=/(?:\d)(?:ms|s)/gi,o=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;/^[^, ]*,/.test(n)&&(n=n.replace(/(?:,)(?![^(]*\))/g,""));var i=n.split(/(?:,)(?![^(]*\))/g);return i.forEach(function(n,r){t.forEach(function(t){n.indexOf(t)!==-1&&(i[r]="",e.forEach(function(o,a){i[r]+=n.trim().replace(new RegExp(t,"g"),function(n){return o+n}),a<e.length-1&&(i[r]+=",")}))})}),n=i.join(","),r.test(n)||"0"===n||(n=n.replace(o,function(n){return n+=parseFloat(n,10)>10?"ms":"s"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition: @process_webkit; - -moz-transition: @process_moz; - -o-transition: @process_opera; - transition: @process; -} - -.transition-delay(...) { - @process: ~`(function(r){r=r||"0";var s=/(?:\d)(?:ms|s)/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-delay: @process; - -moz-transition-delay: @process; - -o-transition-delay: @process; - transition-delay: @process; -} - -.transition-duration(...) { - @process: ~`(function(r){r=r||"0";var s=/ms|s/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-duration: @process; - -moz-transition-duration: @process; - -o-transition-duration: @process; - transition-duration: @process; -} - -.transition-property(...) { - @process_webkit: ~`(function(r){r=r||"all";var o=["background-size","border-radius","border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","box-shadow","column","transform","filter"],t="-webkit-";return o.forEach(function(o,e){r.indexOf(o)!==-1&&(r=r.replace(new RegExp(o,"g"),function(r){return t+r}))}),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(n){n=n||"all";var r=["background-size","box-shadow","column","transform","filter"],o="-moz-";return r.forEach(function(r,e){n.indexOf(r)!==-1&&(n=n.replace(new RegExp(r,"g"),function(n){return o+n}))}),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_opera: ~`(function(n){n=n||"all";var r=["transform"],e="-o-";return r.forEach(function(r,f){n.indexOf(r)!==-1&&(n=n.replace(new RegExp(r,"g"),function(n){return e+n}))}),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){n=n||"all";var o=["-webkit-","-moz-","-o-",""],r=["column","transform","filter"],t=n.split(/(?:,)(?![^(]*\))/g);return t.forEach(function(n,f){r.forEach(function(r){n.indexOf(r)!==-1&&(t[f]="",o.forEach(function(i,c){t[f]+=n.trim().replace(new RegExp(r,"g"),function(n){return i+n}),c<o.length-1&&(t[f]+=",")}))})}),n=t.join(",")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-property: @process_webkit; - -moz-transition-property: @process_moz; - -o-transition-property: @process_opera; - transition-property: @process; -} - -.transition-timing-function(...) { - @process: ~`(function(e){return e=e||"ease"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-timing-function: @process; - -moz-transition-timing-function: @process; - -o-transition-timing-function: @process; - transition-timing-function: @process; -} - .translate(...) { @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; -webkit-transform: translate(@process); @@ -839,15 +58,6 @@ transform: translate(@process); } -.translate3d(...) { - @process: ~`(function(n){n=n||"0, 0, 0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translate3d(@process); - -moz-transform: translate3d(@process); - -ms-transform: translate3d(@process); - -o-transform: translate3d(@process); - transform: translate3d(@process); -} - .translateX(...) { @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; -webkit-transform: translateX(@process); @@ -866,15 +76,6 @@ transform: translateY(@process); } -.translateZ(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translateZ(@process); - -moz-transform: translateZ(@process); - -ms-transform: translateZ(@process); - -o-transform: translateZ(@process); - transform: translateZ(@process); -} - .user-select(...) { @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; -webkit-user-select: @process; diff --git a/packages/rocketchat-livechat/app/client/stylesheets/utils/_loading.import.less b/packages/rocketchat-livechat/app/client/stylesheets/utils/_loading.import.less new file mode 100644 index 0000000000000000000000000000000000000000..838d5625823a5aa994d9b6f0cb2995d440c6d739 --- /dev/null +++ b/packages/rocketchat-livechat/app/client/stylesheets/utils/_loading.import.less @@ -0,0 +1,53 @@ +.loading-animation { + color: @secondary-font-color; + font-size: 1.3rem; + margin-left: 32px; + margin-top: 12px; + margin-bottom: 5px; +} + +.loading-animation > div { + width: 3px; + height: 3px; + border-radius: 100%; + display: inline-block; + background-color: @secondary-font-color; + -webkit-animation: loading-bouncedelay 1.4s infinite ease-in-out both; + animation: loading-bouncedelay 1.4s infinite ease-in-out both; +} + +.loading-animation .bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} + +.loading-animation .bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} + +@-webkit-keyframes loading-bouncedelay { + 0%, + 80%, + 100% { + -webkit-transform: scale(0); + } + + 40% { + -webkit-transform: scale(1); + } +} + +@keyframes loading-bouncedelay { + 0%, + 80%, + 100% { + -webkit-transform: scale(0); + transform: scale(0); + } + + 40% { + -webkit-transform: scale(1); + transform: scale(1); + } +} diff --git a/packages/rocketchat-livechat/app/client/stylesheets/utils/_reset.import.less b/packages/rocketchat-livechat/app/client/stylesheets/utils/_reset.import.less index fce81e7bf5d8e0f45517e0e26f3c535734c6bac0..274ac1571a3ff7801ee7bd375fc978b693b3e265 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/utils/_reset.import.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/utils/_reset.import.less @@ -2,46 +2,139 @@ * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) * http://cssreset.com */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { margin: 0; padding: 0; - border: 0; font-size: 100%; // font: inherit; vertical-align: baseline; + border: 0 solid; // set default border style + &::after, + &::before { + border: 0 solid; + } } + /* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { display: block; } + body { line-height: 1; } -ol, ul { + +ol, +ul { list-style: none; } -blockquote, q { + +blockquote, +q { quotes: none; } -blockquote:before, blockquote:after, -q:before, q:after { + +blockquote::before, +blockquote::after, +q::before, +q::after { content: ''; content: none; } + table { border-collapse: collapse; border-spacing: 0; -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/app/client/stylesheets/_variables.less b/packages/rocketchat-livechat/app/client/stylesheets/utils/_variables.import.less similarity index 66% rename from packages/rocketchat-livechat/app/client/stylesheets/_variables.less rename to packages/rocketchat-livechat/app/client/stylesheets/utils/_variables.import.less index 82fec29be9153d83b8063f276e0060087de63d4c..a8aa9a3b06f329074d9ab277e6ef275c5bb23483 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/_variables.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/utils/_variables.import.less @@ -13,20 +13,20 @@ //@primary-background-color: #38393d; @primary-background-color: #04436a; -@secondary-background-color: #F4F4F4; -@tertiary-background-color: #EAEAEA; +@secondary-background-color: #f4f4f4; +@tertiary-background-color: #eaeaea; -@link-font-color: #008CE3; +@link-font-color: #008ce3; @primary-font-color: #444444; @secondary-font-color: #7f7f7f; @tertiary-font-color: rgba(255, 255, 255, 0.6); @quaternary-font-color: rgba(255, 255, 255, 0.85); -@info-font-color: #AAAAAA; +@info-font-color: #aaaaaa; -@status-online: #35AC19; -@status-offline: rgba(150, 150, 150, 0.50); -@status-busy: #D30230; +@status-online: #35ac19; +@status-offline: rgba(150, 150, 150, 0.5); +@status-busy: #d30230; @status-away: #fcb316; -@window-border-color: #E7E7E7; +@window-border-color: #e7e7e7; diff --git a/packages/rocketchat-livechat/app/client/views/avatar.coffee b/packages/rocketchat-livechat/app/client/views/avatar.coffee index b0abe8754a17e541350f55cda4e2643c23d25398..5e2e62ab9ef3dccec20d19c86b9457726773d451 100644 --- a/packages/rocketchat-livechat/app/client/views/avatar.coffee +++ b/packages/rocketchat-livechat/app/client/views/avatar.coffee @@ -4,7 +4,7 @@ Template.avatar.helpers if not username? and this.userId? username = Meteor.users.findOne(this.userId)?.username - if not username? + if not username? or Meteor.user()?.username is username return Session.get "avatar_random_#{username}" diff --git a/packages/rocketchat-livechat/app/client/views/livechatWindow.html b/packages/rocketchat-livechat/app/client/views/livechatWindow.html index c7eb3fc4c3d34170f57a14cc423a3238ffae6bb7..f84029593a03adc9076d240b632e2b302fb1abc1 100644 --- a/packages/rocketchat-livechat/app/client/views/livechatWindow.html +++ b/packages/rocketchat-livechat/app/client/views/livechatWindow.html @@ -1,9 +1,20 @@ <template name="livechatWindow"> {{#if livechatStarted}} - <div class="livechat-room"> + <div class="livechat-room {{#if popoutActive}}popout{{/if}}"> <div class="title" style="background-color:{{color}}; color: {{fontColor}}"> <div class="toolbar"> + {{#unless popoutActive}} + {{#if isOpened}} + <svg class="minimize" title="Minimize" viewBox="0 0 448 448" xmlns="http://www.w3.org/2000/svg"> + <path d="M448 328v48c0 22-18 40-40 40h-368c-22 0-40-18-40-40v-48c0-22 18-40 40-40h368c22 0 40 18 40 40z"></path> + </svg> + {{else}} + <svg class="maximize" title="Maximize" viewBox="0 0 448 448" xmlns="http://www.w3.org/2000/svg"> + <path d="M64 352h320v-192h-320v192zM448 72v304c0 22-18 40-40 40h-368c-22 0-40-18-40-40v-304c0-22 18-40 40-40h368c22 0 40 18 40 40z"></path> + </svg> + {{/if}} + {{/unless}} {{#if soundActive}} <svg class="sound" title="Toggle notification sound" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"> <path d="M912 1696q0-16-16-16-59 0-101.5-42.5t-42.5-101.5q0-16-16-16t-16 16q0 73 51.5 124.5t124.5 51.5q16 0 16-16zm816-288q0 52-38 90t-90 38h-448q0 106-75 181t-181 75-181-75-75-181h-448q-52 0-90-38t-38-90q50-42 91-88t85-119.5 74.5-158.5 50-206 19.5-260q0-152 117-282.5t307-158.5q-8-19-8-39 0-40 28-68t68-28 68 28 28 68q0 20-8 39 190 28 307 158.5t117 282.5q0 139 19.5 260t50 206 74.5 158.5 85 119.5 91 88z" /> @@ -14,9 +25,8 @@ </svg> {{/if}} {{#unless popoutActive}} - <svg class="popout" aria-label="{{_ "Open in a new window"}}" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> - <path d="M392.857,292.354h-18.274c-2.669,0-4.859,0.855-6.563,2.573c-1.718,1.708-2.573,3.897-2.573,6.563v91.361 c0,12.563-4.47,23.315-13.415,32.262c-8.945,8.945-19.701,13.414-32.264,13.414H82.224c-12.562,0-23.317-4.469-32.264-13.414 c-8.945-8.946-13.417-19.698-13.417-32.262V155.31c0-12.562,4.471-23.313,13.417-32.259c8.947-8.947,19.702-13.418,32.264-13.418 h200.994c2.669,0,4.859-0.859,6.57-2.57c1.711-1.713,2.566-3.9,2.566-6.567V82.221c0-2.662-0.855-4.853-2.566-6.563 c-1.711-1.713-3.901-2.568-6.57-2.568H82.224c-22.648,0-42.016,8.042-58.102,24.125C8.042,113.297,0,132.665,0,155.313v237.542 c0,22.647,8.042,42.018,24.123,58.095c16.086,16.084,35.454,24.13,58.102,24.13h237.543c22.647,0,42.017-8.046,58.101-24.13 c16.085-16.077,24.127-35.447,24.127-58.095v-91.358c0-2.669-0.856-4.859-2.574-6.57 C397.709,293.209,395.519,292.354,392.857,292.354z"></path> - <path d="M506.199,41.971c-3.617-3.617-7.905-5.424-12.85-5.424H347.171c-4.948,0-9.233,1.807-12.847,5.424 c-3.617,3.615-5.428,7.898-5.428,12.847s1.811,9.233,5.428,12.85l50.247,50.248L198.424,304.067 c-1.906,1.903-2.856,4.093-2.856,6.563c0,2.479,0.953,4.668,2.856,6.571l32.548,32.544c1.903,1.903,4.093,2.852,6.567,2.852 s4.665-0.948,6.567-2.852l186.148-186.148l50.251,50.248c3.614,3.617,7.898,5.426,12.847,5.426s9.233-1.809,12.851-5.426 c3.617-3.616,5.424-7.898,5.424-12.847V54.818C511.626,49.866,509.813,45.586,506.199,41.971z"></path> + <svg class="popout" aria-label="{{_ "Open in a new window"}}" viewBox="0 0 448 448" xmlns="http://www.w3.org/2000/svg"> + <path d="M320 232v-120c0-8.75-7.25-16-16-16h-120c-6.5 0-12.25 4-14.75 9.75-2.5 6-1.25 13 3.5 17.5l36 36-133.5 133.5c-6.25 6.25-6.25 16.25 0 22.5l25.5 25.5c6.25 6.25 16.25 6.25 22.5 0l133.5-133.5 36 36c3 3.25 7 4.75 11.25 4.75 2 0 4.25-0.5 6.25-1.25 5.75-2.5 9.75-8.25 9.75-14.75zM384 104v240c0 39.75-32.25 72-72 72h-240c-39.75 0-72-32.25-72-72v-240c0-39.75 32.25-72 72-72h240c39.75 0 72 32.25 72 72z"></path> </svg> {{/unless}} </div> diff --git a/packages/rocketchat-livechat/app/client/views/livechatWindow.js b/packages/rocketchat-livechat/app/client/views/livechatWindow.js index 8785b0fe2fe7941cc6b7d29f4e529e05c2edb1b7..0fe40f00f572c8162dcaa4f1b857b1e30a04470c 100644 --- a/packages/rocketchat-livechat/app/client/views/livechatWindow.js +++ b/packages/rocketchat-livechat/app/client/views/livechatWindow.js @@ -41,6 +41,9 @@ Template.livechatWindow.helpers({ }, videoCalling() { return LivechatVideoCall.isActive(); + }, + isOpened() { + return Livechat.isWidgetOpened(); } }); @@ -105,6 +108,10 @@ Template.livechatWindow.onCreated(function() { Livechat.room = result.room._id; } + if (result.agentData) { + Livechat.agent = result.agentData; + } + TAPi18n.setLanguage((result.language || defaultAppLanguage()).split('-').shift()); Triggers.setTriggers(result.triggers); @@ -113,6 +120,14 @@ Template.livechatWindow.onCreated(function() { result.departments.forEach((department) => { Department.insert(department); }); + + Livechat.ready(); + } + }); + + $(window).on('focus', () => { + if (Livechat.isWidgetOpened()) { + $('textarea').focus(); } }); }); diff --git a/packages/rocketchat-livechat/app/client/views/loading.html b/packages/rocketchat-livechat/app/client/views/loading.html new file mode 100644 index 0000000000000000000000000000000000000000..e13a3f77ba1c13393abbdeeae5bd9f80af50743a --- /dev/null +++ b/packages/rocketchat-livechat/app/client/views/loading.html @@ -0,0 +1,8 @@ +<template name="loading"> + <div class="loading-animation"> + {{_ "Connecting to an Agent"}} + <div class="bounce1"></div> + <div class="bounce2"></div> + <div class="bounce3"></div> + </div> +</template> \ No newline at end of file diff --git a/packages/rocketchat-livechat/app/client/views/message.coffee b/packages/rocketchat-livechat/app/client/views/message.coffee index 313e7d787938b37c62f190989198e79598b1450c..ebe1b3b017a577ee64882fa5afd61683fcc799a2 100644 --- a/packages/rocketchat-livechat/app/client/views/message.coffee +++ b/packages/rocketchat-livechat/app/client/views/message.coffee @@ -40,6 +40,12 @@ Template.message.helpers system: -> return 'system' if this.t in ['s', 'p', 'f', 'r', 'au', 'ru', 'ul', 'wm', 'uj', 'livechat-close'] + sender: -> + agent = Livechat.agent + if agent && @u.username is agent.username + return agent.name or agent.username + return @u.username + Template.message.onViewRendered = (context) -> view = this diff --git a/packages/rocketchat-livechat/app/client/views/message.html b/packages/rocketchat-livechat/app/client/views/message.html index ab01f7a29ff122b256eb64ba99a6884afc91d0d6..7dc9aba0c829470d31b5001ec07e70339953fdeb 100644 --- a/packages/rocketchat-livechat/app/client/views/message.html +++ b/packages/rocketchat-livechat/app/client/views/message.html @@ -1,21 +1,23 @@ <template name="message"> - <li id="{{_id}}" class="message sequential {{system}} {{t}} {{own}} {{isTemp}} {{error}}" data-username="{{u.username}}" data-date="{{date}}"> - <span class="thumb thumb-small" data-username="{{u.username}}" tabindex="1">{{> avatar username=u.username}}</span> - <span class="user" data-username="{{u.username}}" tabindex="1"> - {{#if own}} - {{_ "You"}} - {{else}} - {{u.username}} - {{/if}} - </span> - <span class="info"> - <span class="time">{{time}}</span> - {{#if edit}} - <span class="edited">({{_ "edited"}})</span> - {{/if}} - </span> - <div class="body" dir="auto"> - {{{body}}} + <li id="{{_id}}" class="message background-transparent-dark-hover sequential {{system}} {{t}} {{own}} {{isTemp}} {{error}}" data-username="{{u.username}}" data-date="{{date}}"> + <div class="content"> + <span class="thumb thumb-small" data-username="{{u.username}}" tabindex="1">{{> avatar username=u.username}}</span> + <span class="user" data-username="{{u.username}}" tabindex="1"> + {{#if own}} + {{_ "You"}} + {{else}} + {{sender}} + {{/if}} + </span> + <div class="body" dir="auto"> + {{{body}}} + <span class="info"> + <span class="time">{{time}}</span> + {{#if edit}} + <span class="edited">({{_ "edited"}})</span> + {{/if}} + </span> + </div> </div> </li> </template> diff --git a/packages/rocketchat-livechat/app/client/views/messages.html b/packages/rocketchat-livechat/app/client/views/messages.html index 0f8004e3face7f706f2ecfa5712074474094d98c..7698ca27f444efb970a66d8d76b141b7b0eba8d0 100644 --- a/packages/rocketchat-livechat/app/client/views/messages.html +++ b/packages/rocketchat-livechat/app/client/views/messages.html @@ -1,4 +1,22 @@ <template name="messages"> + {{#with agentData}} + <div class="header"> + <div class="picture"> + <img src="{{avatar}}"> + </div> + <div class="info"> + <ul> + <li><h2>{{name}}</h2></li> + {{#if email}} + <li>{{email}}</li> + {{/if}} + {{#if phone}} + <li>{{phone}}</li> + {{/if}} + </ul> + </div> + </div> + {{/with}} <div class="messages"> <div class="wrapper"> <ul> @@ -7,28 +25,53 @@ {{/each}} </ul> </div> - <div class="new-message not"> + <div class="new-message background-primary-action-color color-primary-action-contrast not"> <span>{{_ "New_messages"}}</span> </div> - <div class="error"> + <div class="error error-color error-background"> <span></span> </div> </div> <div class="footer"> - <div class="message-bar"> - <div class="input-wrapper"> - <textarea class="input-message" placeholder="{{_ "Type_your_message"}}"></textarea> + {{#if showConnecting}} + {{> loading}} + {{else}} + <div class="message-bar"> + <div class="input-wrapper"> + <textarea class="input-message" placeholder="{{_ "Type_your_message"}}"></textarea> + </div> + <div class="buttons"> + <svg class="send-button" aria-label="{{_ "Send"}}" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1764 11q33 24 27 64l-256 1536q-5 29-32 45-14 8-31 8-11 0-24-5l-453-185-242 295q-18 23-49 23-13 0-22-4-19-7-30.5-23.5t-11.5-36.5v-349l864-1059-1069 925-395-162q-37-14-40-55-2-40 32-59l1664-960q15-9 32-9 20 0 36 11z"/></svg> + {{#if videoCallEnabled}} + <svg class="video-button" aria-label="{{_ "Video"}}" viewBox="0 0 459 459" xmlns="http://www.w3.org/2000/svg"><path d="M357,191.25V102c0-15.3-10.2-25.5-25.5-25.5h-306C10.2,76.5,0,86.7,0,102v255c0,15.3,10.2,25.5,25.5,25.5h306 c15.3,0,25.5-10.2,25.5-25.5v-89.25l102,102V89.25L357,191.25z"/></svg> + {{/if}} + </div> </div> - <div class="buttons"> - <svg class="send-button" aria-label="{{_ "Send"}}" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1764 11q33 24 27 64l-256 1536q-5 29-32 45-14 8-31 8-11 0-24-5l-453-185-242 295q-18 23-49 23-13 0-22-4-19-7-30.5-23.5t-11.5-36.5v-349l864-1059-1069 925-395-162q-37-14-40-55-2-40 32-59l1664-960q15-9 32-9 20 0 36 11z"/></svg> - {{#if videoCallEnabled}} - <svg class="video-button" aria-label="{{_ "Video"}}" viewBox="0 0 459 459" xmlns="http://www.w3.org/2000/svg"><path d="M357,191.25V102c0-15.3-10.2-25.5-25.5-25.5h-306C10.2,76.5,0,86.7,0,102v255c0,15.3,10.2,25.5,25.5,25.5h306 c15.3,0,25.5-10.2,25.5-25.5v-89.25l102,102V89.25L357,191.25z"/></svg> - {{/if}} - </div> - </div> + {{/if}} - {{> options show=showOptions}} - <button class="toggle-options">{{optionsLink}}</button> + {{#if usersTyping.users}} + {{#with usersTyping}} + <div class="typing"> + <strong>{{users}}</strong> + {{#if multi}} + {{#if selfTyping}} + {{_ "are_also_typing"}} + {{else}} + {{_ "are_typing"}} + {{/if}} + {{else}} + {{#if selfTyping}} + {{_ "is_also_typing" context="male"}} + {{else}} + {{_ "is_typing" context="male"}} + {{/if}} + {{/if}} + </div> + {{/with}} + {{else}} + {{> options show=showOptions}} + <button class="toggle-options secondary-font-color">{{optionsLink}}</button> + {{/if}} </div> </template> diff --git a/packages/rocketchat-livechat/app/client/views/messages.js b/packages/rocketchat-livechat/app/client/views/messages.js index cd6ca83ecc49fb40cc7afcc2315650aaec20124e..5bda8a026ac7def5d63074ab5c264ba1cdc6de91 100644 --- a/packages/rocketchat-livechat/app/client/views/messages.js +++ b/packages/rocketchat-livechat/app/client/views/messages.js @@ -1,4 +1,4 @@ -/* globals Livechat, LivechatVideoCall */ +/* globals Livechat, LivechatVideoCall, MsgTyping */ Template.messages.helpers({ messages() { @@ -29,6 +29,59 @@ Template.messages.helpers({ }, videoCallEnabled() { return Livechat.videoCall; + }, + showConnecting() { + return Livechat.connecting; + }, + usersTyping() { + const users = MsgTyping.get(visitor.getRoom()); + if (users.length === 0) { + return; + } + if (users.length === 1) { + return { + multi: false, + selfTyping: MsgTyping.selfTyping.get(), + users: users[0] + }; + } + // usernames = _.map messages, (message) -> return message.u.username + let last = users.pop(); + if (users.length > 4) { + last = t('others'); + } + // else + let usernames = users.join(', '); + usernames = [usernames, last]; + return { + multi: true, + selfTyping: MsgTyping.selfTyping.get(), + users: usernames.join(` ${t('and')} `) + }; + }, + agentData() { + const agent = Livechat.agent; + if (!agent) { + return null; + } + + const agentData = { + avatar: getAvatarUrlFromUsername(agent.username) + }; + + if (agent.name) { + agentData.name = agent.name; + } + + if (agent.emails && agent.emails[0] && agent.emails[0].address) { + agentData.email = agent.emails[0].address; + } + + if (agent.customFields && agent.customFields.phone) { + agentData.phone = agent.customFields.phone; + } + + return agentData; } }); diff --git a/packages/rocketchat-livechat/app/client/views/offlineForm.html b/packages/rocketchat-livechat/app/client/views/offlineForm.html index 50d443485526be8ad1246f0a1e185d6cf27ea254..655687876b9bf9b6ee876c17b6db08196720f4fa 100644 --- a/packages/rocketchat-livechat/app/client/views/offlineForm.html +++ b/packages/rocketchat-livechat/app/client/views/offlineForm.html @@ -6,7 +6,7 @@ <p class="offline-message">{{{offlineMessage}}}</p> <form> - <div class="error"> + <div class="error error-color error-background"> <span>{{{error}}}</span> </div> diff --git a/packages/rocketchat-livechat/app/client/views/options.html b/packages/rocketchat-livechat/app/client/views/options.html index 8c57e9f6379daf7db4cd829510b8696c5fdd3ece..50bc5217f78d4fa1790d12305dcca5bb47e6966a 100644 --- a/packages/rocketchat-livechat/app/client/views/options.html +++ b/packages/rocketchat-livechat/app/client/views/options.html @@ -1,5 +1,5 @@ <template name="options"> - <div class="options-menu {{show}}"> + <div class="options-menu content-background-color {{show}}"> <ul> <li><button class="end-chat"><i class="icon-cancel"></i> {{_ "End_chat"}}</button></li> </ul> diff --git a/packages/rocketchat-livechat/app/client/views/register.html b/packages/rocketchat-livechat/app/client/views/register.html index cdb108bca9cbf43e212876d312519dd79a75b077..9538e7113b9caef5f758fac3eca6487a4f3f6663 100644 --- a/packages/rocketchat-livechat/app/client/views/register.html +++ b/packages/rocketchat-livechat/app/client/views/register.html @@ -1,6 +1,6 @@ <template name="register"> <div class="livechat-form"> - <div class="error"> + <div class="error error-color error-background"> <span>{{{error}}}</span> </div> @@ -11,11 +11,11 @@ <input type="text" name="name" id="guestName" placeholder="{{_ "Name"}}"> <input type="email" name="email" id="guestEmail" placeholder="{{_ "Email"}}"> - {{#if hasDepartments}} + {{#if showDepartments}} <select name="department"> <option value="">{{_ "Select_a_department"}}</option> {{#each departments}} - <option value="{{_id}}">{{name}}</option> + <option value="{{_id}}" selected="{{selectedDepartment}}">{{name}}</option> {{/each}} </select> {{/if}} diff --git a/packages/rocketchat-livechat/app/client/views/register.js b/packages/rocketchat-livechat/app/client/views/register.js index e7a9610688ac1b0f249b76217ad2e733b3b3a6bd..f1119080040c01c7db4a1f65a5187b6a2a747d8b 100644 --- a/packages/rocketchat-livechat/app/client/views/register.js +++ b/packages/rocketchat-livechat/app/client/views/register.js @@ -7,14 +7,17 @@ Template.register.helpers({ welcomeMessage() { return ''; }, - hasDepartments() { - return Department.find().count() > 1; + showDepartments() { + return Department.find({ showOnRegistration: true }).count() > 1; }, departments() { - return Department.find(); + return Department.find({ showOnRegistration: true }); }, videoCallEnabled() { return Livechat.videoCall; + }, + selectedDepartment() { + return this._id === Livechat.department; } }); @@ -37,7 +40,7 @@ Template.register.events({ } else { var departmentId = instance.$('select[name=department]').val(); if (!departmentId) { - var department = Department.findOne(); + var department = Department.findOne({ showOnRegistration: true }); if (department) { departmentId = department._id; } @@ -47,7 +50,7 @@ Template.register.events({ token: visitor.getToken(), name: $name.val(), email: $email.val(), - department: departmentId + department: Livechat.deparment || departmentId }; Meteor.call('livechat:registerGuest', guest, function(error, result) { if (error != null) { diff --git a/packages/rocketchat-livechat/app/i18n/cs.i18n.json b/packages/rocketchat-livechat/app/i18n/cs.i18n.json index 98ebc4fe0c1415dc2f3bd03c3da46b9d2f3e9dd1..96a789d1b1cb5c7a68de8494e78f4d09ec534036 100644 --- a/packages/rocketchat-livechat/app/i18n/cs.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/cs.i18n.json @@ -18,6 +18,7 @@ "Please_answer_survey": "VÄ›nujte prosÃm chvilku Äasu ohodnocenà chatu.", "Please_fill_name_and_email": "ProsÃm vyplňte jméno a e-mail", "Powered_by": "PoužÃvá technologii", + "Request_video_chat": "Zažádat o video chat", "Select_a_department": "Vyberte oddÄ›lenÃ", "Send": "Poslat", "Skip": "PÅ™eskoÄit", @@ -26,6 +27,7 @@ "Survey_instructions": "HodnoÅ¥te každou otázku dle vašà spokojenosti, 1 - zcela nespokojeni a 5 - zcela spokojeni.", "Thank_you_for_your_feedback": "DÄ›kujeme Vám za VaÅ¡e hodnocenÃ", "Thanks_We_ll_get_back_to_you_soon": "DÃky! OdpovÃme co nejdÅ™Ãve.", + "transcript_sent": "Kopie konverzace odeslána", "Type_your_email": "Zadejte svůj e-mail", "Type_your_message": "NapiÅ¡te zprávu", "Type_your_name": "Zadejte své jméno", diff --git a/packages/rocketchat-livechat/app/i18n/hr.i18n.json b/packages/rocketchat-livechat/app/i18n/hr.i18n.json index b73f6c436d4c44376f09324057c6b68d80e107de..48d2554c79c6efcf1b29ceb283f6c26fda21c863 100644 --- a/packages/rocketchat-livechat/app/i18n/hr.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/hr.i18n.json @@ -27,6 +27,7 @@ "Survey_instructions": "Ocijenite svako pitanje u skladu s VaÅ¡e zadovoljstvo, 1 Å¡to znaÄi da su potpuno nezadovoljni i 5 Å¡to znaÄi da su u potpunosti zadovoljni.", "Thank_you_for_your_feedback": "Hvala vam na povratnim informacijama", "Thanks_We_ll_get_back_to_you_soon": "Hvala! Javit ćemo vam se uskoro.", + "transcript_sent": "Prijepis poslan", "Type_your_email": "UpiÅ¡ite VaÅ¡ e-mail", "Type_your_message": "UpiÅ¡ite svoju poruku", "Type_your_name": "UpiÅ¡ite svoje ime", diff --git a/packages/rocketchat-livechat/app/i18n/sv.i18n.json b/packages/rocketchat-livechat/app/i18n/sv.i18n.json index 4ede7dd162e361c436b959861887da69976b372f..e1aacc1dfac4e142b3e4a2d4f3ebc478dce17076 100644 --- a/packages/rocketchat-livechat/app/i18n/sv.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/sv.i18n.json @@ -11,6 +11,7 @@ "How_satisfied_were_you_with_this_chat": "Hur nöjd var du med chatten?", "Installation": "Installation", "New_messages": "Nya meddelanden", + "No": "Nej", "Please_answer_survey": "Vänligen ta en stund för att svara pÃ¥ en snabb enkät om chatten.", "Please_fill_name_and_email": "Vänligen fyll i namn och e-postadress", "Powered_by": "powered by", diff --git a/packages/rocketchat-livechat/app/package.json b/packages/rocketchat-livechat/app/package.json index 6c51c870a3a7bcbadaac669ad7d024989374089d..f875db3e9ced1620aaee20b36995db5c72f1ab42 100644 --- a/packages/rocketchat-livechat/app/package.json +++ b/packages/rocketchat-livechat/app/package.json @@ -20,10 +20,11 @@ "email": "support@rocket.chat" }, "dependencies": { - "autolinker": "^1.3.2", - "babel-runtime": "^6.18.0", - "moment": "^2.16.0", + "autolinker": "^1.4.0", "jquery": "^2.1.0", + "babel-runtime": "^6.20.0", + "bcrypt": "^1.0.2", + "moment": "^2.17.1", "toastr": "^2.1.2" } } diff --git a/packages/rocketchat-livechat/assets/rocket-livechat.js b/packages/rocketchat-livechat/assets/rocket-livechat.js index e2129eacace6bbf2307e373dd3386ec328d84034..0fcac2ce9ffddf39a8447daa617759651010dad2 100644 --- a/packages/rocketchat-livechat/assets/rocket-livechat.js +++ b/packages/rocketchat-livechat/assets/rocket-livechat.js @@ -6,15 +6,9 @@ var hookQueue = []; var ready = false; - var closeWidget = function() { - widget.dataset.state = 'closed'; - widget.style.height = '30px'; - }; - - var openWidget = function() { - widget.dataset.state = 'opened'; - widget.style.height = '300px'; - }; + var widgetWidth = '320px'; + var widgetHeightOpened = '350px'; + var widgetHeightClosed = '30px'; // hooks var callHook = function(action, params) { @@ -29,6 +23,19 @@ iframe.contentWindow.postMessage(data, '*'); }; + var closeWidget = function() { + widget.dataset.state = 'closed'; + widget.style.height = widgetHeightClosed; + callHook('widgetClosed'); + }; + + var openWidget = function() { + widget.dataset.state = 'opened'; + widget.style.height = widgetHeightOpened; + callHook('widgetOpened'); + document.querySelector('.rocketchat-widget iframe').focus(); + }; + var api = { ready: function() { ready = true; @@ -75,6 +82,14 @@ callHook('setTheme', theme); }; + var setDepartment = function(department) { + callHook('setDepartment', department); + }; + + var clearDepartment = function() { + callHook('clearDepartment'); + }; + var currentPage = { href: null, title: null @@ -107,8 +122,8 @@ '</div><div class="rocketchat-overlay"></div>'; chatWidget.style.position = 'fixed'; - chatWidget.style.width = '300px'; - chatWidget.style.height = '30px'; + chatWidget.style.width = widgetWidth; + chatWidget.style.height = widgetHeightClosed; chatWidget.style.borderTopLeftRadius = '5px'; chatWidget.style.borderTopRightRadius = '5px'; chatWidget.style.bottom = '0'; @@ -137,7 +152,7 @@ } else { chatWidget.style.left = 'auto'; chatWidget.style.right = '50px'; - chatWidget.style.width = '300px'; + chatWidget.style.width = widgetWidth; } }; @@ -168,7 +183,9 @@ w.RocketChat.livechat = { pageVisited: pageVisited, setCustomField: setCustomField, - setTheme: setTheme + setTheme: setTheme, + setDepartment: setDepartment, + clearDepartment: clearDepartment }; // proccess queue diff --git a/packages/rocketchat-livechat/client/route.js b/packages/rocketchat-livechat/client/route.js index 1958f4e4d44a706c085412a818623bdccebf9359..887ca7750d214fc1b0939289aa21dec7c128584b 100644 --- a/packages/rocketchat-livechat/client/route.js +++ b/packages/rocketchat-livechat/client/route.js @@ -59,6 +59,22 @@ AccountBox.addRoute({ pageTemplate: 'livechatTriggers' }, livechatManagerRoutes); +AccountBox.addRoute({ + name: 'livechat-trigger-edit', + path: '/triggers/:_id/edit', + sideNav: 'livechatFlex', + i18nPageTitle: 'Edit_Trigger', + pageTemplate: 'livechatTriggersForm' +}, livechatManagerRoutes); + +AccountBox.addRoute({ + name: 'livechat-trigger-new', + path: '/triggers/new', + sideNav: 'livechatFlex', + i18nPageTitle: 'New_Trigger', + pageTemplate: 'livechatTriggersForm' +}, livechatManagerRoutes); + AccountBox.addRoute({ name: 'livechat-installation', path: '/installation', diff --git a/packages/rocketchat-livechat/client/stylesheets/lesshat.less b/packages/rocketchat-livechat/client/stylesheets/lesshat.less new file mode 100644 index 0000000000000000000000000000000000000000..7c9f0ae86a267eb3660d2381a0bba2620f0488f4 --- /dev/null +++ b/packages/rocketchat-livechat/client/stylesheets/lesshat.less @@ -0,0 +1,17 @@ +// lesshat - The best mixin library in the world +// +// version: v4.1.0 (2016-07-19) + +.calc(...) { + @process: ~`(function(a){function c(c,t){var r=");\n",s=e.split(","),l=s[0]+":"+c+"("+(s[1].trim()||0)+r;"start"==t?a="0;\n"+l:a+=l}a=a||8121991;var t="@{state}",e=a;if(8121991==a)return a;switch(t){case"1":c("-webkit-calc","start"),c("-moz-calc"),c("calc");break;case"2":c("-webkit-calc","start"),c("-moz-calc");break;case"3":c("-webkit-calc","start"),c("calc");break;case"4":c("-webkit-calc","start");break;case"5":c("-moz-calc","start"),c("calc");break;case"6":c("-moz-calc","start");break;case"7":c("calc","start")}return a=a.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + @state: 1; -lh-property: @process; +} + +.transform(...) { + @process: ~`(function(e){e=e||"none";var r={translate:"px",rotate:"deg",rotate3d:"deg",skew:"deg"};/^\w*\(?[a-z0-9.]*\)?/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,""));for(var t in r)e.indexOf(t)>=0&&(e=e.replace(new RegExp(t+"[\\w]?\\([a-z0-9, %]*\\)"),function(e){var n=/(\d+\.?\d*)(?!\w|%)/g;return"rotate3d"==t&&(n=/,\s*\d+$/),e.replace(n,function(e){return e+r[t]})}));return e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: @process; + -moz-transform: @process; + -ms-transform: @process; + -o-transform: @process; + transform: @process; +} diff --git a/packages/rocketchat-livechat/client/stylesheets/livechat.less b/packages/rocketchat-livechat/client/stylesheets/livechat.less index 38715ab7ab5f936b87f245626167675e5d123d5f..7e268decb3fce3bf8afdaec64a0312700a4fdf0b 100644 --- a/packages/rocketchat-livechat/client/stylesheets/livechat.less +++ b/packages/rocketchat-livechat/client/stylesheets/livechat.less @@ -1,10 +1,20 @@ +@import "lesshat.less"; + +@header-min-height: 30px; +@footer-min-height: 55px; +@link-font-color: #008ce3; +@primary-font-color: #444444; +@secondary-font-color: #7f7f7f; +@info-font-color: #aaaaaa; + .flex-list { .active { - background-color: rgba(255,255,255,0.075); + background-color: rgba(255, 255, 255, 0.075); } } -.trigger-option, .trigger-value { +.trigger-option, +.trigger-value { float: left; display: inline-block; } @@ -33,7 +43,7 @@ text-align: left; width: 100%; height: 200px; - background-color: #EFEFEF; + background-color: #efefef; font-family: courier; font-size: 12px; display: block; @@ -45,7 +55,8 @@ margin-bottom: 1em; } -.livechat-settings-div, .livechat-preview-div { +.livechat-settings-div, +.livechat-preview-div { width: 50%; float: left; height: 95%; @@ -54,15 +65,14 @@ } .livechat-settings-div { - border-right: 1px solid #CCC; + border-right: 1px solid #cccccc; } .livechat-preview { width: 340px; height: 350px; margin: 0 auto; - - border-bottom: 1px solid #CCC; + border-bottom: 1px solid #cccccc; position: relative; .preview-wrapper { @@ -71,14 +81,6 @@ padding: 0 20px; height: 300px; bottom: 0; - - @header-min-height: 30px; - @footer-min-height: 55px; - @link-font-color: #008CE3; - @primary-font-color: #444444; - @secondary-font-color: #7f7f7f; - @info-font-color: #AAAAAA; - font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif, "Meiryo UI"; font-size: 0.8rem; color: @primary-font-color; @@ -94,7 +96,7 @@ line-height: inherit; padding: 5px; margin: 5px 0; - border: 1px solid #E7E7E7; + border: 1px solid #e7e7e7; border-radius: 5px; outline: none; } @@ -122,14 +124,15 @@ border-radius: 0; line-height: 16px; position: relative; - cursor: pointer;background-color: #FFF; + cursor: pointer; color: rgba(255, 255, 255, 0.85); - background-color: lighten(desaturate(@primary-background-color, 15%), 12.5%); + span { position: relative; z-index: 2; } - &:before { + + &::before { background-color: rgba(0, 0, 0, 0.1); content: " "; position: absolute; @@ -139,29 +142,27 @@ height: 100%; opacity: 0; z-index: 1; - .transition(opacity .1s ease-out); + transition: opacity 0.1s ease-out; } + &:hover { text-decoration: none; - color: #FFF; - &:before { + color: #ffffff; + + &::before { opacity: 1; } } - &.secondary { - background-color: @tertiary-background-color; - color: @primary-font-color; - &:before { - background-color: rgba(0, 0, 0, 0.045); - } - } + &.clean { font-size: 14px; box-shadow: 0 0 3px rgba(0, 0, 0, 0.08); + &.primary { font-weight: 600; } } + &.button-block { display: block; width: 100%; @@ -175,14 +176,13 @@ .title { flex: 1 0 @header-min-height; - line-height: @header-min-height; - border-top-right-radius: 5px; border-top-left-radius: 5px; - color: #FFF; + color: #ffffff; z-index: 10; cursor: pointer; + h1 { margin: 0; padding: 0 5px; @@ -197,19 +197,21 @@ padding-right: 5px; } } + .messages { flex: 1 1 100%; - - background-color: #FFF; - border-left: 1px solid #E7E7E7; - border-right: 1px solid #E7E7E7; + background-color: #ffffff; + border-left: 1px solid #e7e7e7; + border-right: 1px solid #e7e7e7; overflow-y: auto; + .wrapper { padding-bottom: 6px; ul { list-style-type: none; padding: 0; + li { padding: 0; } @@ -222,14 +224,15 @@ line-height: 18px; margin: 12px 10px 0; min-height: 36px; + &:nth-child(1) { margin-top: 0; } + &.new-day { margin-top: 60px; - } - &.new-day { - &:before { + + &::before { content: attr(data-date); display: block; position: absolute; @@ -242,43 +245,51 @@ color: @secondary-font-color; z-index: 10; padding: 0 10px; - background-color: #FFF; + background-color: #ffffff; min-width: 120px; } - &:after { + + &::after { content: " "; display: block; position: absolute; top: -20px; left: 0; width: 100%; - border-top: 1px solid #ddd; + border-top: 1px solid #dddddd; } } + .edit-message { display: none; cursor: pointer; } + &.own:hover:not(.system) .edit-message { display: inline-block; } + .delete-message { display: none; cursor: pointer; } + &.own:hover:not(.system) .delete-message { display: inline-block; } + .user { display: inline-block; font-weight: 600; color: #444444; margin-right: 5px; outline: none; + &:hover { - color: #333; + color: #333333; } } + .thumb { position: absolute; left: 0; @@ -287,78 +298,96 @@ width: 30px; height: 30px; } + .info { font-size: 10px; color: @info-font-color; } + &.sequential { - // margin-top: 5px; padding-top: 5px; margin-top: 0; margin-bottom: 0; min-height: 20px; + .user { display: none; } + .thumb { display: none; } + .info { position: absolute; text-align: right; left: -20px; width: 55px; + .time { display: none; } + .edited { display: inline-block; } + .edit-message { float: left; margin-left: 1px; } + .delete-message { float: left; } } + &:hover { .time { display: inline-block; } + .edited { display: none; } } } + &.system { .body { color: @info-font-color; font-style: italic; text-transform: lowercase; + em { font-weight: 600; } } } + .avatar-initials { line-height: 40px; } + a { color: @link-font-color; font-weight: 400; + &:hover { color: darken(@link-font-color, 10%); text-decoration: underline; } } + .body { opacity: 1; - .transition(opacity 1s linear); + transition: opacity 1s linear; } + &.temp .body { - opacity: .5; + opacity: 0.5; } + &.msg-error .body { text-decoration: line-through; } @@ -378,6 +407,7 @@ } } } + .new-message { margin: 0 -65px; position: absolute; @@ -386,15 +416,16 @@ width: 130px; height: 30px; text-align: center; - color: #FFF; + color: #ffffff; line-height: 30px; font-size: 0.8em; cursor: pointer; bottom: 8px; left: 50%; z-index: 5; - .transition(transform 0.3s ease-out); + transition: transform 0.3s ease-out; .transform(translateY(-40px)); + &.not { .transform(translateY(100%)); } @@ -404,11 +435,10 @@ bottom: 40px; position: fixed; width: 100%; - background-color: #F7D799; + background-color: #f7d799; padding: 5px; z-index: 8; - - .transition(transform 0.2s ease-out); + transition: transform 0.2s ease-out; .transform(translateY(100%)); &.show { @@ -416,27 +446,26 @@ } } } + .footer { flex: 1 0 @footer-min-height; - z-index: 10; - - background-color: #FCFCFC; - border-top: 1px solid #E7E7E7; - border-left: 1px solid #E7E7E7; - border-right: 1px solid #E7E7E7; + background-color: #fcfcfc; + border-top: 1px solid #e7e7e7; + border-left: 1px solid #e7e7e7; + border-right: 1px solid #e7e7e7; .input-wrapper { - padding: 6px 6px 0 6px; + padding: 6px 6px 0; padding-right: 30px; + textarea { display: block; padding: 6px 8px; padding-right: 38px; overflow-y: auto; resize: none; - border: 1px solid #E7E7E7; - // margin: 10px; + border: 1px solid #e7e7e7; border-radius: 5px; max-height: 200px; width: 100%; @@ -444,7 +473,7 @@ -webkit-appearance: none; height: 28px; line-height: normal; - background-color: #fff; + background-color: #ffffff; position: relative; outline: none; } @@ -457,7 +486,7 @@ top: -28px; color: @secondary-font-color; cursor: pointer; - .transition(color .15s ease-out); + transition: color 0.15s ease-out; &:hover { color: @primary-font-color; @@ -469,8 +498,8 @@ flex: 1 1 100%; background-color: white; padding: 1em 10px; - border-left: 1px solid #E7E7E7; - border-right: 1px solid #E7E7E7; + border-left: 1px solid #e7e7e7; + border-right: 1px solid #e7e7e7; .offline-message { padding: 1em 0; @@ -481,7 +510,8 @@ } form { - input, textarea { + input, + textarea { display: block; width: 100%; } @@ -496,7 +526,7 @@ .error { display: none; - background-color: #F7D799; + background-color: #f7d799; padding: 5px; &.show { @@ -508,18 +538,23 @@ } } - &.closed, &.closed-offline { + &.closed, + &.closed-offline { .preview-wrapper { height: 32px; + .livechat-room .title .toolbar { display: none; } + .messages { display: none; } + .footer { display: none; } + .offline { display: none; } @@ -532,7 +567,7 @@ li { display: inline-block; - background-color: #DDD; + background-color: #dddddd; border-radius: 10px; padding: 2px 8px 2px 2px; margin: 1px 0; @@ -553,40 +588,16 @@ } } -.user-view { - li { - color: @secondary-font-color; - line-height: 18px; - font-size: 12px; - font-weight: 300; - } -} - -.icon-chat-empty.status-offline { - color: @status-offline; -} - -.icon-chat-empty.status-online { - color: @status-online; -} - -.icon-chat-empty.status-busy { - color: @status-busy; -} - -.icon-chat-empty.status-away { - color: @status-away; -} - .visitor-custom-fields { padding: 0 20px; } -.visitor-navigation, .visitor-custom-fields { +.visitor-navigation, +.visitor-custom-fields { .visitor-scroll { height: 130px; overflow-y: auto; - border: 1px solid #E7E7E7; + border: 1px solid #e7e7e7; border-radius: 4px; padding: 4px; margin-top: 4px; @@ -600,7 +611,6 @@ text-overflow: ellipsis; display: block; overflow: hidden; - color: @secondary-font-color; text-decoration: underline; @@ -615,8 +625,7 @@ .livechat-section { opacity: 0.5; - - .transition(opacity .4s ease); + transition: opacity 0.4s ease; &.available { opacity: 1; @@ -627,21 +636,16 @@ float: right; margin-right: 10px; font-size: 20px; - &.available { - color: @status-online; - } - &.not-available { - color: @status-offline; - } } .external-message { padding: 10px; position: relative; - &:after { + + &::after { content: " "; position: absolute; - border-bottom: 1px solid #CCC; + border-bottom: 1px solid #cccccc; left: 10px; right: 10px; bottom: 0; @@ -649,9 +653,17 @@ } .user-view { + li { + color: @secondary-font-color; + line-height: 18px; + font-size: 12px; + font-weight: 300; + } + nav.centered-buttons { text-align: center; margin-bottom: 1em; + button { display: inline-block; width: auto; @@ -669,6 +681,7 @@ .visitor-edit { padding: 20px; + h3 { font-size: 24px; margin-bottom: 8px; diff --git a/packages/rocketchat-livechat/client/stylesheets/load.js b/packages/rocketchat-livechat/client/stylesheets/load.js deleted file mode 100644 index 3376dd0f6fd87d5408e7d56a33fc30e95f7b8bc3..0000000000000000000000000000000000000000 --- a/packages/rocketchat-livechat/client/stylesheets/load.js +++ /dev/null @@ -1,3 +0,0 @@ -RocketChat.theme.addPackageAsset(() => { - return Assets.getText('client/stylesheets/livechat.less'); -}); diff --git a/packages/rocketchat-livechat/client/views/app/livechatAppearance.html b/packages/rocketchat-livechat/client/views/app/livechatAppearance.html index 430533d3493439b9389188e382530147be201152..a8d9651ef9697f1f6fec87df35b93374403ba704 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatAppearance.html +++ b/packages/rocketchat-livechat/client/views/app/livechatAppearance.html @@ -1,148 +1,150 @@ <template name="livechatAppearance"> - <div class="livechat-settings-div"> - <h2>{{_ "Settings"}}</h2> + {{#requiresPermission 'view-livechat-manager'}} + <div class="livechat-settings-div"> + <h2>{{_ "Settings"}}</h2> - <form class="rocket-form"> - <fieldset> - <legend>{{_ "Livechat_online"}}</legend> - <div class="input-line"> - <label for="title">{{_ "Title"}}</label> - <input type="text" class="preview-settings" name="title" id="title" value="{{title}}"> - </div> - <div class="input-line"> - <label for="color">{{_ "Title_bar_color"}}</label> - <input type="text" class="preview-settings minicolors" name="color" id="color" value="{{color}}"> - </div> - </fieldset> - <fieldset> - <legend>{{_ "Livechat_offline"}}</legend> - <div class="input-line"> - <label for="displayOfflineForm">{{_ "Display_offline_form"}}</label> - <div class="inline-fields"> - <input type="radio" class="preview-settings" name="displayOfflineForm" id="displayOfflineFormTrue" checked="{{displayOfflineFormTrueChecked}}" value="true"> - <label for="displayOfflineFormTrue">{{_ "True"}}</label> - <input type="radio" class="preview-settings" name="displayOfflineForm" id="displayOfflineFormFalse" checked="{{displayOfflineFormFalseChecked}}" value="false"> - <label for="displayOfflineFormFalse">{{_ "False"}}</label> + <form class="rocket-form"> + <fieldset> + <legend>{{_ "Livechat_online"}}</legend> + <div class="input-line"> + <label for="title">{{_ "Title"}}</label> + <input type="text" class="preview-settings" name="title" id="title" value="{{title}}"> </div> + <div class="input-line"> + <label for="color">{{_ "Title_bar_color"}}</label> + <input type="text" class="preview-settings minicolors" name="color" id="color" value="{{color}}"> + </div> + </fieldset> + <fieldset> + <legend>{{_ "Livechat_offline"}}</legend> + <div class="input-line"> + <label for="displayOfflineForm">{{_ "Display_offline_form"}}</label> + <div class="inline-fields"> + <input type="radio" class="preview-settings" name="displayOfflineForm" id="displayOfflineFormTrue" checked="{{displayOfflineFormTrueChecked}}" value="true"> + <label for="displayOfflineFormTrue">{{_ "True"}}</label> + <input type="radio" class="preview-settings" name="displayOfflineForm" id="displayOfflineFormFalse" checked="{{displayOfflineFormFalseChecked}}" value="false"> + <label for="displayOfflineFormFalse">{{_ "False"}}</label> + </div> + </div> + <div class="input-line"> + <label for="offlineUnavailableMessage">{{_ "Offline_form_unavailable_message"}}</label> + <textarea class="preview-settings" name="offlineUnavailableMessage" id="offlineUnavailableMessage">{{offlineUnavailableMessage}}</textarea> + </div> + <div class="input-line"> + <label for="offlineMessage">{{_ "Offline_message"}}</label> + <textarea class="preview-settings" name="offlineMessage" id="offlineMessage">{{offlineMessage}}</textarea> + </div> + <div class="input-line"> + <label for="titleOffline">{{_ "Title_offline"}}</label> + <input type="text" class="preview-settings" name="titleOffline" id="titleOffline" value="{{titleOffline}}"> + </div> + <div class="input-line"> + <label for="colorOffline">{{_ "Title_bar_color_offline"}}</label> + <input type="text" class="preview-settings minicolors" name="colorOffline" id="colorOffline" value="{{colorOffline}}"> + </div> + <div class="input-line"> + <label for="emailOffline">{{_ "Email_address_to_send_offline_messages"}}</label> + <input type="text" class="preview-settings" name="emailOffline" id="emailOffline" value="{{emailOffline}}"> + </div> + <div class="input-line"> + <label for="offlineSuccessMessage">{{_ "Offline_success_message"}}</label> + <textarea class="preview-settings" name="offlineSuccessMessage" id="offlineSuccessMessage">{{offlineSuccessMessage}}</textarea> + </div> + </fieldset> + <div class="submit"> + <button class="button secondary reset-settings"><i class="icon-ccw"></i>{{_ "Reset"}}</button> + <button class="button primary save"><i class="icon-floppy"></i>{{_ "Save"}}</button> </div> - <div class="input-line"> - <label for="offlineUnavailableMessage">{{_ "Offline_form_unavailable_message"}}</label> - <textarea class="preview-settings" name="offlineUnavailableMessage" id="offlineUnavailableMessage">{{offlineUnavailableMessage}}</textarea> - </div> - <div class="input-line"> - <label for="offlineMessage">{{_ "Offline_message"}}</label> - <textarea class="preview-settings" name="offlineMessage" id="offlineMessage">{{offlineMessage}}</textarea> - </div> - <div class="input-line"> - <label for="titleOffline">{{_ "Title_offline"}}</label> - <input type="text" class="preview-settings" name="titleOffline" id="titleOffline" value="{{titleOffline}}"> - </div> - <div class="input-line"> - <label for="colorOffline">{{_ "Title_bar_color_offline"}}</label> - <input type="text" class="preview-settings minicolors" name="colorOffline" id="colorOffline" value="{{colorOffline}}"> - </div> - <div class="input-line"> - <label for="emailOffline">{{_ "Email_address_to_send_offline_messages"}}</label> - <input type="text" class="preview-settings" name="emailOffline" id="emailOffline" value="{{emailOffline}}"> - </div> - <div class="input-line"> - <label for="offlineSuccessMessage">{{_ "Offline_success_message"}}</label> - <textarea class="preview-settings" name="offlineSuccessMessage" id="offlineSuccessMessage">{{offlineSuccessMessage}}</textarea> - </div> - </fieldset> - <div class="submit"> - <button class="button secondary reset-settings"><i class="icon-ccw"></i>{{_ "Reset"}}</button> - <button class="button primary save"><i class="icon-floppy"></i>{{_ "Save"}}</button> - </div> - </form> - </div> + </form> + </div> - <div class="livechat-preview-div"> - <h2>{{_ "Preview"}}</h2> + <div class="livechat-preview-div"> + <h2>{{_ "Preview"}}</h2> - <select class="preview-mode"> - <optgroup label="{{_ "Online"}}"> - <option value="opened">{{_ "Chat_window"}}</option> - <option value="closed">{{_ "Chat_button"}}</option> - </optgroup> - <optgroup label="{{_ "Offline"}}"> - <option value="opened-offline">{{_ "Offline_form"}}</option> - <option value="offline-unavailable">{{_ "Offline_unavailable"}}</option> - <option value="offline-success">{{_ "Success_message"}}</option> - <option value="closed-offline">{{_ "Chat_button"}}</option> - </optgroup> - </select> + <select class="preview-mode"> + <optgroup label="{{_ "Online"}}"> + <option value="opened">{{_ "Chat_window"}}</option> + <option value="closed">{{_ "Chat_button"}}</option> + </optgroup> + <optgroup label="{{_ "Offline"}}"> + <option value="opened-offline">{{_ "Offline_form"}}</option> + <option value="offline-unavailable">{{_ "Offline_unavailable"}}</option> + <option value="offline-success">{{_ "Success_message"}}</option> + <option value="closed-offline">{{_ "Chat_button"}}</option> + </optgroup> + </select> - <div class="livechat-preview {{previewState}}"> - <div class="preview-wrapper"> - {{#with sampleData}} - <div class="livechat-room"> - <div class="title" style="background-color:{{sampleColor}}"> - <div class="toolbar"> - - <i class="popout icon-link-ext" title="Open in a new window"></i> - </div> - <h1>{{sampleTitle}}</h1> - </div> - {{#if showOnline}} - <div class="messages"> - <div class="wrapper"> - <ul> - {{#each messages}} - <li id="{{_id}}" class="message {{sequential}}" data-username="{{u.username}}" data-date="{{date}}"> - <span class="thumb thumb-small" data-username="{{u.username}}" tabindex="1">{{> avatar username=u.username}}</span> - <span class="user" data-username="{{u.username}}" tabindex="1">{{u.username}}</span> - <span class="info"> - <span class="time">{{time}}</span> - </span> - <div class="body" dir="auto"> - {{{body}}} - </div> - </li> - {{/each}} - </ul> + <div class="livechat-preview {{previewState}}"> + <div class="preview-wrapper"> + {{#with sampleData}} + <div class="livechat-room"> + <div class="title" style="background-color:{{sampleColor}}"> + <div class="toolbar"> + + <i class="popout icon-link-ext" title="Open in a new window"></i> </div> + <h1>{{sampleTitle}}</h1> </div> - <div class="footer"> - <div class="input-wrapper"> - <textarea class="input-message" placeholder="Type your message"></textarea> + {{#if showOnline}} + <div class="messages"> + <div class="wrapper"> + <ul> + {{#each messages}} + <li id="{{_id}}" class="message {{sequential}}" data-username="{{u.username}}" data-date="{{date}}"> + <span class="thumb thumb-small" data-username="{{u.username}}" tabindex="1">{{> avatar username=u.username}}</span> + <span class="user" data-username="{{u.username}}" tabindex="1">{{u.username}}</span> + <span class="info"> + <span class="time">{{time}}</span> + </span> + <div class="body" dir="auto"> + {{{body}}} + </div> + </li> + {{/each}} + </ul> + </div> </div> - <i class="send-button icon-paper-plane" aria-label="{{_ "Send"}}"></i> - </div> - {{/if}} + <div class="footer"> + <div class="input-wrapper"> + <textarea class="input-message" placeholder="Type your message"></textarea> + </div> + <i class="send-button icon-paper-plane" aria-label="{{_ "Send"}}"></i> + </div> + {{/if}} - {{#if showOfflineForm}} - <div class="offline"> - <p class="offline-message">{{{sampleOfflineMessage}}}</p> + {{#if showOfflineForm}} + <div class="offline"> + <p class="offline-message">{{{sampleOfflineMessage}}}</p> - <form> - <input type="text" id="name" name="name" placeholder="{{_ "Type_your_name"}}"> + <form> + <input type="text" id="name" name="name" placeholder="{{_ "Type_your_name"}}"> - <input type="email" id="email" name="email" placeholder="{{_ "Type_your_email"}}"> + <input type="email" id="email" name="email" placeholder="{{_ "Type_your_email"}}"> - <textarea id="message" name="message" placeholder="{{_"Type_your_message"}}" rows="2"></textarea> + <textarea id="message" name="message" placeholder="{{_"Type_your_message"}}" rows="2"></textarea> - <div class="buttons"> - <button class="button primary send">{{_ "Send"}}</button> - </div> - </form> - </div> - {{/if}} + <div class="buttons"> + <button class="button primary send">{{_ "Send"}}</button> + </div> + </form> + </div> + {{/if}} - {{#if showOfflineSuccess}} - <div class="offline"> - <p class="message-sent">{{{sampleOfflineSuccessMessage}}}</p> - </div> - {{/if}} + {{#if showOfflineSuccess}} + <div class="offline"> + <p class="message-sent">{{{sampleOfflineSuccessMessage}}}</p> + </div> + {{/if}} - {{#if showOfflineUnavailable}} - <div class="offline"> - <p class="offline-message">{{{sampleOfflineUnavailableMessage}}}</p> - </div> - {{/if}} - </div> - {{/with}} + {{#if showOfflineUnavailable}} + <div class="offline"> + <p class="offline-message">{{{sampleOfflineUnavailableMessage}}}</p> + </div> + {{/if}} + </div> + {{/with}} + </div> </div> </div> - </div> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.html b/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.html index 2229140be81e7314745d07c93bb892cb45753b36..9b66c52ac3f100f8d3e64ec051e74a14bb9ed19c 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.html +++ b/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.html @@ -1,65 +1,64 @@ <template name="livechatCurrentChats"> - <fieldset> - <form class="form-inline" method="post"> - <div class="form-group"> - <label for="name">{{_ "Name"}}</label> - <input type="text" name="name"> - </div> + {{#requiresPermission 'view-livechat-manager'}} + <fieldset> + <form class="form-inline" method="post"> + <div class="form-group"> + <label for="name">{{_ "Name"}}</label> + <input type="text" name="name"> + </div> - <div class="form-group"> - <label for="agent">{{_ "Served_By"}}</label> - <select name="agent"> - <option value=""></option> - {{#each agents}} - <option value="{{_id}}">{{username}}</option> - {{/each}} - </select> - </div> - <div class="form-group"> - <label for="status">{{_ "Status"}}</label> - <select name="status"> - <option value=""></option> - <option value="opened">{{_ "Opened"}}</option> - <option value="closed">{{_ "Closed"}}</option> - </select> - </div> - <!--Added by Deepankar--> - <div class="input-group input-daterange" data-date-autoclose="true" data-date-todayHighlight="true" data-provide="datepicker" > - <span class="input-group-addon">From</span> + <div class="form-group"> + <label for="agent">{{_ "Served_By"}}</label> + <select name="agent"> + <option value=""></option> + {{#each agents}} + <option value="{{_id}}">{{username}}</option> + {{/each}} + </select> + </div> + <div class="form-group"> + <label for="status">{{_ "Status"}}</label> + <select name="status"> + <option value=""></option> + <option value="opened">{{_ "Opened"}}</option> + <option value="closed">{{_ "Closed"}}</option> + </select> + </div> + <div class="form-group input-daterange"> + <span class="input-group-addon">{{_ "Date_From"}}</span> <input type="text" class="form-control" id="from" name="from" > - <span class="input-group-addon">to</span> + <span class="input-group-addon">{{_ "Date_to"}}</span> <input type="text" class="form-control" id="to" name="to"> - - <button class="button">{{_ "Filter"}}</button> </div> - <!--Added by Deepankar--> - </form> - </fieldset> - <div class="list"> - <table> - <thead> - <tr> - <th width="25%">{{_ "Name"}}</th> - <th width="25%">{{_ "Served_By"}}</th> - <th width="15%">{{_ "Started_At"}}</th> - <th width="15%">{{_ "Last_Message_At"}}</th> - <th width="10%">{{_ "Status"}}</th> - </tr> - </thead> - <tbody> - {{#each livechatRoom}} - <tr class="row-link"> - <td>{{label}}</td> - <td>{{servedBy}}</td> - <td>{{startedAt}}</td> - <td>{{lastMessage}}</td> - <td>{{status}}</td> + <button class="button">{{_ "Filter"}}</button> + </form> + </fieldset> + <div class="list"> + <table> + <thead> + <tr> + <th width="25%">{{_ "Name"}}</th> + <th width="25%">{{_ "Served_By"}}</th> + <th width="15%">{{_ "Started_At"}}</th> + <th width="15%">{{_ "Last_Message_At"}}</th> + <th width="10%">{{_ "Status"}}</th> </tr> - {{/each}} - </tbody> - </table> - </div> - <div class="text-center"> - <button class="button load-more">{{_ "Load_more"}}</button> - </div> + </thead> + <tbody> + {{#each livechatRoom}} + <tr class="row-link"> + <td>{{label}}</td> + <td>{{servedBy}}</td> + <td>{{startedAt}}</td> + <td>{{lastMessage}}</td> + <td>{{status}}</td> + </tr> + {{/each}} + </tbody> + </table> + </div> + <div class="text-center"> + <button class="button load-more">{{_ "Load_more"}}</button> + </div> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.js b/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.js index 57bcdbd87b9c961974d70ab7cf6cb4292578a562..810391ba94bc74dfde6d7841794ef3c0f7ba8788 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.js +++ b/packages/rocketchat-livechat/client/views/app/livechatCurrentChats.js @@ -1,8 +1,10 @@ import moment from 'moment'; +const LivechatRoom = new Mongo.Collection('livechatRoom'); + Template.livechatCurrentChats.helpers({ livechatRoom() { - return ChatRoom.find({ t: 'l' }, { sort: { ts: -1 } }); + return LivechatRoom.find({ t: 'l' }, { sort: { ts: -1 } }); }, startedAt() { return moment(this.ts).format('L LTS'); @@ -38,6 +40,18 @@ Template.livechatCurrentChats.events({ } }); + if (!_.isEmpty(filter.from)) { + filter.from = moment(filter.from, moment.localeData().longDateFormat('L')).toDate(); + } else { + delete filter.from; + } + + if (!_.isEmpty(filter.to)) { + filter.to = moment(filter.to, moment.localeData().longDateFormat('L')).toDate(); + } else { + delete filter.to; + } + instance.filter.set(filter); instance.limit.set(20); } @@ -53,3 +67,11 @@ Template.livechatCurrentChats.onCreated(function() { this.subscribe('livechat:rooms', this.filter.get(), 0, this.limit.get()); }); }); + +Template.livechatCurrentChats.onRendered(function() { + this.$('.input-daterange').datepicker({ + autoclose: true, + todayHighlight: true, + format: moment.localeData().longDateFormat('L').toLowerCase() + }); +}); diff --git a/packages/rocketchat-livechat/client/views/app/livechatCustomFieldForm.html b/packages/rocketchat-livechat/client/views/app/livechatCustomFieldForm.html index 3dbfaa536b6d1c9c8c67c132435335c7cb46cd4c..991128748682dabc11713d3e230fcac2016d855c 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatCustomFieldForm.html +++ b/packages/rocketchat-livechat/client/views/app/livechatCustomFieldForm.html @@ -1,46 +1,48 @@ <template name="livechatCustomFieldForm"> - <form id="customField-form" data-id="{{customField._id}}"> - <div class="rocket-form"> - {{#if Template.subscriptionsReady}} - <fieldset> - <div class="input-line"> - <label>{{_ "Field"}}</label> - <div> - <input type="text" name="field" value="{{customField._id}}" readonly="{{$exists customField._id}}" placeholder="{{_ "Field"}}" /> + {{#requiresPermission 'view-livechat-manager'}} + <form id="customField-form" data-id="{{customField._id}}"> + <div class="rocket-form"> + {{#if Template.subscriptionsReady}} + <fieldset> + <div class="input-line"> + <label>{{_ "Field"}}</label> + <div> + <input type="text" name="field" value="{{customField._id}}" readonly="{{$exists customField._id}}" placeholder="{{_ "Field"}}" /> + </div> </div> - </div> - <div class="input-line"> - <label>{{_ "Label"}}</label> - <div> - <input type="text" name="label" value="{{customField.label}}" placeholder="{{_ "Label"}}" /> + <div class="input-line"> + <label>{{_ "Label"}}</label> + <div> + <input type="text" name="label" value="{{customField.label}}" placeholder="{{_ "Label"}}" /> + </div> </div> - </div> - <div class="input-line"> - <label>{{_ "Scope"}}</label> - <div> - <select name="scope"> - <option value="visitor" selected="{{$eq customField.scope 'visitor'}}">{{_ "Visitor"}}</option> - <option value="room" selected="{{$eq customField.scope 'room'}}">{{_ "Room"}}</option> - </select> + <div class="input-line"> + <label>{{_ "Scope"}}</label> + <div> + <select name="scope"> + <option value="visitor" selected="{{$eq customField.scope 'visitor'}}">{{_ "Visitor"}}</option> + <option value="room" selected="{{$eq customField.scope 'room'}}">{{_ "Room"}}</option> + </select> + </div> </div> - </div> - <div class="input-line"> - <label>{{_ "Visibility"}}</label> - <div> - <select name="visibility"> - <option value="visible" selected="{{$eq customField.visibility 'visible'}}">{{_ "Visible"}}</option> - <option value="hidden" selected="{{$eq customField.visibility 'hidden'}}">{{_ "Hidden"}}</option> - </select> + <div class="input-line"> + <label>{{_ "Visibility"}}</label> + <div> + <select name="visibility"> + <option value="visible" selected="{{$eq customField.visibility 'visible'}}">{{_ "Visible"}}</option> + <option value="hidden" selected="{{$eq customField.visibility 'hidden'}}">{{_ "Hidden"}}</option> + </select> + </div> </div> + </fieldset> + <div class="submit"> + <button class="button back" type="button"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button> + <button class="button primary save"><i class="icon-floppy"></i><span>{{_ "Save"}}</span></button> </div> - </fieldset> - <div class="submit"> - <button class="button back" type="button"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button> - <button class="button primary save"><i class="icon-floppy"></i><span>{{_ "Save"}}</span></button> - </div> - {{else}} - {{> loading}} - {{/if}} - </div> - </form> + {{else}} + {{> loading}} + {{/if}} + </div> + </form> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatCustomFields.html b/packages/rocketchat-livechat/client/views/app/livechatCustomFields.html index c6d178d0af214f06f8969b730f1317c1057412c2..b03734f64485f218b68b9d7f9682220adfca658a 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatCustomFields.html +++ b/packages/rocketchat-livechat/client/views/app/livechatCustomFields.html @@ -1,27 +1,29 @@ <template name="livechatCustomFields"> - <div class="list"> - <table> - <thead> - <tr> - <th width="25%">{{_ "Field"}}</th> - <th width="25%">{{_ "Label"}}</th> - <th width="25%">{{_ "Scope"}}</th> - <th width="25%">{{_ "Visibility"}}</th> - <th>{{_ "Delete"}}</th> - </tr> - </thead> - <tbody> - {{#each customFields}} - <tr class="custom-field-info row-link" data-id="{{_id}}"> - <td>{{_id}}</td> - <td>{{label}}</td> - <td>{{scope}}</td> - <td>{{visibility}}</td> - <td><a href="#remove" class="remove-custom-field"><i class="icon-trash"></i></a></td> + {{#requiresPermission 'view-livechat-manager'}} + <div class="list"> + <table> + <thead> + <tr> + <th width="25%">{{_ "Field"}}</th> + <th width="25%">{{_ "Label"}}</th> + <th width="25%">{{_ "Scope"}}</th> + <th width="25%">{{_ "Visibility"}}</th> + <th>{{_ "Delete"}}</th> </tr> - {{/each}} - </tbody> - </table> - </div> - <a href="{{pathFor 'livechat-customfield-new'}}" class="button primary">{{_ "New_Custom_Field"}}</a> + </thead> + <tbody> + {{#each customFields}} + <tr class="custom-field-info row-link" data-id="{{_id}}"> + <td>{{_id}}</td> + <td>{{label}}</td> + <td>{{scope}}</td> + <td>{{visibility}}</td> + <td><a href="#remove" class="remove-custom-field"><i class="icon-trash"></i></a></td> + </tr> + {{/each}} + </tbody> + </table> + </div> + <a href="{{pathFor 'livechat-customfield-new'}}" class="button primary">{{_ "New_Custom_Field"}}</a> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatDashboard.html b/packages/rocketchat-livechat/client/views/app/livechatDashboard.html index ae5a2b7df4fffcd938e5e53b0283ee761966c868..d5e3d425edac395dc52e0b2db4aa03039c7535d0 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDashboard.html +++ b/packages/rocketchat-livechat/client/views/app/livechatDashboard.html @@ -1,3 +1,5 @@ <template name="livechatDashboard"> - <h1>Dashboard</h1> + {{#requiresPermission 'view-livechat-manager'}} + <h1>Dashboard</h1> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html index f9d3e063c9e69623b68a6203abf1aa6423dd8a30..2ea9125710de5f06cf4f1604d6eaf4556eb1d20c 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html @@ -1,82 +1,91 @@ <template name="livechatDepartmentForm"> - <form id="department-form" data-id="{{department._id}}"> - <div class="rocket-form"> - {{#if Template.subscriptionsReady}} - <fieldset> - <div class="input-line"> - <label>{{_ "Enabled"}}</label> - <div> - <label><input type="radio" name="enabled" value="1" checked="{{$eq department.enabled true}}" /> {{_ "Yes"}}</label> - <label><input type="radio" name="enabled" value="0" checked="{{$eq department.enabled false}}" /> {{_ "No"}}</label> + {{#requiresPermission 'view-livechat-manager'}} + <form id="department-form" data-id="{{department._id}}"> + <div class="rocket-form"> + {{#if Template.subscriptionsReady}} + <fieldset> + <div class="input-line"> + <label>{{_ "Enabled"}}</label> + <div> + <label><input type="radio" name="enabled" value="1" checked="{{$eq department.enabled true}}" /> {{_ "Yes"}}</label> + <label><input type="radio" name="enabled" value="0" checked="{{$eq department.enabled false}}" /> {{_ "No"}}</label> + </div> </div> - </div> - <div class="input-line"> - <label>{{_ "Name"}}</label> - <div> - <input type="text" name="name" value="{{department.name}}" placeholder="{{_ "Name"}}" /> + <div class="input-line"> + <label>{{_ "Name"}}</label> + <div> + <input type="text" name="name" value="{{department.name}}" placeholder="{{_ "Name"}}" /> + </div> </div> - </div> - <div class="input-line"> - <label>{{_ "Description"}}</label> - <div> - <textarea name="description" rows="6">{{department.description}}</textarea> + <div class="input-line"> + <label>{{_ "Description"}}</label> + <div> + <textarea name="description" rows="6">{{department.description}}</textarea> + </div> </div> - </div> - <hr /> - <h2>{{_ "Agents"}}</h2> + <div class="input-line"> + <label>{{_ "Show_on_registration_page"}}</label> + <div> + <label><input type="radio" name="showOnRegistration" value="1" checked="{{showOnRegistration true}}" /> {{_ "Yes"}}</label> + <label><input type="radio" name="showOnRegistration" value="0" checked="{{showOnRegistration false}}" /> {{_ "No"}}</label> + </div> + </div> + <hr /> + <h2>{{_ "Agents"}}</h2> - <fieldset> - <legend>{{_ "Available_agents"}}</legend> + <fieldset> + <legend>{{_ "Available_agents"}}</legend> - <ul class="department-agents available-agents"> - {{#each availableAgents}} - <li><i class="icon-plus-circled"></i>{{username}}</li> - {{/each}} - </ul> - </fieldset> + <ul class="department-agents available-agents"> + {{#each availableAgents}} + <li><i class="icon-plus-circled"></i>{{username}}</li> + {{/each}} + </ul> + </fieldset> - <fieldset> - <legend>{{_ "Selected_agents"}}</legend> + <fieldset> + <legend>{{_ "Selected_agents"}}</legend> - <div class="list"> - <table> - <thead> - <tr> - <th width="25%">{{_ "Username"}}</th> - <th>{{_ "Count"}}</th> - <th>{{_ "Order"}}</th> - <th> </th> - </tr> - </thead> - <tbody> - {{#if selectedAgents}} - {{#each selectedAgents}} - <tr class="agent-info"> - <td>{{username}}</td> - <td><input type="text" class="count-{{agentId}}" name="count" value="{{count}}" size="3"></td> - <td><input type="text" class="order-{{agentId}}" name="order" value="{{order}}" size="3"></td> - <td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td> - </tr> - {{/each}} - {{else}} + <div class="list"> + <table> + <thead> <tr> - <td colspan="4">{{_ "There_are_no_agents_added_to_this_department_yet"}}</td> + <th width="25%">{{_ "Username"}}</th> + <th>{{_ "Count"}}</th> + <th>{{_ "Order"}}</th> + <th> </th> </tr> - {{/if}} - </tbody> - </table> - </div> + </thead> + <tbody> + {{#if selectedAgents}} + {{#each selectedAgents}} + <tr class="agent-info"> + <td>{{username}}</td> + <td><input type="text" class="count-{{agentId}}" name="count" value="{{count}}" size="3"></td> + <td><input type="text" class="order-{{agentId}}" name="order" value="{{order}}" size="3"></td> + <td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td> + </tr> + {{/each}} + {{else}} + <tr> + <td colspan="4">{{_ "There_are_no_agents_added_to_this_department_yet"}}</td> + </tr> + {{/if}} + </tbody> + </table> + </div> - </fieldset> + </fieldset> - </fieldset> - <div class="submit"> - <button class="button back" type="button"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button> - <button class="button primary save"><i class="icon-floppy"></i><span>{{_ "Save"}}</span></button> - </div> - {{else}} - {{> loading}} - {{/if}} - </div> - </form> + </fieldset> + <div class="submit"> + <button class="button back" type="button"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button> + <button class="button primary save"><i class="icon-floppy"></i><span>{{_ "Save"}}</span></button> + </div> + {{else}} + {{> loading}} + {{/if}} + </div> + </form> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js index ec40ba225e12ad95ab11b0a883122d532c3c6d7c..4b3825a8caa861eb3d3304479cf27c280299595f 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js @@ -12,6 +12,10 @@ Template.livechatDepartmentForm.helpers({ availableAgents() { var selected = _.pluck(Template.instance().selectedAgents.get(), 'username'); return AgentUsers.find({ username: { $nin: selected }}, { sort: { username: 1 } }); + }, + showOnRegistration(value) { + let department = Template.instance().department.get(); + return department.showOnRegistration === value || (department.showOnRegistration === undefined && value === true); } }); @@ -24,6 +28,7 @@ Template.livechatDepartmentForm.events({ var enabled = instance.$('input[name=enabled]:checked').val(); var name = instance.$('input[name=name]').val(); var description = instance.$('textarea[name=description]').val(); + var showOnRegistration = instance.$('input[name=showOnRegistration]:checked').val(); if (enabled !== '1' && enabled !== '0') { return toastr.error(t('Please_select_enabled_yes_or_no')); @@ -39,7 +44,8 @@ Template.livechatDepartmentForm.events({ var departmentData = { enabled: enabled === '1' ? true : false, name: name.trim(), - description: description.trim() + description: description.trim(), + showOnRegistration: showOnRegistration === '1' ? true : false }; var departmentAgents = []; diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartments.html b/packages/rocketchat-livechat/client/views/app/livechatDepartments.html index 4da6e4e5fecd9be9d7e985145c8cebdd5d61de82..49a85d77eefc709d701db058ab057980d6e2c55d 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartments.html +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartments.html @@ -1,28 +1,32 @@ <template name="livechatDepartments"> - <div class="list"> - <table> - <thead> - <tr> - <th width="25%">{{_ "Name"}}</th> - <th width="25%">{{_ "Description"}}</th> - <th width="25%">{{_ "Num_Agents"}}</th> - <th width="25%">{{_ "Enabled"}}</th> - <th>{{_ "Delete"}}</th> - </tr> - </thead> - <tbody> - {{#each departments}} - <tr class="department-info row-link" data-id="{{_id}}"> - <td>{{name}}</td> - <td>{{description}}</td> - <td>{{numAgents}}</td> - <td>{{#if enabled}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td> - <td><a href="#remove" class="remove-department"><i class="icon-trash"></i></a></td> + {{#requiresPermission 'view-livechat-manager'}} + <div class="list"> + <table> + <thead> + <tr> + <th width="20%">{{_ "Name"}}</th> + <th width="30%">{{_ "Description"}}</th> + <th width="10%">{{_ "Num_Agents"}}</th> + <th width="20%">{{_ "Enabled"}}</th> + <th width="20%">{{_ "Show_on_registration_page"}}</th> + <th>{{_ "Delete"}}</th> </tr> - {{/each}} - </tbody> - </table> - </div> + </thead> + <tbody> + {{#each departments}} + <tr class="department-info row-link" data-id="{{_id}}"> + <td>{{name}}</td> + <td>{{description}}</td> + <td>{{numAgents}}</td> + <td>{{#if enabled}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td> + <td>{{#if showOnRegistration}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td> + <td><a href="#remove" class="remove-department"><i class="icon-trash"></i></a></td> + </tr> + {{/each}} + </tbody> + </table> + </div> - <a href="{{pathFor 'livechat-department-new'}}" class="button primary">{{_ "New_Department"}}</a> + <a href="{{pathFor 'livechat-department-new'}}" class="button primary">{{_ "New_Department"}}</a> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatInstallation.html b/packages/rocketchat-livechat/client/views/app/livechatInstallation.html index 6d912bb3cca378198721177f7d2cefa6f1f5d4b3..92d646c9052d17de44544f09e941b1c442d1f305 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatInstallation.html +++ b/packages/rocketchat-livechat/client/views/app/livechatInstallation.html @@ -1,8 +1,10 @@ <template name="livechatInstallation"> - <p>{{{_ "To_install_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site"}}}</p> + {{#requiresPermission 'view-livechat-manager'}} + <p>{{{_ "To_install_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site"}}}</p> - <div class="livechat-code"> - <textarea class="clipboard" data-clipboard-target=".livechat-code textarea">{{script}}</textarea> - <button class="button clipboard" data-clipboard-target=".livechat-code textarea"><i class="icon-docs"></i>{{_ "Copy_to_clipboard"}}</button> - </div> + <div class="livechat-code"> + <textarea class="clipboard" data-clipboard-target=".livechat-code textarea">{{script}}</textarea> + <button class="button clipboard" data-clipboard-target=".livechat-code textarea"><i class="icon-docs"></i>{{_ "Copy_to_clipboard"}}</button> + </div> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatIntegrations.html b/packages/rocketchat-livechat/client/views/app/livechatIntegrations.html index 3803a76673fc6a6359674aa8542555e630abd49e..61670e4cad50985417f354860ec7d41f522fc365 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatIntegrations.html +++ b/packages/rocketchat-livechat/client/views/app/livechatIntegrations.html @@ -1,41 +1,43 @@ <template name="livechatIntegrations"> - <div class="rocket-form"> - <h2>{{_ "Webhooks"}}</h2> - <p> - {{_ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM"}} - <a href="https://rocket.chat/docs/administrator-guides/livechat/#integrations">{{_ "Click_here"}}</a> {{_ "to_see_more_details_on_how_to_integrate"}} - </p> + {{#requiresPermission 'view-livechat-manager'}} + <div class="rocket-form"> + <h2>{{_ "Webhooks"}}</h2> + <p> + {{_ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM"}} + <a href="https://rocket.chat/docs/administrator-guides/livechat/#integrations">{{_ "Click_here"}}</a> {{_ "to_see_more_details_on_how_to_integrate"}} + </p> - <form id="integration-form"> - <div class="input-line"> - <label for="webhookUrl">{{_ "Webhook_URL"}}</label> - <div> - <input type="url" name="webhookUrl" id="webhookUrl" value="{{webhookUrl}}" placeholder="https://yourdomain.com/webhook/entrypoint"> + <form id="integration-form"> + <div class="input-line"> + <label for="webhookUrl">{{_ "Webhook_URL"}}</label> + <div> + <input type="url" name="webhookUrl" id="webhookUrl" value="{{webhookUrl}}" placeholder="https://yourdomain.com/webhook/entrypoint"> + </div> </div> - </div> - <div class="input-line"> - <label for="secretToken">{{_ "Secret_token"}}</label> - <div> - <input type="text" name="secretToken" id="secretToken" value="{{secretToken}}"> + <div class="input-line"> + <label for="secretToken">{{_ "Secret_token"}}</label> + <div> + <input type="text" name="secretToken" id="secretToken" value="{{secretToken}}"> + </div> </div> - </div> - <div class="input-line"> - <label for="sendOnClose"> - <input type="checkbox" name="sendOnClose" id="sendOnClose" value="1" checked="{{sendOnCloseChecked}}"> - {{_ "Send_request_on_chat_close"}} - </label> - </div> - <div class="input-line"> - <label for="sendOnOffline"> - <input type="checkbox" name="sendOnOffline" id="sendOnOffline" value="1" checked="{{sendOnOfflineChecked}}"> - {{_ "Send_request_on_offline_messages"}} - </label> - </div> - <div class="submit"> - <button class="button danger reset-settings" type="button"><i class="icon-ccw"></i>{{_ "Reset"}}</button> - <button class="button secondary test" type="button" disabled="{{disableTest}}">{{_ "Send_Test"}}</button> - <button class="button primary save"><i class="icon-floppy"></i>{{_ "Save"}}</button> - </div> - </form> - </div> + <div class="input-line"> + <label for="sendOnClose"> + <input type="checkbox" name="sendOnClose" id="sendOnClose" value="1" checked="{{sendOnCloseChecked}}"> + {{_ "Send_request_on_chat_close"}} + </label> + </div> + <div class="input-line"> + <label for="sendOnOffline"> + <input type="checkbox" name="sendOnOffline" id="sendOnOffline" value="1" checked="{{sendOnOfflineChecked}}"> + {{_ "Send_request_on_offline_messages"}} + </label> + </div> + <div class="submit"> + <button class="button danger reset-settings" type="button"><i class="icon-ccw"></i>{{_ "Reset"}}</button> + <button class="button secondary test" type="button" disabled="{{disableTest}}">{{_ "Send_Test"}}</button> + <button class="button primary save"><i class="icon-floppy"></i>{{_ "Save"}}</button> + </div> + </form> + </div> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.html b/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.html index d6f03caacefcc481d00c27b62164efc53def78bb..d662002f4a7e83f758538bf1d94a07cfa1e0f34c 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.html +++ b/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.html @@ -1,59 +1,61 @@ <template name="livechatOfficeHours"> - <div class="livechat-officeHours-div"> - <form class="rocket-form" id="officeHoursForm"> + {{#requiresPermission 'view-livechat-manager'}} + <div class="livechat-officeHours-div"> + <form class="rocket-form" id="officeHoursForm"> - <fieldset> - <legend>{{_ "Office_hours_enabled"}}</legend> - <input type="radio" class="preview-settings" name="enableOfficeHours" id="enableOfficeHoursTrue" checked="{{enableOfficeHoursTrueChecked}}" value="true"> - <label for="displayOfflineFormTrue">{{_ "True"}}</label> - <input type="radio" class="preview-settings" name="enableOfficeHours" id="enableOfficeHoursFalse" checked="{{enableOfficeHoursFalseChecked}}" value="false"> - <label for="displayOfflineFormFalse">{{_ "False"}}</label> - </fieldset> + <fieldset> + <legend>{{_ "Office_hours_enabled"}}</legend> + <input type="radio" class="preview-settings" name="enableOfficeHours" id="enableOfficeHoursTrue" checked="{{enableOfficeHoursTrueChecked}}" value="true"> + <label for="displayOfflineFormTrue">{{_ "True"}}</label> + <input type="radio" class="preview-settings" name="enableOfficeHours" id="enableOfficeHoursFalse" checked="{{enableOfficeHoursFalseChecked}}" value="false"> + <label for="displayOfflineFormFalse">{{_ "False"}}</label> + </fieldset> - <!-- days open --> - <fieldset> - <legend>{{_ "Open_days_of_the_week"}}</legend> - {{#each day in days}} - {{#if open day}} - <label class="dayOpenCheck"><input type="checkbox" name={{openName day}} checked>{{name day}}</label> - {{else}} - <label class="dayOpenCheck"><input type="checkbox" name={{openName day}}>{{name day}}</label> - {{/if}} - {{/each}} - </fieldset> + <!-- days open --> + <fieldset> + <legend>{{_ "Open_days_of_the_week"}}</legend> + {{#each day in days}} + {{#if open day}} + <label class="dayOpenCheck"><input type="checkbox" name={{openName day}} checked>{{name day}}</label> + {{else}} + <label class="dayOpenCheck"><input type="checkbox" name={{openName day}}>{{name day}}</label> + {{/if}} + {{/each}} + </fieldset> - <!-- times --> - <fieldset> - <legend>{{_ "Hours"}}</legend> - {{#each day in days}} - <div class="input-line"> - <h1><strong>{{name day}}</strong></h1> - <table style="width:100%;"> - <tr> - <td>{{_ "Open"}}:</td> - <td>{{_ "Close"}}:</td> - </tr> - <tr> - <td> - <div style="margin-right:30px"> - <input type="time" class="preview-settings" name={{startName day}} id={{startName day}} value={{start day}} style="width=100px;"> - </div> - </td> - <td> - <div style="margin-right:30px"> - <input type="time" class="preview-settings" name={{finishName day}} id={{finishName day}} value={{finish day}} style="width=100px;"> - </div> - </td> - </tr> - </table> - </div> - {{/each}} - </fieldset> + <!-- times --> + <fieldset> + <legend>{{_ "Hours"}}</legend> + {{#each day in days}} + <div class="input-line"> + <h1><strong>{{name day}}</strong></h1> + <table style="width:100%;"> + <tr> + <td>{{_ "Open"}}:</td> + <td>{{_ "Close"}}:</td> + </tr> + <tr> + <td> + <div style="margin-right:30px"> + <input type="time" class="preview-settings" name={{startName day}} id={{startName day}} value={{start day}} style="width=100px;"> + </div> + </td> + <td> + <div style="margin-right:30px"> + <input type="time" class="preview-settings" name={{finishName day}} id={{finishName day}} value={{finish day}} style="width=100px;"> + </div> + </td> + </tr> + </table> + </div> + {{/each}} + </fieldset> - <div class="submit"> - <button class="button"><i class="icon-floppy"></i>{{_ "Save"}}</button> - </div> - </form> - </div> -</template> \ No newline at end of file + <div class="submit"> + <button class="button"><i class="icon-floppy"></i>{{_ "Save"}}</button> + </div> + </form> + </div> + {{/requiresPermission}} +</template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.js b/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.js index aa4c2e6856bcc4b001b192376a23ce213fcf6c21..2d294b5aff6ac5a396cab3b4af8d8f9a3826320b 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.js +++ b/packages/rocketchat-livechat/client/views/app/livechatOfficeHours.js @@ -69,9 +69,8 @@ Template.livechatOfficeHours.events({ let value = e.currentTarget.value; if (e.currentTarget.type === 'radio') { value = value === 'true'; + instance[e.currentTarget.name].set(value); } - - instance[e.currentTarget.name].set(value); }, 'submit .rocket-form'(e, instance) { e.preventDefault(); diff --git a/packages/rocketchat-livechat/client/views/app/livechatQueue.html b/packages/rocketchat-livechat/client/views/app/livechatQueue.html index 35f838b5f4c4899e2b101f4d1cd2e100a85200b9..454e9f513b499688ccbff819596bf44e05bc9052 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatQueue.html +++ b/packages/rocketchat-livechat/client/views/app/livechatQueue.html @@ -1,5 +1,5 @@ <template name="livechatQueue"> - {{#if hasPermission}} + {{#requiresPermission 'view-livechat-manager'}} {{#each departments}} <p class="queue-department"> {{_ "Department"}}: <strong>{{name}}</strong> @@ -34,7 +34,5 @@ </div> </div> {{/each}} - {{else}} - <p>{{_ "You_are_not_authorized_to_view_this_page"}}</p> - {{/if}} + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatTriggers.html b/packages/rocketchat-livechat/client/views/app/livechatTriggers.html index 25a155f3671db4602198479139e8ca53317c79c0..ea15e3b22c14e09e65748c811451d3a6fed4e718 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatTriggers.html +++ b/packages/rocketchat-livechat/client/views/app/livechatTriggers.html @@ -1,32 +1,28 @@ <template name="livechatTriggers"> - <form id="trigger-form"> - <div class="rocket-form"> - <fieldset> - <legend>{{_ "Condition"}}</legend> - <div class="conditions"> - {{#each conditions}} - {{> livechatTriggerCondition}} + {{#requiresPermission 'view-livechat-manager'}} + <div class="list"> + <table> + <thead> + <tr> + <th width="30%">{{_ "Name"}}</th> + <th width="50%">{{_ "Description"}}</th> + <th width="20%">{{_ "Enabled"}}</th> + <th>{{_ "Delete"}}</th> + </tr> + </thead> + <tbody> + {{#each triggers}} + <tr class="trigger-info row-link" data-id="{{_id}}"> + <td>{{name}}</td> + <td>{{description}}</td> + <td>{{#if enabled}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td> + <td><a href="#remove" class="remove-trigger"><i class="icon-trash"></i></a></td> + </tr> {{/each}} - {{#unless conditions}} - {{> livechatTriggerCondition}} - {{/unless}} - </div> - </fieldset> - <fieldset> - <legend>{{_ "Action"}}</legend> - <div class="actions"> - {{#each actions}} - {{> livechatTriggerAction}} - {{/each}} - {{#unless actions}} - {{> livechatTriggerAction}} - {{/unless}} - </div> - </fieldset> - <div class="submit"> - <button class="button danger delete-trigger" type="button">{{_ "Delete"}}</button> - <button class="button primary save"><i class="icon-floppy"></i><span>{{_ "Save"}}</span></button> - </div> + </tbody> + </table> </div> - </form> + + <a href="{{pathFor 'livechat-trigger-new'}}" class="button primary">{{_ "New_Trigger"}}</a> + {{/requiresPermission}} </template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatTriggers.js b/packages/rocketchat-livechat/client/views/app/livechatTriggers.js index 74adc00334bb43acdb71163ac6dbc9be28e16e29..cbe198de740bfae329e2d110e03abeab19b05153 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatTriggers.js +++ b/packages/rocketchat-livechat/client/views/app/livechatTriggers.js @@ -1,68 +1,42 @@ -import toastr from 'toastr'; Template.livechatTriggers.helpers({ - conditions() { - var trigger = Template.instance().trigger.get(); - if (!trigger) { - return []; - } - - return trigger.conditions; - }, - actions() { - var trigger = Template.instance().trigger.get(); - if (!trigger) { - return []; - } - - return trigger.actions; + triggers() { + return LivechatTrigger.find(); } }); Template.livechatTriggers.events({ - 'submit #trigger-form'(e, instance) { + 'click .remove-trigger'(e/*, instance*/) { e.preventDefault(); - var $btn = instance.$('button.save'); - - var oldBtnValue = $btn.html(); - $btn.html(t('Saving')); - - var data = { - conditions: [], - actions: [] - }; + e.stopPropagation(); - $('.each-condition').each(function() { - data.conditions.push({ - name: $('.trigger-condition', this).val(), - value: $('.' + $('.trigger-condition', this).val() + '-value').val() - }); - }); - - $('.each-action').each(function() { - if ($('.trigger-action', this).val() === 'send-message') { - data.actions.push({ - name: $('.trigger-action', this).val(), - params: { - name: $('[name=send-message-name]', this).val(), - msg: $('[name=send-message-msg]', this).val() - } - }); - } else { - data.actions.push({ - name: $('.trigger-action', this).val(), - value: $('.' + $('.trigger-action', this).val() + '-value').val() + swal({ + title: t('Are_you_sure'), + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Yes'), + cancelButtonText: t('Cancel'), + closeOnConfirm: false, + html: false + }, () => { + Meteor.call('livechat:removeTrigger', this._id, function(error/*, result*/) { + if (error) { + return handleError(error); + } + swal({ + title: t('Removed'), + text: t('Trigger_removed'), + type: 'success', + timer: 1000, + showConfirmButton: false }); - } + }); }); + }, - Meteor.call('livechat:saveTrigger', data, function(error/*, result*/) { - $btn.html(oldBtnValue); - if (error) { - return handleError(error); - } - - toastr.success(t('Saved')); - }); + 'click .trigger-info'(e/*, instance*/) { + e.preventDefault(); + FlowRouter.go('livechat-trigger-edit', { _id: this._id }); }, 'click .delete-trigger'(e/*, instance*/) { @@ -78,7 +52,7 @@ Template.livechatTriggers.events({ closeOnConfirm: false, html: false }, () => { - Meteor.call('livechat:removeTrigger', function(error/*, result*/) { + Meteor.call('livechat:removeTrigger', this._id, function(error/*, result*/) { if (error) { return handleError(error); } @@ -96,9 +70,5 @@ Template.livechatTriggers.events({ }); Template.livechatTriggers.onCreated(function() { - this.subscribe('livechat:trigger'); - this.trigger = new ReactiveVar(null); - this.autorun(() => { - this.trigger.set(LivechatTrigger.findOne()); - }); + this.subscribe('livechat:triggers'); }); diff --git a/packages/rocketchat-livechat/client/views/app/livechatTriggersForm.html b/packages/rocketchat-livechat/client/views/app/livechatTriggersForm.html new file mode 100644 index 0000000000000000000000000000000000000000..aad02c3ac4961a5476a24da9d51d487f05dbaf1a --- /dev/null +++ b/packages/rocketchat-livechat/client/views/app/livechatTriggersForm.html @@ -0,0 +1,55 @@ +<template name="livechatTriggersForm"> + {{#requiresPermission 'view-livechat-manager'}} + <form id="trigger-form"> + <div class="rocket-form"> + <fieldset> + <div class="input-line"> + <label>{{_ "Enabled"}}</label> + <div> + <label><input type="radio" name="enabled" value="1" checked="{{$eq enabled true}}" /> {{_ "Yes"}}</label> + <label><input type="radio" name="enabled" value="0" checked="{{$eq enabled false}}" /> {{_ "No"}}</label> + </div> + </div> + <div class="input-line"> + <label>{{_ "Name"}}</label> + <div> + <input type="text" name="name" value="{{name}}" /> + </div> + </div> + <div class="input-line"> + <label>{{_ "Description"}}</label> + <div> + <input type="text" name="description" value="{{description}}" /> + </div> + </div> + </fieldset> + <fieldset> + <legend>{{_ "Condition"}}</legend> + <div class="conditions"> + {{#each conditions}} + {{> livechatTriggerCondition}} + {{/each}} + {{#unless conditions}} + {{> livechatTriggerCondition}} + {{/unless}} + </div> + </fieldset> + <fieldset> + <legend>{{_ "Action"}}</legend> + <div class="actions"> + {{#each actions}} + {{> livechatTriggerAction}} + {{/each}} + {{#unless actions}} + {{> livechatTriggerAction}} + {{/unless}} + </div> + </fieldset> + <div class="submit"> + <button class="button back" type="button"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button> + <button class="button primary save"><i class="icon-floppy"></i><span>{{_ "Save"}}</span></button> + </div> + </div> + </form> + {{/requiresPermission}} +</template> diff --git a/packages/rocketchat-livechat/client/views/app/livechatTriggersForm.js b/packages/rocketchat-livechat/client/views/app/livechatTriggersForm.js new file mode 100644 index 0000000000000000000000000000000000000000..2b16ff6583ab1d24ffeaa4205d9013975c266d21 --- /dev/null +++ b/packages/rocketchat-livechat/client/views/app/livechatTriggersForm.js @@ -0,0 +1,94 @@ +import toastr from 'toastr'; +Template.livechatTriggersForm.helpers({ + name() { + const trigger = LivechatTrigger.findOne(FlowRouter.getParam('_id')); + return trigger && trigger.name; + }, + description() { + const trigger = LivechatTrigger.findOne(FlowRouter.getParam('_id')); + return trigger && trigger.description; + }, + enabled() { + const trigger = LivechatTrigger.findOne(FlowRouter.getParam('_id')); + return trigger && trigger.enabled; + }, + conditions() { + const trigger = LivechatTrigger.findOne(FlowRouter.getParam('_id')); + if (!trigger) { + return []; + } + + return trigger.conditions; + }, + actions() { + const trigger = LivechatTrigger.findOne(FlowRouter.getParam('_id')); + if (!trigger) { + return []; + } + + return trigger.actions; + } +}); + +Template.livechatTriggersForm.events({ + 'submit #trigger-form'(e, instance) { + e.preventDefault(); + const $btn = instance.$('button.save'); + + const oldBtnValue = $btn.html(); + $btn.html(t('Saving')); + + const data = { + _id: FlowRouter.getParam('_id'), + name: instance.$('input[name=name]').val(), + description: instance.$('input[name=description]').val(), + enabled: instance.$('input[name=enabled]:checked').val() === '1' ? true : false, + conditions: [], + actions: [] + }; + + $('.each-condition').each(function() { + data.conditions.push({ + name: $('.trigger-condition', this).val(), + value: $('.' + $('.trigger-condition', this).val() + '-value').val() + }); + }); + + $('.each-action').each(function() { + if ($('.trigger-action', this).val() === 'send-message') { + data.actions.push({ + name: $('.trigger-action', this).val(), + params: { + name: $('[name=send-message-name]', this).val(), + msg: $('[name=send-message-msg]', this).val() + } + }); + } else { + data.actions.push({ + name: $('.trigger-action', this).val(), + value: $('.' + $('.trigger-action', this).val() + '-value').val() + }); + } + }); + + Meteor.call('livechat:saveTrigger', data, function(error/*, result*/) { + $btn.html(oldBtnValue); + if (error) { + return handleError(error); + } + + FlowRouter.go('livechat-triggers'); + + toastr.success(t('Saved')); + }); + }, + + 'click button.back'(e/*, instance*/) { + e.preventDefault(); + FlowRouter.go('livechat-triggers'); + } +}); + +Template.livechatTriggersForm.onCreated(function() { + this.subscribe('livechat:triggers', FlowRouter.getParam('_id')); +}); diff --git a/packages/rocketchat-livechat/client/views/app/livechatUsers.html b/packages/rocketchat-livechat/client/views/app/livechatUsers.html index 539eb625372f74258dc6002dfe87817cb7426854..33c7e7544708cadf3e3c03131b8a03d0c2bfcdd0 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatUsers.html +++ b/packages/rocketchat-livechat/client/views/app/livechatUsers.html @@ -1,71 +1,72 @@ <template name="livechatUsers"> - <h2>{{_ "Livechat_managers"}}</h2> - <form id="form-manager" class="inline"> - <label>{{_ "Add_manager"}}</label> - {{> inputAutocomplete settings=managerAutocompleteSettings name="username" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} - <button name="add" class="button primary add">{{_ "Add"}}</button> - </form> - <div class="list"> - <table> - <thead> - <tr> - <th> </th> - <th width="34%">{{_ "Name"}}</th> - <th width="33%">{{_ "Username"}}</th> - <th width="33%">{{_ "Email"}}</th> - <th> </th> - </tr> - </thead> - <tbody> - {{#each managers}} - <tr class="user-info" data-id="{{_id}}"> - <td> - <div class="user-image status-{{status}}"> - {{> avatar username=username}} - </div> - </td> - <td>{{name}}</td> - <td>{{username}}</td> - <td>{{emailAddress}}</td> - <td><a href="#remove" class="remove-manager"><i class="icon-trash"></i></a></td> + {{#requiresPermission 'view-livechat-manager'}} + <h2>{{_ "Livechat_managers"}}</h2> + <form id="form-manager" class="inline"> + <label>{{_ "Add_manager"}}</label> + {{> inputAutocomplete settings=managerAutocompleteSettings name="username" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} + <button name="add" class="button primary add">{{_ "Add"}}</button> + </form> + <div class="list"> + <table> + <thead> + <tr> + <th> </th> + <th width="34%">{{_ "Name"}}</th> + <th width="33%">{{_ "Username"}}</th> + <th width="33%">{{_ "Email"}}</th> + <th> </th> </tr> - {{/each}} - </tbody> - </table> - </div> - <h2>{{_ "Livechat_agents"}}</h2> - <form id="form-agent" class="inline"> - <label>{{_ "Add_agent"}}</label> - {{> inputAutocomplete settings=agentAutocompleteSettings name="username" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} - <button name="add" class="button primary add">{{_ "Add"}}</button> - </form> - <div class="list"> - <table> - <thead> - <tr> - <th> </th> - <th width="34%">{{_ "Name"}}</th> - <th width="33%">{{_ "Username"}}</th> - <th width="33%">{{_ "Email"}}</th> - <th> </th> - </tr> - </thead> - <tbody> - {{#each agents}} - <tr class="user-info" data-id="{{_id}}"> - <td> - <div class="user-image status-{{status}}"> - {{> avatar username=username}} - </div> - </td> - <td>{{name}}</td> - <td>{{username}}</td> - <td>{{emailAddress}}</td> - <td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td> + </thead> + <tbody> + {{#each managers}} + <tr class="user-info" data-id="{{_id}}"> + <td> + <div class="user-image status-{{status}}"> + {{> avatar username=username}} + </div> + </td> + <td>{{name}}</td> + <td>{{username}}</td> + <td>{{emailAddress}}</td> + <td><a href="#remove" class="remove-manager"><i class="icon-trash"></i></a></td> + </tr> + {{/each}} + </tbody> + </table> + </div> + <h2>{{_ "Livechat_agents"}}</h2> + <form id="form-agent" class="inline"> + <label>{{_ "Add_agent"}}</label> + {{> inputAutocomplete settings=agentAutocompleteSettings name="username" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} + <button name="add" class="button primary add">{{_ "Add"}}</button> + </form> + <div class="list"> + <table> + <thead> + <tr> + <th> </th> + <th width="34%">{{_ "Name"}}</th> + <th width="33%">{{_ "Username"}}</th> + <th width="33%">{{_ "Email"}}</th> + <th> </th> </tr> - {{/each}} - </tbody> - </table> - </div> + </thead> + <tbody> + {{#each agents}} + <tr class="user-info" data-id="{{_id}}"> + <td> + <div class="user-image status-{{status}}"> + {{> avatar username=username}} + </div> + </td> + <td>{{name}}</td> + <td>{{username}}</td> + <td>{{emailAddress}}</td> + <td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td> + </tr> + {{/each}} + </tbody> + </table> + </div> + {{/requiresPermission}} </template> - diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js b/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js index 0b3291c888cef5f2b99e976cb41bd9ff36dab258..f743a751b8ace72a9b099c54dfc41eb45fbf12c1 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js @@ -10,8 +10,8 @@ Template.visitorEdit.helpers({ email() { const visitor = Template.instance().visitor.get(); - if (visitor.emails && visitor.emails.length > 0) { - return visitor.emails[0].address; + if (visitor.visitorEmails && visitor.visitorEmails.length > 0) { + return visitor.visitorEmails[0].address; } }, diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorForward.js b/packages/rocketchat-livechat/client/views/app/tabbar/visitorForward.js index e7481e4af3b4377b70b1486e1435c3f0968384e8..97afe34d0a99ec98308b87ad11a878b4274a4050 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorForward.js +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorForward.js @@ -45,7 +45,7 @@ Template.visitorForward.events({ if (instance.find('#forwardUser').value) { transferData.userId = instance.find('#forwardUser').value; } else if (instance.find('#forwardDepartment').value) { - transferData.deparmentId = instance.find('#forwardDepartment').value; + transferData.departmentId = instance.find('#forwardDepartment').value; } Meteor.call('livechat:transfer', transferData, (error, result) => { diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html index a606fbc588cf7b9302debd8458233e3e72e03994..21e6041d869d6d4ed29d9d51349ebec51b12c993 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html @@ -11,11 +11,11 @@ {{#with user}} <div class="info"> <h3 title="{{username}}"><i class="status-{{status}}"></i> {{username}}</h3> - <p>{{name}}</p> + <p class="secondary-font-color">{{name}}</p> <ul> {{#if utc}}<li><i class="icon-clock"></i>{{userTime}} (UTC {{utc}})</li>{{/if}} - {{#each emails}} <li><i class="icon-mail"></i> {{address}}{{#if verified}} <i class="icon-ok"></i>{{/if}}</li> {{/each}} + {{#each visitorEmails}} <li><i class="icon-mail"></i> {{address}}{{#if verified}} <i class="icon-ok success-color"></i>{{/if}}</li> {{/each}} {{#each phone}} <li><i class="icon-phone"></i> {{phoneNumber}}</li> {{/each}} {{#if lastLogin}} <li><i class="icon-calendar"></i> {{_ "Created_at"}}: {{createdAt}}</li> {{/if}} {{#if lastLogin}} <li><i class="icon-calendar"></i> {{_ "Last_login"}}: {{lastLogin}}</li> {{/if}} diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorNavigation.js b/packages/rocketchat-livechat/client/views/app/tabbar/visitorNavigation.js index 60944d09bd53f489b2b6489a713a01ff8aa4e958..65f4df64c83cba585c79360e2e92a5ecfa0bc107 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorNavigation.js +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorNavigation.js @@ -8,7 +8,9 @@ Template.visitorNavigation.helpers({ pageVisited() { const room = ChatRoom.findOne({ _id: this.rid }, { fields: { 'v.token': 1 } }); - return LivechatPageVisited.find({ token: room.v.token }, { sort: { ts: -1 } }); + if (room && room.v && room.v.token) { + return LivechatPageVisited.find({ token: room.v.token }, { sort: { ts: -1 } }); + } }, pageTitle() { diff --git a/packages/rocketchat-livechat/client/views/sideNav/livechat.js b/packages/rocketchat-livechat/client/views/sideNav/livechat.js index 94586f60f6a5213cd3a45d90db69412072d9dff0..d716738f6a51a4a1aa0ffa792217df3e7c2bd093 100644 --- a/packages/rocketchat-livechat/client/views/sideNav/livechat.js +++ b/packages/rocketchat-livechat/client/views/sideNav/livechat.js @@ -62,7 +62,7 @@ Template.livechat.helpers({ const statusLivechat = Template.instance().statusLivechat.get(); return { - status: statusLivechat, + status: statusLivechat === 'available' ? 'status-online' : 'status-offline', icon: statusLivechat === 'available' ? 'icon-toggle-on' : 'icon-toggle-off', hint: statusLivechat === 'available' ? t('Available') : t('Not_Available') }; @@ -114,7 +114,7 @@ Template.livechat.events({ if (isConfirm) { Meteor.call('livechat:takeInquiry', this._id, (error, result) => { if (!error) { - FlowRouter.go(RocketChat.roomTypes.getRouteLink(result.t, result)); + RocketChat.roomTypes.openRouteLink(result.t, result); } }); } diff --git a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html index 064a644e0d1256785749a98192d43c84b1d2f11c..4b348a757528228d29de9bac2463fb610f0de293 100644 --- a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html +++ b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html @@ -7,18 +7,16 @@ <div class="content"> <div class="wrapper"> <ul class="flex-list"> - <li> - <!-- <a href="{{pathFor 'livechat-dashboard'}}" class="{{active 'livechat-dashboard'}}">{{_ "Dashboard"}}</a> --> - <a href="{{pathFor 'livechat-current-chats'}}" class="{{active 'livechat-current-chats'}}">{{_ "Current_Chats"}}</a> - <a href="{{pathFor 'livechat-users'}}" class="{{active 'livechat-users'}}">{{_ "User_management"}}</a> - <a href="{{pathFor 'livechat-departments'}}" class="{{active 'livechat-departments' 'livechat-department-edit'}}">{{_ "Departments"}}</a> - <a href="{{pathFor 'livechat-triggers'}}" class="{{active 'livechat-triggers'}}">{{_ "Triggers"}}</a> - <a href="{{pathFor 'livechat-customfields'}}" class="{{active 'livechat-customfields'}}">{{_ "Custom_Fields"}}</a> - <a href="{{pathFor 'livechat-installation'}}" class="{{active 'livechat-installation'}}">{{_ "Installation"}}</a> - <a href="{{pathFor 'livechat-appearance'}}" class="{{active 'livechat-appearance'}}">{{_ "Appearance"}}</a> - <a href="{{pathFor 'livechat-integrations'}}" class="{{active 'livechat-integrations'}}">{{_ "Integrations"}}</a> - <a href="{{pathFor 'livechat-officeHours'}}" class="{{active 'livechat-officeHours'}}">{{_ "Office_Hours"}}</a> - </li> + <li><!-- <a href="{{pathFor 'livechat-dashboard'}}" class="{{active 'livechat-dashboard'}}">{{_ "Dashboard"}}</a> --></li> + <li><a href="{{pathFor 'livechat-current-chats'}}" class="{{active 'livechat-current-chats'}}">{{_ "Current_Chats"}}</a></li> + <li><a href="{{pathFor 'livechat-users'}}" class="{{active 'livechat-users'}}">{{_ "User_management"}}</a></li> + <li><a href="{{pathFor 'livechat-departments'}}" class="{{active 'livechat-departments' 'livechat-department-edit'}}">{{_ "Departments"}}</a></li> + <li><a href="{{pathFor 'livechat-triggers'}}" class="{{active 'livechat-triggers'}}">{{_ "Triggers"}}</a></li> + <li><a href="{{pathFor 'livechat-customfields'}}" class="{{active 'livechat-customfields'}}">{{_ "Custom_Fields"}}</a></li> + <li><a href="{{pathFor 'livechat-installation'}}" class="{{active 'livechat-installation'}}">{{_ "Installation"}}</a></li> + <li><a href="{{pathFor 'livechat-appearance'}}" class="{{active 'livechat-appearance'}}">{{_ "Appearance"}}</a></li> + <li><a href="{{pathFor 'livechat-integrations'}}" class="{{active 'livechat-integrations'}}">{{_ "Integrations"}}</a></li> + <li><a href="{{pathFor 'livechat-officeHours'}}" class="{{active 'livechat-officeHours'}}">{{_ "Office_Hours"}}</a></li> </ul> </div> </div> diff --git a/packages/rocketchat-livechat/config.js b/packages/rocketchat-livechat/config.js index 1270237727a9d0409fe69631b8542a88e13ba586..c935d988c41fcd3931855e68f33d24dce5e30d4b 100644 --- a/packages/rocketchat-livechat/config.js +++ b/packages/rocketchat-livechat/config.js @@ -14,6 +14,14 @@ Meteor.startup(function() { i18nLabel: 'Display_offline_form' }); + RocketChat.settings.add('Livechat_validate_offline_email', true, { + type: 'boolean', + group: 'Livechat', + public: true, + section: 'Offline', + i18nLabel: 'Validate_email_address' + }); + RocketChat.settings.add('Livechat_offline_form_unavailable', '', { type: 'string', group: 'Livechat', @@ -184,7 +192,7 @@ Meteor.startup(function() { type: 'boolean', group: 'Livechat', public: true, - i18nLabel: 'Office_Hours_Enabled' + i18nLabel: 'Office_hours_enabled' }); RocketChat.settings.add('Livechat_videocall_enabled', false, { @@ -211,4 +219,19 @@ Meteor.startup(function() { enableQuery: { _id: 'Livechat_enable_transcript', value: true } }); + RocketChat.settings.add('Livechat_open_inquiery_show_connecting', false, { + type: 'boolean', + group: 'Livechat', + public: true, + i18nLabel: 'Livechat_open_inquiery_show_connecting', + enableQuery: { _id: 'Livechat_Routing_Method', value: 'Guest_Pool' } + }); + + RocketChat.settings.add('Livechat_AllowedDomainsList', '', { + type: 'string', + group: 'Livechat', + public: true, + i18nLabel: 'Livechat_AllowedDomainsList', + i18nDescription: 'Domains_allowed_to_embed_the_livechat_widget' + }); }); diff --git a/packages/rocketchat-livechat/livechat.js b/packages/rocketchat-livechat/livechat.js index 684227b212f84b7b071b01fbb42480f3dc525886..7fbc07b3df072b37922345f71d90daa17b8b19f6 100644 --- a/packages/rocketchat-livechat/livechat.js +++ b/packages/rocketchat-livechat/livechat.js @@ -11,6 +11,22 @@ WebApp.connectHandlers.use('/livechat', Meteor.bindEnvironment((req, res, next) } res.setHeader('content-type', 'text/html; charset=utf-8'); + var domainWhiteList = RocketChat.settings.get('Livechat_AllowedDomainsList'); + + if (!_.isEmpty(domainWhiteList.trim())) { + domainWhiteList = _.map(domainWhiteList.split(','), function(domain) { + return domain.trim(); + }); + + let d = req.headers.referer.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1]; + if (!_.contains(domainWhiteList, d)) { + res.setHeader('X-FRAME-OPTIONS', 'DENY'); + return next(); + } + + res.setHeader('X-FRAME-OPTIONS', 'ALLOW-FROM ' + d); + } + const head = Assets.getText('public/head.html'); const html = `<html> diff --git a/packages/rocketchat-livechat/package.js b/packages/rocketchat-livechat/package.js index 6af2910319eb4317ef98beca9dfa24e827201793..bdae5fdbf47e4a26e5c5e634804df41e51dd159b 100644 --- a/packages/rocketchat-livechat/package.js +++ b/packages/rocketchat-livechat/package.js @@ -27,6 +27,7 @@ Package.onUse(function(api) { api.use('rocketchat:authorization'); api.use('rocketchat:logger'); api.use('rocketchat:api'); + api.use('rocketchat:streamer'); api.use('konecty:user-presence'); api.use('rocketchat:ui'); api.use('kadira:flow-router', 'client'); @@ -50,9 +51,7 @@ Package.onUse(function(api) { api.addFiles('client/ui.js', 'client'); api.addFiles('client/route.js', 'client'); - // add stylesheets to theme compiler - api.addAssets('client/stylesheets/livechat.less', 'server'); - api.addFiles('client/stylesheets/load.js', 'server'); + api.addFiles('client/stylesheets/livechat.less', 'client'); // collections api.addFiles('client/collections/AgentUsers.js', 'client'); @@ -91,6 +90,8 @@ Package.onUse(function(api) { api.addFiles('client/views/app/livechatQueue.js', 'client'); api.addFiles('client/views/app/livechatTriggers.html', 'client'); api.addFiles('client/views/app/livechatTriggers.js', 'client'); + api.addFiles('client/views/app/livechatTriggersForm.html', 'client'); + api.addFiles('client/views/app/livechatTriggersForm.js', 'client'); api.addFiles('client/views/app/livechatUsers.html', 'client'); api.addFiles('client/views/app/livechatUsers.js', 'client'); api.addFiles('client/views/app/livechatOfficeHours.html', 'client'); @@ -132,6 +133,7 @@ Package.onUse(function(api) { api.addFiles('server/methods/closeByVisitor.js', 'server'); api.addFiles('server/methods/closeRoom.js', 'server'); api.addFiles('server/methods/getCustomFields.js', 'server'); + api.addFiles('server/methods/getAgentData.js', 'server'); api.addFiles('server/methods/getInitialData.js', 'server'); api.addFiles('server/methods/loginByToken.js', 'server'); api.addFiles('server/methods/pageVisited.js', 'server'); @@ -190,6 +192,7 @@ Package.onUse(function(api) { api.addFiles('server/publications/livechatManagers.js', 'server'); api.addFiles('server/publications/livechatRooms.js', 'server'); api.addFiles('server/publications/livechatQueue.js', 'server'); + api.addFiles('server/publications/livechatTriggers.js', 'server'); api.addFiles('server/publications/visitorHistory.js', 'server'); api.addFiles('server/publications/visitorInfo.js', 'server'); api.addFiles('server/publications/visitorPageVisited.js', 'server'); diff --git a/packages/rocketchat-livechat/roomType.js b/packages/rocketchat-livechat/roomType.js index fe1860cea324112f3f025d31f7398c4b324010fa..03e20cdef3764459908eaa09f0dd1ae0f20ad5f2 100644 --- a/packages/rocketchat-livechat/roomType.js +++ b/packages/rocketchat-livechat/roomType.js @@ -1,4 +1,4 @@ -/* globals openRoom */ +/* globals openRoom, LivechatInquiry */ RocketChat.roomTypes.add('l', 5, { template: 'livechat', @@ -38,6 +38,22 @@ RocketChat.roomTypes.add('l', 5, { return room && room.open === true; }, + getUserStatus(roomId) { + let guestName; + const room = Session.get('roomData' + roomId); + + if (room) { + guestName = room.v && room.v.username; + } else { + const inquiry = LivechatInquiry.findOne({ rid: roomId }); + guestName = inquiry && inquiry.v && inquiry.v.username; + } + + if (guestName) { + return Session.get('user_' + guestName + '_status'); + } + }, + notSubscribedTpl: { template: 'livechatNotSubscribed' } diff --git a/packages/rocketchat-livechat/server/lib/Livechat.js b/packages/rocketchat-livechat/server/lib/Livechat.js index 34af055d948ad4af6fab95bae860c4ec348cc89d..2ba268ac2de26073ffd83086caa0bb79c266bbdb 100644 --- a/packages/rocketchat-livechat/server/lib/Livechat.js +++ b/packages/rocketchat-livechat/server/lib/Livechat.js @@ -41,11 +41,15 @@ RocketChat.Livechat = { } if (room == null) { - // if no department selected verify if there is at least one active and choose one randomly + // if no department selected verify if there is at least one active and pick the first if (!guest.department) { var departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents(); if (departments.count() > 0) { - guest.department = departments.fetch()[0]._id; + departments.forEach((dept) => { + if (!guest.department && dept.showOnRegistration) { + guest.department = dept._id; + } + }); } } @@ -68,7 +72,9 @@ RocketChat.Livechat = { if (guest.name) { message.alias = guest.name; } - return _.extend(RocketChat.sendMessage(guest, message, room), { newRoom: newRoom }); + + // return messages; + return _.extend(RocketChat.sendMessage(guest, message, room), { newRoom: newRoom, showConnecting: this.showConnecting() }); }, registerGuest({ token, name, email, department, phone, loginToken, username } = {}) { check(token, String); @@ -100,16 +106,11 @@ RocketChat.Livechat = { var existingUser = null; - if (s.trim(email) !== '' && (existingUser = RocketChat.models.Users.findOneByEmailAddress(email))) { - if (existingUser.type !== 'visitor') { - throw new Meteor.Error('error-invalid-user', 'This email belongs to a registered user.'); - } - - updateUser.$addToSet = { - globalRoles: 'livechat-guest' - }; - + if (s.trim(email) !== '' && (existingUser = RocketChat.models.Users.findOneGuestByEmailAddress(email))) { if (loginToken) { + if (!updateUser.$addToSet) { + updateUser.$addToSet = {}; + } updateUser.$addToSet['services.resume.loginTokens'] = loginToken; } @@ -121,7 +122,8 @@ RocketChat.Livechat = { username: username, globalRoles: ['livechat-guest'], department: department, - type: 'visitor' + type: 'visitor', + joinDefaultChannels: false }; if (this.connection) { @@ -149,7 +151,7 @@ RocketChat.Livechat = { } if (email && email.trim() !== '') { - updateUser.$set.emails = [ + updateUser.$set.visitorEmails = [ { address: email } ]; } @@ -171,7 +173,7 @@ RocketChat.Livechat = { if (phone) { updateData.phone = phone; } - const ret = RocketChat.models.Users.saveUserById(_id, updateData); + const ret = RocketChat.models.Users.saveGuestById(_id, updateData); Meteor.defer(() => { RocketChat.callbacks.run('livechat.saveGuest', updateData); @@ -200,7 +202,6 @@ RocketChat.Livechat = { RocketChat.sendMessage(user, message, room); RocketChat.models.Subscriptions.hideByRoomIdAndUserId(room._id, user._id); - RocketChat.models.Messages.createCommandWithRoomIdAndUser('promptTranscript', room._id, user); Meteor.defer(() => { @@ -285,10 +286,12 @@ RocketChat.Livechat = { agent = RocketChat.Livechat.getNextAgent(transferData.departmentId); } - if (agent && agent.agentId !== room.servedBy._id) { - room.usernames = _.without(room.usernames, room.servedBy.username).concat(agent.username); + const servedBy = room.servedBy; + + if (agent && agent.agentId !== servedBy._id) { + room.usernames = _.without(room.usernames, servedBy.username).concat(agent.username); - RocketChat.models.Rooms.changeAgentByRoomId(room._id, room.usernames, agent); + RocketChat.models.Rooms.changeAgentByRoomId(room._id, agent); let subscriptionData = { rid: room._id, @@ -306,13 +309,18 @@ RocketChat.Livechat = { mobilePushNotifications: 'all', emailNotifications: 'all' }; - RocketChat.models.Subscriptions.removeByRoomIdAndUserId(room._id, room.servedBy._id); + RocketChat.models.Subscriptions.removeByRoomIdAndUserId(room._id, servedBy._id); RocketChat.models.Subscriptions.insert(subscriptionData); - RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(room._id, { _id: room.servedBy._id, username: room.servedBy.username }); + RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(room._id, { _id: servedBy._id, username: servedBy.username }); RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(room._id, { _id: agent.agentId, username: agent.username }); + RocketChat.Livechat.stream.emit(room._id, { + type: 'agentData', + data: RocketChat.models.Users.getAgentInfo(agent.agentId) + }); + return true; } @@ -381,8 +389,8 @@ RocketChat.Livechat = { postData.crmData = room.crmData; } - if (visitor.emails && visitor.emails.length > 0) { - postData.visitor.email = visitor.emails[0].address; + if (visitor.visitorEmails && visitor.visitorEmails.length > 0) { + postData.visitor.email = visitor.visitorEmails[0].address; } if (visitor.phone && visitor.phone.length > 0) { postData.visitor.phone = visitor.phone[0].phoneNumber; @@ -465,7 +473,8 @@ RocketChat.Livechat = { check(departmentData, { enabled: Boolean, name: String, - description: Match.Optional(String) + description: Match.Optional(String), + showOnRegistration: Boolean }); check(departmentAgents, [ @@ -482,7 +491,7 @@ RocketChat.Livechat = { } } - return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentAgents); + return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData, departmentAgents); }, removeDepartment(_id) { @@ -495,9 +504,20 @@ RocketChat.Livechat = { } return RocketChat.models.LivechatDepartment.removeById(_id); + }, + + showConnecting() { + if (RocketChat.settings.get('Livechat_Routing_Method') === 'Guest_Pool') { + return RocketChat.settings.get('Livechat_open_inquiery_show_connecting'); + } else { + return false; + } } }; +RocketChat.Livechat.stream = new Meteor.Streamer('livechat-room'); +RocketChat.Livechat.stream.allowRead('logged'); + RocketChat.settings.get('Livechat_history_monitor_type', (key, value) => { RocketChat.Livechat.historyMonitorType = value; }); diff --git a/packages/rocketchat-livechat/server/lib/QueueMethods.js b/packages/rocketchat-livechat/server/lib/QueueMethods.js index 85013f3a4feec3ce5d793c527f7311341043a6cc..7921aaa83073823185aa3c8966ee4bb477e0704d 100644 --- a/packages/rocketchat-livechat/server/lib/QueueMethods.js +++ b/packages/rocketchat-livechat/server/lib/QueueMethods.js @@ -18,11 +18,12 @@ RocketChat.QueueMethods = { lm: new Date(), code: roomCode, label: guest.name || guest.username, - usernames: [agent.username, guest.username], + // usernames: [agent.username, guest.username], t: 'l', ts: new Date(), v: { _id: guest._id, + username: guest.username, token: message.token }, servedBy: { @@ -53,6 +54,11 @@ RocketChat.QueueMethods = { RocketChat.models.Rooms.insert(room); RocketChat.models.Subscriptions.insert(subscriptionData); + RocketChat.Livechat.stream.emit(room._id, { + type: 'agentData', + data: RocketChat.models.Users.getAgentInfo(agent.agentId) + }); + return room; }, /* Guest Pool Queuing Method: @@ -96,6 +102,11 @@ RocketChat.QueueMethods = { department: guest.department, agents: agentIds, status: 'open', + v: { + _id: guest._id, + username: guest.username, + token: message.token + }, t: 'l' }; const room = _.extend({ @@ -104,11 +115,12 @@ RocketChat.QueueMethods = { lm: new Date(), code: roomCode, label: guest.name || guest.username, - usernames: [guest.username], + // usernames: [guest.username], t: 'l', ts: new Date(), v: { _id: guest._id, + username: guest.username, token: message.token }, cl: false, diff --git a/packages/rocketchat-livechat/server/methods/getAgentData.js b/packages/rocketchat-livechat/server/methods/getAgentData.js new file mode 100644 index 0000000000000000000000000000000000000000..98f8e2808ae88de59a98932a0797264eb15799bc --- /dev/null +++ b/packages/rocketchat-livechat/server/methods/getAgentData.js @@ -0,0 +1,19 @@ +Meteor.methods({ + 'livechat:getAgentData'(roomId) { + check(roomId, String); + + const room = RocketChat.models.Rooms.findOneById(roomId); + const user = Meteor.user(); + + // allow to only user to send transcripts from their own chats + if (!room || room.t !== 'l' || !room.v || !user.profile || room.v.token !== user.profile.token) { + throw new Meteor.Error('error-invalid-room', 'Invalid room'); + } + + if (!room.servedBy) { + return; + } + + return RocketChat.models.Users.getAgentInfo(room.servedBy._id); + } +}); diff --git a/packages/rocketchat-livechat/server/methods/getInitialData.js b/packages/rocketchat-livechat/server/methods/getInitialData.js index 5cb96801983897180430e1273de6006a0d14b85f..41d5f5505116666a162f991c2032db2ad10a568f 100644 --- a/packages/rocketchat-livechat/server/methods/getInitialData.js +++ b/packages/rocketchat-livechat/server/methods/getInitialData.js @@ -24,7 +24,8 @@ Meteor.methods({ cl: 1, u: 1, usernames: 1, - v: 1 + v: 1, + servedBy: 1 } }).fetch(); @@ -49,9 +50,10 @@ Meteor.methods({ info.transcript = initSettings.Livechat_enable_transcript; info.transcriptMessage = initSettings.Livechat_transcript_message; + info.agentData = room && room[0] && room[0].servedBy && RocketChat.models.Users.getAgentInfo(room[0].servedBy._id); - RocketChat.models.LivechatTrigger.find().forEach((trigger) => { - info.triggers.push(trigger); + RocketChat.models.LivechatTrigger.findEnabled().forEach((trigger) => { + info.triggers.push(_.pick(trigger, '_id', 'actions', 'conditions')); }); RocketChat.models.LivechatDepartment.findEnabledWithAgents().forEach((department) => { diff --git a/packages/rocketchat-livechat/server/methods/removeTrigger.js b/packages/rocketchat-livechat/server/methods/removeTrigger.js index e9433e008905f46d60a6fc138671eab40d91db31..426e0be921b5b4d086c8eeb4ce41559bb8571321 100644 --- a/packages/rocketchat-livechat/server/methods/removeTrigger.js +++ b/packages/rocketchat-livechat/server/methods/removeTrigger.js @@ -1,9 +1,11 @@ Meteor.methods({ - 'livechat:removeTrigger'(/*trigger*/) { + 'livechat:removeTrigger'(triggerId) { if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-livechat-manager')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeTrigger' }); } - return RocketChat.models.LivechatTrigger.removeAll(); + check(triggerId, String); + + return RocketChat.models.LivechatTrigger.removeById(triggerId); } }); diff --git a/packages/rocketchat-livechat/server/methods/saveTrigger.js b/packages/rocketchat-livechat/server/methods/saveTrigger.js index 396d48ce5ba8bdf4e86af5d578ddd2ccf394770b..d3bf4f03ace3c63c4bcdfac4d4bb789ddf9d0b92 100644 --- a/packages/rocketchat-livechat/server/methods/saveTrigger.js +++ b/packages/rocketchat-livechat/server/methods/saveTrigger.js @@ -4,6 +4,19 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveTrigger' }); } - return RocketChat.models.LivechatTrigger.save(trigger); + check(trigger, { + _id: Match.Maybe(String), + name: String, + description: String, + enabled: Boolean, + conditions: Array, + actions: Array + }); + + if (trigger._id) { + return RocketChat.models.LivechatTrigger.updateById(trigger._id, trigger); + } else { + return RocketChat.models.LivechatTrigger.insert(trigger); + } } }); diff --git a/packages/rocketchat-livechat/server/methods/sendOfflineMessage.js b/packages/rocketchat-livechat/server/methods/sendOfflineMessage.js index bba6563f146e3d15e966ea59beb40d3f03082ae2..9dedcaec19a850736d790b1980b8162aa4ace15f 100644 --- a/packages/rocketchat-livechat/server/methods/sendOfflineMessage.js +++ b/packages/rocketchat-livechat/server/methods/sendOfflineMessage.js @@ -1,4 +1,6 @@ /* globals DDPRateLimiter */ +const dns = Npm.require('dns'); + Meteor.methods({ 'livechat:sendOfflineMessage'(data) { check(data, { @@ -7,6 +9,10 @@ Meteor.methods({ message: String }); + if (!RocketChat.settings.get('Livechat_display_offline_form')) { + return false; + } + const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); @@ -26,6 +32,16 @@ Meteor.methods({ fromEmail = RocketChat.settings.get('From_Email'); } + if (RocketChat.settings.get('Livechat_validate_offline_email')) { + const emailDomain = data.email.substr(data.email.lastIndexOf('@') + 1); + + try { + Meteor.wrapAsync(dns.resolveMx)(emailDomain); + } catch (e) { + throw new Meteor.Error('error-invalid-email-address', 'Invalid email address', { method: 'livechat:sendOfflineMessage' }); + } + } + Meteor.defer(() => { Email.send({ to: RocketChat.settings.get('Livechat_offline_email'), diff --git a/packages/rocketchat-livechat/server/methods/startVideoCall.js b/packages/rocketchat-livechat/server/methods/startVideoCall.js index 1a7194ea4126e978696a787630a448d133305316..a499cd75a497181eba897bff11dce020f2620c3c 100644 --- a/packages/rocketchat-livechat/server/methods/startVideoCall.js +++ b/packages/rocketchat-livechat/server/methods/startVideoCall.js @@ -27,7 +27,7 @@ Meteor.methods({ return { roomId: room._id, domain: RocketChat.settings.get('Jitsi_Domain'), - jitsiRoom: 'RocketChat' + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString() + jitsiRoom: RocketChat.settings.get('Jitsi_URL_Room_Prefix') + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString() }; } }); diff --git a/packages/rocketchat-livechat/server/methods/takeInquiry.js b/packages/rocketchat-livechat/server/methods/takeInquiry.js index b431c5721ea6755aa88ce35ca685eab16ffd6202..c89c9c5a00eb84e31167b0b85cc435f6780565a0 100644 --- a/packages/rocketchat-livechat/server/methods/takeInquiry.js +++ b/packages/rocketchat-livechat/server/methods/takeInquiry.js @@ -38,11 +38,9 @@ Meteor.methods({ // update room const room = RocketChat.models.Rooms.findOneById(inquiry.rid); - const usernames = room.usernames.concat(agent.username); - RocketChat.models.Rooms.changeAgentByRoomId(inquiry.rid, usernames, agent); + RocketChat.models.Rooms.changeAgentByRoomId(inquiry.rid, agent); - room.usernames = usernames; room.servedBy = { _id: agent.agentId, username: agent.username @@ -51,6 +49,16 @@ Meteor.methods({ // mark inquiry as taken RocketChat.models.LivechatInquiry.takeInquiry(inquiry._id); + // remove sending message from guest widget + // dont check if setting is true, because if settingwas switched off inbetween guest entered pool, + // and inquiry being taken, message would not be switched off. + RocketChat.models.Messages.createCommandWithRoomIdAndUser('connected', room._id, user); + + RocketChat.Livechat.stream.emit(room._id, { + type: 'agentData', + data: RocketChat.models.Users.getAgentInfo(agent.agentId) + }); + // return room corresponding to inquiry (for redirecting agent to the room route) return room; } diff --git a/packages/rocketchat-livechat/server/methods/transfer.js b/packages/rocketchat-livechat/server/methods/transfer.js index b1efe6f0e6abe9d47a74cad9acb080fc3d4cf3d8..7502dc661b8a54397df10d5750e7d3c0c514d4e2 100644 --- a/packages/rocketchat-livechat/server/methods/transfer.js +++ b/packages/rocketchat-livechat/server/methods/transfer.js @@ -8,7 +8,7 @@ Meteor.methods({ check(transferData, { roomId: String, userId: Match.Optional(String), - deparmentId: Match.Optional(String) + departmentId: Match.Optional(String) }); const room = RocketChat.models.Rooms.findOneById(transferData.roomId); diff --git a/packages/rocketchat-livechat/server/models/LivechatDepartment.js b/packages/rocketchat-livechat/server/models/LivechatDepartment.js index 08627f550ef5d818944bde7d47c3d9d2da8874d4..a1f9285f0ffc642d2b6a8f450fef96531204a6ba 100644 --- a/packages/rocketchat-livechat/server/models/LivechatDepartment.js +++ b/packages/rocketchat-livechat/server/models/LivechatDepartment.js @@ -4,6 +4,11 @@ class LivechatDepartment extends RocketChat.models._Base { constructor() { super('livechat_department'); + + this.tryEnsureIndex({ + numAgents: 1, + enabled: 1 + }); } // FIND @@ -19,18 +24,17 @@ class LivechatDepartment extends RocketChat.models._Base { return this.find(query, options); } - createOrUpdateDepartment(_id, enabled, name, description, agents, extraData) { + createOrUpdateDepartment(_id, { enabled, name, description, showOnRegistration }, agents) { agents = [].concat(agents); var record = { enabled: enabled, name: name, description: description, - numAgents: agents.length + numAgents: agents.length, + showOnRegistration: showOnRegistration }; - _.extend(record, extraData); - if (_id) { this.update({ _id: _id }, { $set: record }); } else { diff --git a/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js b/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js index 681a6c73eecb8d3921995c3885d6b64f23c001da..ef725815b9ca31f847c89a4ba8bc07e34a82f063 100644 --- a/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js +++ b/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js @@ -74,7 +74,7 @@ class LivechatDepartmentAgents extends RocketChat.models._Base { var agents = this.findByDepartmentId(departmentId).fetch(); if (agents.length === 0) { - return; + return []; } var onlineUsers = RocketChat.models.Users.findOnlineUserFromList(_.pluck(agents, 'username')); @@ -93,7 +93,7 @@ class LivechatDepartmentAgents extends RocketChat.models._Base { if (depAgents) { return depAgents; } else { - return null; + return []; } } diff --git a/packages/rocketchat-livechat/server/models/LivechatTrigger.js b/packages/rocketchat-livechat/server/models/LivechatTrigger.js index c7145759c0da8b85ef63370e4c6ed4b35b788c33..0231fb56b5d386941ee61621f869e742f74c61a0 100644 --- a/packages/rocketchat-livechat/server/models/LivechatTrigger.js +++ b/packages/rocketchat-livechat/server/models/LivechatTrigger.js @@ -6,19 +6,24 @@ class LivechatTrigger extends RocketChat.models._Base { super('livechat_trigger'); } - // FIND - save(data) { - const trigger = this.findOne(); - - if (trigger) { - return this.update({ _id: trigger._id }, { $set: data }); - } else { - return this.insert(data); - } + updateById(_id, data) { + return this.update({ _id }, { $set: data }); } removeAll() { - this.remove({}); + return this.remove({}); + } + + findById(_id) { + return this.find({ _id }); + } + + removeById(_id) { + return this.remove({ _id }); + } + + findEnabled() { + return this.find({ enabled: true }); } } diff --git a/packages/rocketchat-livechat/server/models/Rooms.js b/packages/rocketchat-livechat/server/models/Rooms.js index f62f9e6faadd56d420aa161c7d6ce7f82684b324..7d5aae50279ac373ce3e4be02a58ec78c8cebc15 100644 --- a/packages/rocketchat-livechat/server/models/Rooms.js +++ b/packages/rocketchat-livechat/server/models/Rooms.js @@ -44,15 +44,19 @@ RocketChat.models.Rooms.findLivechatByCode = function(code, fields) { let options = {}; + if (fields) { + options.fields = fields; + } + + if (this.useCache) { + return this.cache.findByIndex('t,code', ['l', code], options); + } + const query = { t: 'l', code: code }; - if (fields) { - options.fields = fields; - } - return this.find(query, options); }; @@ -162,13 +166,12 @@ RocketChat.models.Rooms.findOpenByAgent = function(userId) { return this.find(query); }; -RocketChat.models.Rooms.changeAgentByRoomId = function(roomId, newUsernames, newAgent) { +RocketChat.models.Rooms.changeAgentByRoomId = function(roomId, newAgent) { const query = { _id: roomId }; const update = { $set: { - usernames: newUsernames, servedBy: { _id: newAgent.agentId, username: newAgent.username diff --git a/packages/rocketchat-livechat/server/models/Users.js b/packages/rocketchat-livechat/server/models/Users.js index 1c800dac8cc9ef2e37a04641b0b71ff16aa72054..6941e0109060c5b483e32e1910164af79e70099f 100644 --- a/packages/rocketchat-livechat/server/models/Users.js +++ b/packages/rocketchat-livechat/server/models/Users.js @@ -19,7 +19,7 @@ RocketChat.models.Users.setOperator = function(_id, operator) { */ RocketChat.models.Users.findOnlineAgents = function() { var query = { - statusConnection: { + status: { $exists: true, $ne: 'offline' }, @@ -49,7 +49,7 @@ RocketChat.models.Users.findAgents = function() { */ RocketChat.models.Users.findOnlineUserFromList = function(userList) { var query = { - statusConnection: { + status: { $exists: true, $ne: 'offline' }, @@ -69,7 +69,7 @@ RocketChat.models.Users.findOnlineUserFromList = function(userList) { */ RocketChat.models.Users.getNextAgent = function() { var query = { - statusConnection: { + status: { $exists: true, $ne: 'offline' }, @@ -214,3 +214,77 @@ RocketChat.models.Users.getNextVisitorUsername = function() { return 'guest-' + (livechatCount.value.value + 1); }; + +RocketChat.models.Users.saveGuestById = function(_id, data) { + const setData = {}; + const unsetData = {}; + + if (data.name) { + if (!_.isEmpty(s.trim(data.name))) { + setData.name = s.trim(data.name); + } else { + unsetData.name = 1; + } + } + + if (data.email) { + if (!_.isEmpty(s.trim(data.email))) { + setData.visitorEmails = [ + { address: s.trim(data.email) } + ]; + } else { + unsetData.visitorEmails = 1; + } + } + + if (data.phone) { + if (!_.isEmpty(s.trim(data.phone))) { + setData.phone = [ + { phoneNumber: s.trim(data.phone) } + ]; + } else { + unsetData.phone = 1; + } + } + + const update = {}; + + if (!_.isEmpty(setData)) { + update.$set = setData; + } + + if (!_.isEmpty(unsetData)) { + update.$unset = unsetData; + } + + if (_.isEmpty(update)) { + return true; + } + + return this.update({ _id }, update); +}; + +RocketChat.models.Users.findOneGuestByEmailAddress = function(emailAddress) { + const query = { + 'visitorEmails.address': new RegExp('^' + s.escapeRegExp(emailAddress) + '$', 'i') + }; + + return this.findOne(query); +}; + +RocketChat.models.Users.getAgentInfo = function(agentId) { + const query = { + _id: agentId + }; + + const options = { + fields: { + name: 1, + username: 1, + emails: 1, + customFields: 1 + } + }; + + return this.findOne(query, options); +}; diff --git a/packages/rocketchat-livechat/server/models/indexes.js b/packages/rocketchat-livechat/server/models/indexes.js index de862be54c18bbbe52d781f128f3d2053d059fef..6dbd49633926c8014649317aad617b602ad8fd4a 100644 --- a/packages/rocketchat-livechat/server/models/indexes.js +++ b/packages/rocketchat-livechat/server/models/indexes.js @@ -1,4 +1,5 @@ Meteor.startup(function() { RocketChat.models.Rooms.tryEnsureIndex({ code: 1 }); RocketChat.models.Rooms.tryEnsureIndex({ open: 1 }, { sparse: 1 }); + RocketChat.models.Users.tryEnsureIndex({ 'visitorEmails.address': 1 }); }); diff --git a/packages/rocketchat-livechat/server/publications/livechatInquiries.js b/packages/rocketchat-livechat/server/publications/livechatInquiries.js index db2b43bb2db02367c296b6a624dd22f89c67b600..e32adc9c8d96879af1dfa0774f7824ed5c8ccccd 100644 --- a/packages/rocketchat-livechat/server/publications/livechatInquiries.js +++ b/packages/rocketchat-livechat/server/publications/livechatInquiries.js @@ -7,6 +7,10 @@ Meteor.publish('livechat:inquiry', function() { return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:inquiry' })); } + const query = { + agents: this.userId, + status: 'open' + }; - return RocketChat.models.LivechatInquiry.find(); + return RocketChat.models.LivechatInquiry.find(query); }); diff --git a/packages/rocketchat-livechat/server/publications/livechatRooms.js b/packages/rocketchat-livechat/server/publications/livechatRooms.js index d4f498a49df135d9054ed1b9133f2fd391ed3232..b05e442fa9c740be7bb3633cac589d5f782d019e 100644 --- a/packages/rocketchat-livechat/server/publications/livechatRooms.js +++ b/packages/rocketchat-livechat/server/publications/livechatRooms.js @@ -11,8 +11,8 @@ Meteor.publish('livechat:rooms', function(filter = {}, offset = 0, limit = 20) { name: Match.Maybe(String), // room name to filter agent: Match.Maybe(String), // agent _id who is serving status: Match.Maybe(String), // either 'opened' or 'closed' - from: Match.Maybe(String), - to: Match.Maybe(String) + from: Match.Maybe(Date), + to: Match.Maybe(Date) }); let query = {}; @@ -29,12 +29,38 @@ Meteor.publish('livechat:rooms', function(filter = {}, offset = 0, limit = 20) { query.open = { $exists: false }; } } - if (filter.from && filter.to) { - var StartDate = new Date(filter.from); - var ToDate = new Date(filter.to); - ToDate.setDate(ToDate.getDate() + 1); - query['ts'] = { $gt: StartDate, $lt: ToDate }; + if (filter.from) { + query.ts = { + $gte: filter.from + }; } + if (filter.to) { + filter.to.setDate(filter.to.getDate() + 1); + filter.to.setSeconds(filter.to.getSeconds() - 1); - return RocketChat.models.Rooms.findLivechat(query, offset, limit); + if (!query.ts) { + query.ts = {}; + } + query.ts.$lte = filter.to; + } + + let self = this; + + let handle = RocketChat.models.Rooms.findLivechat(query, offset, limit).observeChanges({ + added(id, fields) { + self.added('livechatRoom', id, fields); + }, + changed(id, fields) { + self.changed('livechatRoom', id, fields); + }, + removed(id) { + self.removed('livechatRoom', id); + } + }); + + this.ready(); + + this.onStop(() => { + handle.stop(); + }); }); diff --git a/packages/rocketchat-livechat/server/publications/livechatTriggers.js b/packages/rocketchat-livechat/server/publications/livechatTriggers.js new file mode 100644 index 0000000000000000000000000000000000000000..3baeb011f402bb69c8a37a3f1dbba11b8828039b --- /dev/null +++ b/packages/rocketchat-livechat/server/publications/livechatTriggers.js @@ -0,0 +1,15 @@ +Meteor.publish('livechat:triggers', function(_id) { + if (!this.userId) { + return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:triggers' })); + } + + if (!RocketChat.authz.hasPermission(this.userId, 'view-livechat-manager')) { + return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:triggers' })); + } + + if (_id !== undefined) { + return RocketChat.models.LivechatTrigger.findById(_id); + } else { + return RocketChat.models.LivechatTrigger.find(); + } +}); diff --git a/packages/rocketchat-livechat/server/publications/visitorHistory.js b/packages/rocketchat-livechat/server/publications/visitorHistory.js index 00bddde40c6fe1231c40744c836802e06d124bbf..2b26b101ad14dd61a9df9dc49d640368803cf677 100644 --- a/packages/rocketchat-livechat/server/publications/visitorHistory.js +++ b/packages/rocketchat-livechat/server/publications/visitorHistory.js @@ -16,6 +16,7 @@ Meteor.publish('livechat:visitorHistory', function({ rid: roomId }) { } if (room && room.v && room.v._id) { + // CACHE: can we stop using publications here? return RocketChat.models.Rooms.findByVisitorId(room.v._id); } else { return this.ready(); diff --git a/packages/rocketchat-livechat/server/startup.js b/packages/rocketchat-livechat/server/startup.js index fe66acc87413432f03054d2b6eb982b38038dec2..dd06a08ef01e4374ee747d2e8b567331be5b4a9b 100644 --- a/packages/rocketchat-livechat/server/startup.js +++ b/packages/rocketchat-livechat/server/startup.js @@ -1,26 +1,16 @@ Meteor.startup(() => { - RocketChat.roomTypes.setPublish('l', (code) => { - return RocketChat.models.Rooms.findLivechatByCode(code, { - name: 1, - t: 1, - cl: 1, - u: 1, - label: 1, - usernames: 1, - v: 1, - livechatData: 1, - topic: 1, - tags: 1, - sms: 1, - code: 1, - open: 1 - }); + RocketChat.roomTypes.setRoomFind('l', (code) => { + return RocketChat.models.Rooms.findLivechatByCode(code).fetch(); }); RocketChat.authz.addRoomAccessValidator(function(room, user) { return room.t === 'l' && RocketChat.authz.hasPermission(user._id, 'view-livechat-rooms'); }); + RocketChat.authz.addRoomAccessValidator(function(room, user) { + return room.t === 'l' && room.v && room.v._id === user._id; + }); + RocketChat.callbacks.add('beforeLeaveRoom', function(user, room) { if (room.t !== 'l') { return user; diff --git a/packages/rocketchat-livechat/server/unclosedLivechats.js b/packages/rocketchat-livechat/server/unclosedLivechats.js index 3dd99efe20321f79619140678132d01d95542210..f5c64225ce8e175c437a8b91132650e977ac1c72 100644 --- a/packages/rocketchat-livechat/server/unclosedLivechats.js +++ b/packages/rocketchat-livechat/server/unclosedLivechats.js @@ -76,12 +76,12 @@ RocketChat.settings.get('Livechat_agent_leave_action', function(key, value) { } }); -UserPresenceMonitor.onSetUserStatus((user, status, statusConnection) => { +UserPresenceMonitor.onSetUserStatus((user, status/*, statusConnection*/) => { if (!monitorAgents) { return; } if (onlineAgents.exists(user._id)) { - if (statusConnection === 'offline' || user.statusLivechat === 'not-available') { + if (status === 'offline' || user.statusLivechat === 'not-available') { onlineAgents.remove(user._id, () => { runAgentLeaveAction(user._id); }); diff --git a/packages/rocketchat-logger/client/views/viewLogs.coffee b/packages/rocketchat-logger/client/views/viewLogs.coffee index cb86a98cce9895b638da0c1138230a6687314e65..29a96bf9e15a05163a64fef95a1dcc06429aa1e3 100644 --- a/packages/rocketchat-logger/client/views/viewLogs.coffee +++ b/packages/rocketchat-logger/client/views/viewLogs.coffee @@ -14,7 +14,7 @@ Template.viewLogs.helpers ansispan: (string) -> string = ansispan(string.replace(/\s/g, ' ').replace(/(\\n|\n)/g, '<br>')) - string = string.replace(/(.\d{8}-\d\d:\d\d:\d\d\.\d\d\d\(?.{0,2}\)?)/, '<span class="time">$1</span>') + string = string.replace(/(.\d{8}-\d\d:\d\d:\d\d\.\d\d\d\(?.{0,2}\)?)/, '<span class="terminal-time">$1</span>') return string formatTS: (date) -> diff --git a/packages/rocketchat-logger/client/views/viewLogs.html b/packages/rocketchat-logger/client/views/viewLogs.html index 2c136da2059bda5f2d02b25f8711205b6c49d785..88ab40c1914929d4cdde4280a7b67277c319d0ab 100644 --- a/packages/rocketchat-logger/client/views/viewLogs.html +++ b/packages/rocketchat-logger/client/views/viewLogs.html @@ -2,13 +2,13 @@ {{#if hasPermission}} <div class="section terminal"> {{#each logs}} - <div> - <!-- <span class="time">{{formatTS ts}}</span> --> + <div class="terminal-line"> + {{!-- <span class="terminal-time">{{formatTS ts}}</span> --}} {{{ansispan string}}} </div> {{/each}} </div> - <div class="new-logs not"> + <div class="new-logs not color-primary-action-color"> <i class="icon-down-big"></i> <span>{{_ "New_logs"}}</span> </div> diff --git a/packages/rocketchat-mailer/client/views/mailer.html b/packages/rocketchat-mailer/client/views/mailer.html index 2b50819d6fe5756fafef5b97b4c78abca7967ee5..93e136356216e00a6d7c40982a9d4a624ba642ae 100644 --- a/packages/rocketchat-mailer/client/views/mailer.html +++ b/packages/rocketchat-mailer/client/views/mailer.html @@ -1,6 +1,6 @@ <template name="mailer"> <section class="page-container page-list"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Mailer"}}</span> @@ -19,7 +19,7 @@ <input type="text" name="from" value="" placeholder="{{fromEmail}}" /> </div> <div> - <small class="settings-description">{{{_ "From_email_warning"}}}</small> + <small class="settings-description secondary-font-color">{{{_ "From_email_warning"}}}</small> </div> </div> <div class="input-line"> @@ -28,7 +28,7 @@ <input type="checkbox" name="dryrun" value="1" /> </div> <div> - <small class="settings-description">{{{_ "Dry_run_description"}}}</small> + <small class="settings-description secondary-font-color">{{{_ "Dry_run_description"}}}</small> </div> </div> <div class="input-line"> @@ -37,7 +37,7 @@ <input type="text" name="query" value="" /> </div> <div> - <small class="settings-description">{{{_ "Query_description"}}}</small> + <small class="settings-description secondary-font-color">{{{_ "Query_description"}}}</small> </div> </div> <div class="input-line"> @@ -52,7 +52,7 @@ <textarea name="body" rows="10" style="height: auto"></textarea> </div> <div> - <small class="settings-description">{{{_ "Mailer_body_tags"}}}</small> + <small class="settings-description secondary-font-color">{{{_ "Mailer_body_tags"}}}</small> </div> </div> </fieldset> diff --git a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.html b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.html index 1f2739055cddb0689b548d0a6642e4a63053f948..e00707e7b5fb2542815b7868c88c38a963d6b0c1 100644 --- a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.html +++ b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.html @@ -1,12 +1,12 @@ <template name="mailerUnsubscribe"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> <header> <a class="logo" href="/"> <img src="images/logo/logo.svg?v=3" /> </a> </header> - <div class="cms-page"> + <div class="cms-page content-background-color"> {{_ "You_have_successfully_unsubscribed"}} </div> </div> diff --git a/packages/rocketchat-markdown/markdown.coffee b/packages/rocketchat-markdown/markdown.coffee index 61d11f0906a7e3bbe162771502fea4b5dcce15c7..2408f3a3621a364ecbac47820dc673fd9b51ee37 100644 --- a/packages/rocketchat-markdown/markdown.coffee +++ b/packages/rocketchat-markdown/markdown.coffee @@ -56,17 +56,17 @@ class Markdown # >>> # Text # <<< - msg = msg.replace(/(?:>){3}\n+([\s\S]*?)\n+(?:<){3}/g, '<blockquote><span class="copyonly">>>></span>$1<span class="copyonly"><<<</span></blockquote>') + msg = msg.replace(/(?:>){3}\n+([\s\S]*?)\n+(?:<){3}/g, '<blockquote class="background-transparent-darker-before"><span class="copyonly">>>></span>$1<span class="copyonly"><<<</span></blockquote>') # Support >Text for quote - msg = msg.replace(/^>(.*)$/gm, '<blockquote><span class="copyonly">></span>$1</blockquote>') + msg = msg.replace(/^>(.*)$/gm, '<blockquote class="background-transparent-darker-before"><span class="copyonly">></span>$1</blockquote>') # Remove white-space around blockquote (prevent <br>). Because blockquote is block element. - msg = msg.replace(/\s*<blockquote>/gm, '<blockquote>') + msg = msg.replace(/\s*<blockquote class="background-transparent-darker-before">/gm, '<blockquote class="background-transparent-darker-before">') msg = msg.replace(/<\/blockquote>\s*/gm, '</blockquote>') # Remove new-line between blockquotes. - msg = msg.replace(/<\/blockquote>\n<blockquote>/gm, '</blockquote><blockquote>') + msg = msg.replace(/<\/blockquote>\n<blockquote/gm, '</blockquote><blockquote') if not _.isString message message.html = msg diff --git a/packages/rocketchat-markdown/markdowncode.coffee b/packages/rocketchat-markdown/markdowncode.coffee index 0d0bfd0d15cd8b1d9d07ba043ebae5135c54ee97..e74877286cc83b771897089345ad72582011a40c 100644 --- a/packages/rocketchat-markdown/markdowncode.coffee +++ b/packages/rocketchat-markdown/markdowncode.coffee @@ -23,7 +23,7 @@ class MarkdownCode message.tokens.push token: token - text: "#{p1}<span class=\"copyonly\">`</span><span><code class=\"inline\">#{p2}</code></span><span class=\"copyonly\">`</span>#{p3}" + text: "#{p1}<span class=\"copyonly\">`</span><span><code class=\"code-colors inline\">#{p2}</code></span><span class=\"copyonly\">`</span>#{p3}" return token @@ -70,7 +70,7 @@ class MarkdownCode message.tokens.push highlight: true token: token - text: "<pre><code class='hljs " + result.language + "'><span class='copyonly'>```<br></span>" + result.value + "<span class='copyonly'><br>```</span></code></pre>" + text: "<pre><code class='code-colors hljs " + result.language + "'><span class='copyonly'>```<br></span>" + result.value + "<span class='copyonly'><br>```</span></code></pre>" msgParts[index] = token else diff --git a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee index 4aef3aa7e71791f27c16376d36dd5bbbe3cf3569..0d6fab788374077235d6cc1416dedd939ae8e9ff 100644 --- a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee +++ b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee @@ -8,9 +8,6 @@ Template.mentionsFlexTab.helpers message: -> return _.extend(this, { customClass: 'mentions' }) - notReadySubscription: -> - return 'notready' unless Template.instance().subscriptionsReady() - hasMore: -> return Template.instance().hasMore.get() diff --git a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.html b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.html index 88b36c3e7418f29be1928b6f97625150a736ca59..42c5a38ef7428370237ff5d6a798d29f65de9e69 100644 --- a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.html +++ b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.html @@ -14,15 +14,11 @@ {{#each messages}} {{#nrr nrrargs 'message' message}}{{/nrr}} {{/each}} - {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> - {{/if}} </ul> + {{#if hasMore}} + <div class="load-more"> + {{> loading}} + </div> + {{/if}} </div> </template> diff --git a/packages/rocketchat-mentions-flextab/client/views/stylesheets/mentionsFlexTab.less b/packages/rocketchat-mentions-flextab/client/views/stylesheets/mentionsFlexTab.less index eaf5a1253115ea83e9ef94c94691bf70ee04b1f5..c71664547c84e20e008152905b3bed6b0e2e2caa 100644 --- a/packages/rocketchat-mentions-flextab/client/views/stylesheets/mentionsFlexTab.less +++ b/packages/rocketchat-mentions-flextab/client/views/stylesheets/mentionsFlexTab.less @@ -1,33 +1,11 @@ .mentioned-messages-list { - &.notready { - background-image: url(images/logo/loading.gif); - background-repeat: no-repeat; - background-position: 50% 50%; - height: 100px; - - .message { - display: none; - } - } - - li.empty { - text-align: center; - margin-top: 60px; - } - .message-cog-container { .message-action { display: none !important; + &.jump-to-message { display: block !important; } } } - - .load-more { - text-transform: lowercase; - text-align: center; - line-height: 40px; - font-style: italic; - } } diff --git a/packages/rocketchat-mentions/client.coffee b/packages/rocketchat-mentions/client.coffee index a511cc6ba6db0a6124f733c91b2c12016e333488..2132253a829f559deff85422a6ca3a8d2e342f51 100644 --- a/packages/rocketchat-mentions/client.coffee +++ b/packages/rocketchat-mentions/client.coffee @@ -21,7 +21,7 @@ class MentionsClient mentions = mentions.join('|') msg = msg.replace new RegExp("(?:^|\\s|\\n)(@(#{mentions}):?)[:.,\s]?", 'g'), (match, mention, username) -> if username is 'all' or username is 'here' - return match.replace mention, "<a class=\"mention-link mention-link-me mention-link-all\">#{mention}</a>" + return match.replace mention, "<a class=\"mention-link mention-link-me mention-link-all background-attention-color\">#{mention}</a>" if not message.temp? if not _.findWhere(message.mentions, {username: username})? @@ -29,7 +29,7 @@ class MentionsClient classes = 'mention-link' if username is me - classes += ' mention-link-me' + classes += ' mention-link-me background-primary-action-color' return match.replace mention, "<a class=\"#{classes}\" data-username=\"#{username}\">#{mention}</a>" diff --git a/packages/rocketchat-message-attachments/client/messageAttachment.coffee b/packages/rocketchat-message-attachments/client/messageAttachment.coffee index 195fb7cfb1ff347edff2064ce709a74a4a1b41c8..a03b2b66f35b3c6873293193e5fbf87b2b92f104 100644 --- a/packages/rocketchat-message-attachments/client/messageAttachment.coffee +++ b/packages/rocketchat-message-attachments/client/messageAttachment.coffee @@ -12,6 +12,8 @@ Template.messageAttachment.helpers if Meteor.settings.public.sandstorm or url.match /^(https?:)?\/\//i return url + else if navigator.userAgent.indexOf('Electron') > -1 + return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url else return Meteor.absoluteUrl().replace(/\/$/, '') + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url diff --git a/packages/rocketchat-message-attachments/client/messageAttachment.html b/packages/rocketchat-message-attachments/client/messageAttachment.html index 7860efe8775ad7fa0f3919e44320f978be334cc6..b4f81b11ed1a84d3c8a511e0ab4c52f913c8eb69 100644 --- a/packages/rocketchat-message-attachments/client/messageAttachment.html +++ b/packages/rocketchat-message-attachments/client/messageAttachment.html @@ -3,7 +3,7 @@ <!-- <div>fallback: {{fallback}}</div> --> {{pretext}} <div class="attachment-block"> - <div class="attachment-block-border" style="background-color: {{color}}"></div> + <div class="attachment-block-border background-info-font-color" style="background-color: {{color}}"></div> {{#if author_name}} {{#if author_link}} <div class="attachment-author"> @@ -121,6 +121,13 @@ </div> {{/unless}} {{/if}} + + {{#if description}} + <div class="attachment-description"> + <p>{{{description}}}</p> + </div> + {{/if}} + {{#each attachments}} {{injectIndex . ../index @index}} {{> messageAttachment}} {{/each}} diff --git a/packages/rocketchat-message-attachments/client/stylesheets/loader.coffee b/packages/rocketchat-message-attachments/client/stylesheets/loader.coffee deleted file mode 100644 index 767a5689253734975995b2551924a8fbe54e9528..0000000000000000000000000000000000000000 --- a/packages/rocketchat-message-attachments/client/stylesheets/loader.coffee +++ /dev/null @@ -1,2 +0,0 @@ -RocketChat.theme.addPackageAsset -> - return Assets.getText 'client/stylesheets/messageAttachments.less' diff --git a/packages/rocketchat-message-attachments/client/stylesheets/messageAttachments.less b/packages/rocketchat-message-attachments/client/stylesheets/messageAttachments.less index edf23591ed6bbd5d6b3fd0a5bfed638d7dc5be6a..f0cf4996cace3db3a48190bb6e279201e543c128 100644 --- a/packages/rocketchat-message-attachments/client/stylesheets/messageAttachments.less +++ b/packages/rocketchat-message-attachments/client/stylesheets/messageAttachments.less @@ -12,9 +12,11 @@ top: 0; bottom: 0; } + html.rtl & { padding-left: 0; padding-right: 15px; + .attachment-block-border { left: auto; right: 0; @@ -37,9 +39,10 @@ margin-right: 2px; margin-bottom: -2px; } + .time { font-weight: normal; - font-size: .8em; + font-size: 0.8em; } } @@ -86,7 +89,8 @@ .attachment-thumb { padding-right: 10px; padding-top: 5px; - line-height: 0px; + line-height: 0; + html.rtl & { padding-right: 5px; padding-top: 10px; @@ -115,6 +119,7 @@ border-width: 1px; border-radius: 5px; margin-left: 5px; + html.rtl & { margin-right: 5px; margin-left: auto; diff --git a/packages/rocketchat-message-attachments/package.js b/packages/rocketchat-message-attachments/package.js index 0b43fed4b3024ff89c0725d359f6d273c80311f9..c6282012e225eb0bdb5ee401b58c70134c446828 100644 --- a/packages/rocketchat-message-attachments/package.js +++ b/packages/rocketchat-message-attachments/package.js @@ -11,13 +11,13 @@ Package.onUse(function(api) { 'ecmascript', 'coffeescript', 'underscore', - 'rocketchat:lib' + 'rocketchat:lib', + 'less' ]); api.addFiles('client/messageAttachment.html', 'client'); api.addFiles('client/messageAttachment.coffee', 'client'); // stylesheets - api.addAssets('client/stylesheets/messageAttachments.less', 'server'); - api.addFiles('client/stylesheets/loader.coffee', 'server'); + api.addFiles('client/stylesheets/messageAttachments.less', 'client'); }); diff --git a/packages/rocketchat-message-pin/client/views/pinnedMessages.html b/packages/rocketchat-message-pin/client/views/pinnedMessages.html index e932f46ee7a2c490f15820e1b2d1badf6344519d..57b080da5dfb65aec8472402d0e6dff59d00ebde 100644 --- a/packages/rocketchat-message-pin/client/views/pinnedMessages.html +++ b/packages/rocketchat-message-pin/client/views/pinnedMessages.html @@ -14,15 +14,11 @@ {{#each messages}} {{#nrr nrrargs 'message' message}}{{/nrr}} {{/each}} - {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> - {{/if}} </ul> + {{#if hasMore}} + <div class="load-more"> + {{> loading}} + </div> + {{/if}} </div> </template> diff --git a/packages/rocketchat-message-pin/client/views/stylesheets/messagepin.less b/packages/rocketchat-message-pin/client/views/stylesheets/messagepin.less index f6fced030741ccb32ba459e731568d1009cefaf3..fdc99ceb45c981bbb20a4f77c846caadda0e6131 100644 --- a/packages/rocketchat-message-pin/client/views/stylesheets/messagepin.less +++ b/packages/rocketchat-message-pin/client/views/stylesheets/messagepin.less @@ -1,4 +1,4 @@ -.icon-pin.rotate-45:before { +.icon-pin.rotate-45::before { -ms-transform: rotate(45deg); -webkit-transform: rotate(45deg); transform: rotate(45deg); @@ -18,7 +18,7 @@ font-style: italic; .load-more-loading { - color: #aaa; + color: #aaaaaa; } } } diff --git a/packages/rocketchat-message-pin/server/pinMessage.coffee b/packages/rocketchat-message-pin/server/pinMessage.coffee index faa73ce57972ce4f9561ee5317927f65816ecaaf..f2049c584652f113f08988894039fe2f82f2e685 100644 --- a/packages/rocketchat-message-pin/server/pinMessage.coffee +++ b/packages/rocketchat-message-pin/server/pinMessage.coffee @@ -11,28 +11,33 @@ Meteor.methods if Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) is -1 return false + originalMessage = RocketChat.models.Messages.findOneById message._id + + if not originalMessage?._id? + throw new Meteor.Error 'error-invalid-message', 'Message you are pinning was not found', { method: 'pinMessage', action: 'Message_pinning' } + # If we keep history of edits, insert a new message to store history information if RocketChat.settings.get 'Message_KeepHistory' RocketChat.models.Messages.cloneAndSaveAsHistoryById message._id me = RocketChat.models.Users.findOneById Meteor.userId() - message.pinned = true - message.pinnedAt = pinnedAt || Date.now - message.pinnedBy = + originalMessage.pinned = true + originalMessage.pinnedAt = pinnedAt || Date.now + originalMessage.pinnedBy = _id: Meteor.userId() username: me.username - message = RocketChat.callbacks.run 'beforeSaveMessage', message + originalMessage = RocketChat.callbacks.run 'beforeSaveMessage', originalMessage - RocketChat.models.Messages.setPinnedByIdAndUserId message._id, message.pinnedBy, message.pinned + RocketChat.models.Messages.setPinnedByIdAndUserId originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned - RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser 'message_pinned', message.rid, '', me, + RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser 'message_pinned', originalMessage.rid, '', me, attachments: [ - "text" : message.msg - "author_name" : message.u.username, - "author_icon" : getAvatarUrlFromUsername(message.u.username), - "ts" : message.ts + "text" : originalMessage.msg + "author_name" : originalMessage.u.username, + "author_icon" : getAvatarUrlFromUsername(originalMessage.u.username), + "ts" : originalMessage.ts ] unpinMessage: (message) -> @@ -47,20 +52,25 @@ Meteor.methods if Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) is -1 return false + originalMessage = RocketChat.models.Messages.findOneById message._id + + if not originalMessage?._id? + throw new Meteor.Error 'error-invalid-message', 'Message you are unpinning was not found', { method: 'unpinMessage', action: 'Message_pinning' } + # If we keep history of edits, insert a new message to store history information if RocketChat.settings.get 'Message_KeepHistory' - RocketChat.models.Messages.cloneAndSaveAsHistoryById message._id + RocketChat.models.Messages.cloneAndSaveAsHistoryById originalMessage._id me = RocketChat.models.Users.findOneById Meteor.userId() - message.pinned = false - message.pinnedBy = + originalMessage.pinned = false + originalMessage.pinnedBy = _id: Meteor.userId() username: me.username - message = RocketChat.callbacks.run 'beforeSaveMessage', message + originalMessage = RocketChat.callbacks.run 'beforeSaveMessage', originalMessage - RocketChat.models.Messages.setPinnedByIdAndUserId message._id, message.pinnedBy, message.pinned + RocketChat.models.Messages.setPinnedByIdAndUserId originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned # Meteor.defer -> diff --git a/packages/rocketchat-message-snippet/client/page/stylesheets/snippetPage.less b/packages/rocketchat-message-snippet/client/page/stylesheets/snippetPage.less index 5b84f4153566ceade403563c9ef504ebd935545e..4110c87b2dbcb55d04de92851ec32865af26340f 100644 --- a/packages/rocketchat-message-snippet/client/page/stylesheets/snippetPage.less +++ b/packages/rocketchat-message-snippet/client/page/stylesheets/snippetPage.less @@ -4,62 +4,60 @@ @snippet-page-right-border-size: 30px; @snippet-informations-span-line-height: @snippet-informations-height / 2; - .snippet-page { - padding-top: @snippet-page-top-border-size; - padding-left: @snippet-page-left-border-size; - padding-right: @snippet-page-right-border-size; - overflow-y: auto; - overflow-x: hidden; - height: 100%; - width: auto; - - pre code, h1, span { - -webkit-user-select: text; - -khtml-user-select: all; - -moz-user-select: all; - -ms-user-select: all; - user-select: all; - } - - .snippet-informations { - display:inline-block; - - width: 100%; - height: 60px; - - div.avatar { - display: block; - float:left; - clear:left; - width: @snippet-informations-height; - height:@snippet-informations-height; - margin-right: 10px; - } - span.username { - display: block; - line-height: @snippet-informations-span-line-height; - width: 100%; - } - span.snippet-filename { - display:block; - line-height: @snippet-informations-span-line-height; - font-weight: bold; - width:100%; - } - } - - span.info { - font-style: italic; - line-height: @snippet-informations-span-line-height; - color: darkgrey; - } - - .download-button { - float: right; - } - - h1 { - } - + padding-top: @snippet-page-top-border-size; + padding-left: @snippet-page-left-border-size; + padding-right: @snippet-page-right-border-size; + overflow-y: auto; + overflow-x: hidden; + height: 100%; + width: auto; + + pre code, + h1, + span { + -webkit-user-select: text; + -khtml-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + } + + .snippet-informations { + display: inline-block; + width: 100%; + height: 60px; + + .avatar { + display: block; + float: left; + clear: left; + width: @snippet-informations-height; + height: @snippet-informations-height; + margin-right: 10px; + } + + .username { + display: block; + line-height: @snippet-informations-span-line-height; + width: 100%; + } + + .snippet-filename { + display: block; + line-height: @snippet-informations-span-line-height; + font-weight: bold; + width: 100%; + } + } + + .info { + font-style: italic; + line-height: @snippet-informations-span-line-height; + color: darkgrey; + } + + .download-button { + float: right; + } } diff --git a/packages/rocketchat-message-snippet/client/tabBar/views/snippetMessage.html b/packages/rocketchat-message-snippet/client/tabBar/views/snippetMessage.html index 858266510f3b4f7b964e5fc3dc741e0dff8d2f40..9755eed5a7a5ac66e2038a9a3c88384c8b6c9ed8 100644 --- a/packages/rocketchat-message-snippet/client/tabBar/views/snippetMessage.html +++ b/packages/rocketchat-message-snippet/client/tabBar/views/snippetMessage.html @@ -1,5 +1,5 @@ <template name="snippetMessage"> - <li id="{{_id}}" class="message {{isSequential}} {{system}} {{t}} {{own}} {{isTemp}} {{chatops}} {{customClass}}" data-username="{{u.username}}" data-groupable="{{isGroupable}}" data-date="{{date}}" data-timestamp="{{timestamp}}"> + <li id="{{_id}}" class="message background-transparent-dark-hover {{isSequential}} {{system}} {{t}} {{own}} {{isTemp}} {{chatops}} {{customClass}}" data-username="{{u.username}}" data-groupable="{{isGroupable}}" data-date="{{date}}" data-timestamp="{{timestamp}}"> <div class="day-divider"> <span>{{date}}</span> </div> diff --git a/packages/rocketchat-message-snippet/client/tabBar/views/snippetedMessages.html b/packages/rocketchat-message-snippet/client/tabBar/views/snippetedMessages.html index dc894507e4349221a6d0fc8593847b227d921e53..91409e64631252d430ec70dcb452074d0657f5dc 100644 --- a/packages/rocketchat-message-snippet/client/tabBar/views/snippetedMessages.html +++ b/packages/rocketchat-message-snippet/client/tabBar/views/snippetedMessages.html @@ -14,15 +14,11 @@ {{#each messages}} {{#nrr nrrargs 'snippetMessage' message}}{{/nrr}} {{/each}} - {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> - {{/if}} </ul> + {{#if hasMore}} + <div class="load-more"> + {{> loading}} + </div> + {{/if}} </div> </template> diff --git a/packages/rocketchat-message-star/client/views/starredMessages.html b/packages/rocketchat-message-star/client/views/starredMessages.html index 7487384a5f66f8e23252bb7d759a04734e3ae460..337e659bb56e1b83fc2203e3d632c66a622fbc78 100644 --- a/packages/rocketchat-message-star/client/views/starredMessages.html +++ b/packages/rocketchat-message-star/client/views/starredMessages.html @@ -14,15 +14,11 @@ {{#each messages}} {{#nrr nrrargs 'message' message}}{{/nrr}} {{/each}} - {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> - {{/if}} </ul> + {{#if hasMore}} + <div class="load-more"> + {{> loading}} + </div> + {{/if}} </div> </template> diff --git a/packages/rocketchat-message-star/client/views/stylesheets/messagestar.less b/packages/rocketchat-message-star/client/views/stylesheets/messagestar.less index 9b6efe0ac051322f41275ae5311a0783b0199b40..1fabb34a91e1f1cad56c42ed989cefca8ac9c80f 100644 --- a/packages/rocketchat-message-star/client/views/stylesheets/messagestar.less +++ b/packages/rocketchat-message-star/client/views/stylesheets/messagestar.less @@ -12,7 +12,7 @@ font-style: italic; .load-more-loading { - color: #aaa; + color: #aaaaaa; } } } diff --git a/packages/rocketchat-migrations/migrations.js b/packages/rocketchat-migrations/migrations.js index 6bd8c699aa180a58174deace3cb767b9af1659bf..ee54c145e6960a2c84009cabde2795b52845aa63 100644 --- a/packages/rocketchat-migrations/migrations.js +++ b/packages/rocketchat-migrations/migrations.js @@ -1,5 +1,5 @@ +/* eslint-disable */ import moment from 'moment'; - /* Adds migration capabilities. Migrations are defined like: @@ -28,8 +28,7 @@ import moment from 'moment'; be in an inconsistant state. */ -// since we'll be at version 0 by default, we should have a migration set for -// it. +// since we'll be at version 0 by default, we should have a migration set for it. var DefaultMigration = { version: 0, up: function() { @@ -276,7 +275,9 @@ Migrations._migrateTo = function(version, rerun) { log.info('Running ' + direction + '() on version ' + migration.version + maybeName()); try { - migration[direction](migration); + RocketChat.models._CacheControl.withValue(false, function() { + migration[direction](migration); + }); } catch (e) { console.log(makeABox([ "ERROR! SERVER STOPPED", diff --git a/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.html b/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.html index 51f3bb8c78cda1b490f3dfa22fb040c70efbe0d8..6038e7035a167caa4aa869b4bc8d21a40dee21b2 100644 --- a/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.html +++ b/packages/rocketchat-oauth2-server-config/admin/client/views/oauthApp.html @@ -16,14 +16,14 @@ <label>{{_ "Application_Name"}}</label> <div> <input type="text" name="name" value="{{data.name}}" /> - <div class="settings-description">{{_ "Give_the_application_a_name_This_will_be_seen_by_your_users"}}</div> + <div class="settings-description secondary-font-color">{{_ "Give_the_application_a_name_This_will_be_seen_by_your_users"}}</div> </div> </div> <div class="input-line double-col"> <label>{{_ "Redirect_URI"}}</label> <div> <input type="text" name="redirectUri" value="{{data.redirectUri}}" /> - <div class="settings-description">{{_ "After_OAuth2_authentication_users_will_be_redirected_to_this_URL"}}</div> + <div class="settings-description secondary-font-color">{{_ "After_OAuth2_authentication_users_will_be_redirected_to_this_URL"}}</div> </div> </div> {{#if data.clientId}} @@ -31,28 +31,28 @@ <label>{{_ "Client_ID"}}</label> <div> <input type="text" name="clientId" value="{{data.clientId}}" readonly="readonly" /> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=clientId]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=clientId]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> </div> </div> <div class="input-line double-col"> <label>{{_ "Client_Secret"}}</label> <div> <input type="text" name="clientSecret" value="{{data.clientSecret}}" readonly="readonly" /> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=clientSecret]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=clientSecret]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> </div> </div> <div class="input-line double-col"> <label>{{_ "Authorization_URL"}}</label> <div> <input type="text" name="authorization_url" value="{{data.authorization_url}}" readonly="readonly" /> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=authorization_url]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=authorization_url]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> </div> </div> <div class="input-line double-col"> <label>{{_ "Access_Token_URL"}}</label> <div> <input type="text" name="access_token_url" value="{{data.access_token_url}}" readonly="readonly" /> - <div class="settings-description"><button class="clipboard" data-clipboard-target="[name=access_token_url]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> + <div class="settings-description secondary-font-color"><button class="clipboard" data-clipboard-target="[name=access_token_url]">{{_ "COPY_TO_CLIPBOARD"}}</button></div> </div> </div> {{/if}} diff --git a/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.coffee b/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.coffee index c2acdd899d11a13db46046ecadea9d11e61d588b..d43d4ba16e2d88ebadbe468bc92e58563ef53e6c 100644 --- a/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.coffee +++ b/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.coffee @@ -44,4 +44,4 @@ Template.authorize.onRendered -> @autorun (c) => if Meteor.user()?.oauth?.athorizedClients?.indexOf(@data.client_id()) > -1 c.stop() - $('button').click() + $('button[type=submit]').click() diff --git a/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.html b/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.html index 859460f7f822e2fa81223999b6ba7cd4280753bb..49915dcf473889937731fccb9f3e8fbe9b7ab9de 100644 --- a/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.html +++ b/packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.html @@ -29,9 +29,8 @@ <input type="hidden" name="client_id" value="{{client_id}}"> <input type="hidden" name="redirect_uri" value="{{redirect_uri}}"> <input type="hidden" name="response_type" value="code"> - <div class="buttons"> + <div class="buttons-group"> <button id="logout-oauth" class="button danger logout">{{_ "Logout"}}</button> - <div class="horizontal-space"></div> <button id="cancel-oauth" type="button" class="button cancel">{{_ "Cancel"}}</button> <button type="submit" class="button primary save">{{_ "Authorize"}}</button> </div> diff --git a/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/load.coffee b/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/load.coffee deleted file mode 100644 index 631ef382a4e69a8a6ebda08537da49d86e9d3f1e..0000000000000000000000000000000000000000 --- a/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/load.coffee +++ /dev/null @@ -1,2 +0,0 @@ -RocketChat.theme.addPackageAsset -> - return Assets.getText 'oauth/client/stylesheets/oauth2.less' diff --git a/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/oauth2.less b/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/oauth2.less index e83fecf16ffdd0e13633cfebcc82631ffe68222a..1ff8f6f0e3baf9dcc6acd7b9384247d8d19f2564 100644 --- a/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/oauth2.less +++ b/packages/rocketchat-oauth2-server-config/oauth/client/stylesheets/oauth2.less @@ -4,12 +4,14 @@ flex-direction: column; align-items: center; justify-content: center; - left: 0px; - right: 0px; - top: 0px; - bottom: 0px; + left: 0; + right: 0; + top: 0; + bottom: 0; - ul, li, ol { + ul, + li, + ol { list-style: initial; } @@ -30,7 +32,7 @@ justify-content: center; padding-bottom: 20px; margin-bottom: 20px; - border-bottom: 1px solid #eee; + border-bottom: 1px solid #eeeeee; .thumb { height: 40px; @@ -40,6 +42,7 @@ .username { text-align: left; + h1 { font-size: 18px; } @@ -64,7 +67,7 @@ justify-content: center; .horizontal-space { - border-right: 1px solid #ddd; + border-right: 1px solid #dddddd; height: 14px; margin: 0 15px; } diff --git a/packages/rocketchat-oauth2-server-config/package.js b/packages/rocketchat-oauth2-server-config/package.js index 6f7c70c0f403cac59960e01443873cf8421a0a2c..4ad16fbca1cdc45a65a65551f5c9cb6021034cd2 100644 --- a/packages/rocketchat-oauth2-server-config/package.js +++ b/packages/rocketchat-oauth2-server-config/package.js @@ -12,6 +12,7 @@ Package.onUse(function(api) { api.use('rocketchat:api'); api.use('rocketchat:theme'); api.use('rocketchat:oauth2-server'); + api.use('less'); api.use('templating', 'client'); api.use('kadira:flow-router', 'client'); @@ -25,8 +26,7 @@ Package.onUse(function(api) { api.addFiles('oauth/server/oauth2-server.coffee', 'server'); api.addFiles('oauth/server/default-services.coffee', 'server'); - api.addAssets('oauth/client/stylesheets/oauth2.less', 'server'); - api.addFiles('oauth/client/stylesheets/load.coffee', 'server'); + api.addFiles('oauth/client/stylesheets/oauth2.less', 'client'); // Client api.addFiles('oauth/client/oauth2-client.html', 'client'); diff --git a/packages/rocketchat-oembed/client/oembedAudioWidget.html b/packages/rocketchat-oembed/client/oembedAudioWidget.html index 2cd1035c87369e5eb1dbad946884cbfa11bc9ba9..b4eea0aa8d1a5390b29b7c66f09fb30f0108c6b3 100644 --- a/packages/rocketchat-oembed/client/oembedAudioWidget.html +++ b/packages/rocketchat-oembed/client/oembedAudioWidget.html @@ -5,7 +5,7 @@ <span class="collapse-switch icon-right-dir" data-index="{{index}}" data-collapsed="{{collapsed}}"></span> {{else}} <span class="collapse-switch icon-down-dir" data-index="{{index}}" data-collapsed="{{collapsed}}"></span> - <blockquote> + <blockquote class="background-transparent-darker-before"> <audio controls> <source src="{{url}}" type="{{headers.contentType}}"> Your browser does not support the audio element. diff --git a/packages/rocketchat-oembed/client/oembedFrameWidget.html b/packages/rocketchat-oembed/client/oembedFrameWidget.html index 92f4297021aa3e1d34cc34381c56400b2a3540a1..04b58c3ed135a414f49159bce6f5993295b3fac2 100644 --- a/packages/rocketchat-oembed/client/oembedFrameWidget.html +++ b/packages/rocketchat-oembed/client/oembedFrameWidget.html @@ -1,6 +1,6 @@ <template name="oembedFrameWidget"> {{#if parsedUrl}} - <blockquote> + <blockquote class="background-transparent-darker-before"> {{#if meta.oembedProviderName}} {{#if meta.oembedProviderUrl}} <a href="{{meta.oembedProviderUrl}}" style="color: #9e9ea6">{{meta.oembedProviderName}}</a> diff --git a/packages/rocketchat-oembed/client/oembedSandstormGrain.html b/packages/rocketchat-oembed/client/oembedSandstormGrain.html index 97157a6b0b7c82fb45341fc28f8fd21b2de5f8d5..b5a2ac272b28897eebc9a5f9ef0b7183d61d0755 100644 --- a/packages/rocketchat-oembed/client/oembedSandstormGrain.html +++ b/packages/rocketchat-oembed/client/oembedSandstormGrain.html @@ -1,5 +1,5 @@ <template name="oembedSandstormGrain"> - <blockquote class="sandstorm-grain"> + <blockquote class="sandstorm-grain background-transparent-darker-before"> <label> <button onclick="sandstormOembed(event)" data-token="{{token}}" data-descriptor="{{descriptor}}"> diff --git a/packages/rocketchat-oembed/client/oembedUrlWidget.coffee b/packages/rocketchat-oembed/client/oembedUrlWidget.coffee index 8178ac87c5ab55c13ce617161f76c422053fdb52..e78fa4cf73733467ba5913546c8192ae421aa874 100644 --- a/packages/rocketchat-oembed/client/oembedUrlWidget.coffee +++ b/packages/rocketchat-oembed/client/oembedUrlWidget.coffee @@ -36,7 +36,13 @@ Template.oembedUrlWidget.helpers url = decodedOgImage or this.meta.twitterImage - if url?[0] is '/' and this.parsedUrl?.host? + if not url? + return + + if url.indexOf('//') is 0 + url = "#{this.parsedUrl.protocol}#{url}" + + else if url.indexOf('/') is 0 and this.parsedUrl?.host? url = "#{this.parsedUrl.protocol}//#{this.parsedUrl.host}#{url}" return url diff --git a/packages/rocketchat-oembed/client/oembedUrlWidget.html b/packages/rocketchat-oembed/client/oembedUrlWidget.html index 4ca23a82abc215bff22746b8f6d4ba1b4bd6a004..b410473155b5adb27c02719f370a12dd937c6d25 100644 --- a/packages/rocketchat-oembed/client/oembedUrlWidget.html +++ b/packages/rocketchat-oembed/client/oembedUrlWidget.html @@ -1,6 +1,6 @@ <template name="oembedUrlWidget"> {{#if show}} - <blockquote> + <blockquote class="background-transparent-darker-before"> <div style="{{#if image}}min-height: 80px;{{/if}} padding: 10px 3px;"> {{#if image}} {{#if meta.ogImageUserGenerated}} diff --git a/packages/rocketchat-oembed/client/oembedVideoWidget.html b/packages/rocketchat-oembed/client/oembedVideoWidget.html index f29fa1f6119c750699d62352cf85e69f65103391..1a913e2dfb04c262a16c3708ccd84036c0f98f26 100644 --- a/packages/rocketchat-oembed/client/oembedVideoWidget.html +++ b/packages/rocketchat-oembed/client/oembedVideoWidget.html @@ -1,6 +1,6 @@ <template name="oembedVideoWidget"> {{#if parsedUrl}} - <blockquote> + <blockquote class="background-transparent-darker-before"> <div><a href="{{url}}">{{parsedUrl.host}}</a></div> <span>{{title}}</span> {{#if collapsed}} diff --git a/packages/rocketchat-oembed/client/oembedYoutubeWidget.html b/packages/rocketchat-oembed/client/oembedYoutubeWidget.html index 614c11df2d4d505157f7690f240c58a42b3ef913..7295e633e17a65deae60b9031529c8c4d89b85eb 100644 --- a/packages/rocketchat-oembed/client/oembedYoutubeWidget.html +++ b/packages/rocketchat-oembed/client/oembedYoutubeWidget.html @@ -1,6 +1,6 @@ <template name="oembedYoutubeWidget"> {{#if parsedUrl}} - <blockquote> + <blockquote class="background-transparent-darker-before"> <a href="{{url}}">{{parsedUrl.host}}</a> {{#if collapsed}} <span class="collapse-switch icon-right-dir" data-index="{{index}}" data-collapsed="{{collapsed}}"></span><br> diff --git a/packages/rocketchat-otr/client/rocketchat.otr.room.js b/packages/rocketchat-otr/client/rocketchat.otr.room.js index e5e7c591e4a4025128639b651a80beef6ac46175..a6defaec91e501acb2ca7c929180d86bd5300cdf 100644 --- a/packages/rocketchat-otr/client/rocketchat.otr.room.js +++ b/packages/rocketchat-otr/client/rocketchat.otr.room.js @@ -202,7 +202,7 @@ RocketChat.OTR.Room = class { } swal({ - title: '<i class=\'icon-key alert-icon\'></i>' + TAPi18n.__('OTR'), + title: '<i class=\'icon-key alert-icon success-color\'></i>' + TAPi18n.__('OTR'), text: TAPi18n.__('Username_wants_to_start_otr_Do_you_want_to_accept', { username: user.username }), html: true, showCancelButton: true, @@ -237,7 +237,7 @@ RocketChat.OTR.Room = class { this.reset(); const user = Meteor.users.findOne(this.peerId); swal({ - title: '<i class=\'icon-key alert-icon\'></i>' + TAPi18n.__('OTR'), + title: '<i class=\'icon-key alert-icon success-color\'></i>' + TAPi18n.__('OTR'), text: TAPi18n.__('Username_denied_the_OTR_session', { username: user.username }), html: true }); @@ -249,7 +249,7 @@ RocketChat.OTR.Room = class { this.reset(); const user = Meteor.users.findOne(this.peerId); swal({ - title: '<i class=\'icon-key alert-icon\'></i>' + TAPi18n.__('OTR'), + title: '<i class=\'icon-key alert-icon success-color\'></i>' + TAPi18n.__('OTR'), text: TAPi18n.__('Username_ended_the_OTR_session', { username: user.username }), html: true }); diff --git a/packages/rocketchat-otr/client/stylesheets/otr.less b/packages/rocketchat-otr/client/stylesheets/otr.less index c0e653fe050d1dac6678aa7a583c20416f98eabe..dd288cccd7a57d02a242f3ee1e82959645e40eb1 100644 --- a/packages/rocketchat-otr/client/stylesheets/otr.less +++ b/packages/rocketchat-otr/client/stylesheets/otr.less @@ -9,19 +9,18 @@ } .message { - &.otr, &.otr-ack { - .info:before { + &.otr, + &.otr-ack { + .info::before { font-family: 'fontello'; content: "\e952"; visibility: visible; display: inline-block; } } -} -.message { &.otr-ack { - .info:before { + .info::before { color: green; } } @@ -31,6 +30,7 @@ .input-message { padding-right: 48px; } + .inner-right-toolbar { .otr-icon { color: green; diff --git a/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less b/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less index 4a1d89635246eb1c9b5aa55d93e480cd87c05bfe..ad6f5d912f52592ae910cdf9bee90ad5ce83cb8b 100644 --- a/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less +++ b/packages/rocketchat-push-notifications/client/stylesheets/pushNotifications.less @@ -15,6 +15,7 @@ div span { font-size: 14px; + i.icon-pencil { font-size: 12px; margin-left: 3px; diff --git a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html index 695c96ae245f61d5c3ddcff33b36bc0e39c05ae8..edcc49c4c9ffb7ed59d22ecd7bddc8ce17e7d948 100644 --- a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html +++ b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html @@ -15,9 +15,9 @@ <label><input type="radio" name="desktopNotifications" value="nothing" checked="{{$eq desktopNotifications 'nothing'}}" /> {{_ "Nothing"}}</label> <br /> {{#if desktopNotificationDuration}} - <label>{{_ "Duration"}} ({{_ "seconds"}}) <input type="number" name="duration" min="0" value="{{desktopNotificationDuration}}"></label> + <label>{{_ "Duration"}} ({{_ "seconds"}}) <input type="number" name="duration" min="0" value="{{desktopNotificationDuration}}" class="content-background-color"></label> {{else}} - <label>{{_ "Duration"}} ({{_ "seconds"}}) <input type="number" name="duration" min="0" value="" placeholder="{{_ "Use_User_Preferences_or_Global_Settings"}}"></label> + <label>{{_ "Duration"}} ({{_ "seconds"}}) <input type="number" name="duration" min="0" value="" placeholder="{{_ "Use_User_Preferences_or_Global_Settings"}}" class="content-background-color"></label> {{/if}} <button type="button" class="button cancel">{{_ "Cancel"}}</button> @@ -36,7 +36,7 @@ </li> {{/unless}} <li> - <label>{{_ "Mobile_push"}}</label> + <label>{{_ "Mobile"}}</label> <div> {{#if editing 'mobilePushNotifications'}} <label><input type="radio" name="mobilePushNotifications" value="all" checked="{{$eq mobilePushNotifications 'all'}}" /> {{_ "All_messages"}}</label> @@ -68,7 +68,7 @@ </li> {{#unless emailVerified}} <li> - <div class="alert alert-warning"> + <div class="alert alert-warning pending-background pending-border"> {{_ "You_wont_receive_email_notifications_because_you_have_not_verified_your_email"}} </div> </li> diff --git a/packages/rocketchat-reactions/client/init.js b/packages/rocketchat-reactions/client/init.js index 194f35bf968581aaffcb834b96ab643722ccc121..94373cb80a8e8834dff0858f807928bc67248ca0 100644 --- a/packages/rocketchat-reactions/client/init.js +++ b/packages/rocketchat-reactions/client/init.js @@ -7,7 +7,7 @@ Template.room.events({ let user = Meteor.user(); let room = RocketChat.models.Rooms.findOne({ _id: data._arguments[1].rid }); - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !room.reactWhenReadOnly) { return false; } @@ -57,10 +57,12 @@ Meteor.startup(function() { let room = RocketChat.models.Rooms.findOne({ _id: message.rid }); let user = Meteor.user(); - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !room.reactWhenReadOnly) { return false; } else if (!RocketChat.models.Subscriptions.findOne({ rid: message.rid })) { return false; + } else if (message.private) { + return false; } return true; diff --git a/packages/rocketchat-reactions/client/methods/setReaction.js b/packages/rocketchat-reactions/client/methods/setReaction.js index ed3e7fa8b253290b2c962f9705d89a746ca374ea..2aff5c59d2840d9352c6953f1471280d8ec35edf 100644 --- a/packages/rocketchat-reactions/client/methods/setReaction.js +++ b/packages/rocketchat-reactions/client/methods/setReaction.js @@ -9,10 +9,12 @@ Meteor.methods({ let message = RocketChat.models.Messages.findOne({ _id: messageId }); let room = RocketChat.models.Rooms.findOne({ _id: message.rid }); - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !room.reactWhenReadOnly) { return false; } else if (!RocketChat.models.Subscriptions.findOne({ rid: message.rid })) { return false; + } else if (message.private) { + return false; } if (message.reactions && message.reactions[reaction] && message.reactions[reaction].usernames.indexOf(user.username) !== -1) { @@ -25,8 +27,10 @@ Meteor.methods({ if (_.isEmpty(message.reactions)) { delete message.reactions; RocketChat.models.Messages.unsetReactions(messageId); + RocketChat.callbacks.run('unsetReaction', messageId, reaction); } else { RocketChat.models.Messages.setReactions(messageId, message.reactions); + RocketChat.callbacks.run('setReaction', messageId, reaction); } } else { if (!message.reactions) { @@ -40,6 +44,7 @@ Meteor.methods({ message.reactions[reaction].usernames.push(user.username); RocketChat.models.Messages.setReactions(messageId, message.reactions); + RocketChat.callbacks.run('setReaction', messageId, reaction); } return; diff --git a/packages/rocketchat-reactions/client/stylesheets/reaction.less b/packages/rocketchat-reactions/client/stylesheets/reaction.less index c899b0c8c3834312899f47a9b9b89831b1dfd3f7..e16f4e2a9bbbe12a14017b493eb56c6c4dc7b6f5 100644 --- a/packages/rocketchat-reactions/client/stylesheets/reaction.less +++ b/packages/rocketchat-reactions/client/stylesheets/reaction.less @@ -2,6 +2,7 @@ .reactions { padding: 0; margin-top: 4px; + > li { cursor: pointer; position: relative; @@ -9,18 +10,19 @@ line-height: 20px; display: inline-block; border-radius: 5px; - border: 1px solid #E7E7E7; - background-color: #FCFCFC; + border: 1px solid #e7e7e7; + background-color: #fcfcfc; padding: 0 4px 0 2px; - color: #aaa; + color: #aaaaaa; .reaction-emoji { - .emoji, .emojione { + .emoji, + .emojione { min-width: 16px; min-height: 16px; width: 16px; height: 16px; - margin: -.2ex .15em .2ex; + margin: -0.2ex 0.15em 0.2ex; } } @@ -43,15 +45,14 @@ &.add-reaction { visibility: hidden; margin-left: 10px; - color: #888; + color: #888888; opacity: 0; padding: 0 2px; - - .transition(opacity 0.2s ease); + transition: opacity 0.2s ease; html.rtl & { margin-right: 10px; - margin-left: 0px; + margin-left: 0; } } diff --git a/packages/rocketchat-reactions/loadStylesheets.js b/packages/rocketchat-reactions/loadStylesheets.js deleted file mode 100644 index 84dd4eef4230b37c660b9fb068cf2be54e5ff81d..0000000000000000000000000000000000000000 --- a/packages/rocketchat-reactions/loadStylesheets.js +++ /dev/null @@ -1,3 +0,0 @@ -RocketChat.theme.addPackageAsset(function() { - return Assets.getText('client/stylesheets/reaction.less'); -}); diff --git a/packages/rocketchat-reactions/package.js b/packages/rocketchat-reactions/package.js index 3134920987a0faa9134a3cfe5b2fd508117ce127..cc1b2ea8551a4adee2a8fa6327d4b0c105e8370f 100644 --- a/packages/rocketchat-reactions/package.js +++ b/packages/rocketchat-reactions/package.js @@ -12,6 +12,7 @@ Package.onUse(function(api) { api.use('rocketchat:lib'); api.use('rocketchat:theme'); api.use('rocketchat:ui'); + api.use('less'); api.addFiles('client/init.js', 'client'); @@ -19,6 +20,5 @@ Package.onUse(function(api) { api.addFiles('client/methods/setReaction.js', 'client'); api.addFiles('setReaction.js', 'server'); - api.addAssets('client/stylesheets/reaction.less', 'server'); - api.addFiles('loadStylesheets.js', 'server'); + api.addFiles('client/stylesheets/reaction.less', 'client'); }); diff --git a/packages/rocketchat-reactions/setReaction.js b/packages/rocketchat-reactions/setReaction.js index 8b00073968462a5b3b5b7bb197501ea7c2f335e0..a76eb83e1e70856275c95241e71ea4d1a8cc41cc 100644 --- a/packages/rocketchat-reactions/setReaction.js +++ b/packages/rocketchat-reactions/setReaction.js @@ -7,6 +7,10 @@ Meteor.methods({ let message = RocketChat.models.Messages.findOneById(messageId); + if (!message) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' }); + } + let room = Meteor.call('canAccessRoom', message.rid, Meteor.userId()); if (!room) { @@ -15,7 +19,7 @@ Meteor.methods({ const user = Meteor.user(); - if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) { + if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !room.reactWhenReadOnly) { RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { _id: Random.id(), rid: room._id, @@ -37,8 +41,10 @@ Meteor.methods({ if (_.isEmpty(message.reactions)) { delete message.reactions; RocketChat.models.Messages.unsetReactions(messageId); + RocketChat.callbacks.run('unsetReaction', messageId, reaction); } else { RocketChat.models.Messages.setReactions(messageId, message.reactions); + RocketChat.callbacks.run('setReaction', messageId, reaction); } } else { if (!message.reactions) { @@ -52,6 +58,7 @@ Meteor.methods({ message.reactions[reaction].usernames.push(user.username); RocketChat.models.Messages.setReactions(messageId, message.reactions); + RocketChat.callbacks.run('setReaction', messageId, reaction); } msgStream.emit(message.rid, message); diff --git a/packages/rocketchat-sandstorm/client/setPath.js b/packages/rocketchat-sandstorm/client/setPath.js new file mode 100644 index 0000000000000000000000000000000000000000..004ab4cee8ea0d3d4496f5feab6b796279f498fa --- /dev/null +++ b/packages/rocketchat-sandstorm/client/setPath.js @@ -0,0 +1,12 @@ +function updateSandstormMetaData(msg) { + return window.parent.postMessage(msg, '*'); +} + +if (Meteor.settings.public.sandstorm) { + // Set the path of the parent frame when the grain's path changes. + // See https://docs.sandstorm.io/en/latest/developing/path/ + + FlowRouter.triggers.enter([({ path }) => { + updateSandstormMetaData({ setPath: path }); + }]); +} diff --git a/packages/rocketchat-sandstorm/package.js b/packages/rocketchat-sandstorm/package.js index a052ac15b38d74099d2a46753c033f4b99a271a1..bd68220fa2e1ae4f8b08708deb662010f0c3b889 100644 --- a/packages/rocketchat-sandstorm/package.js +++ b/packages/rocketchat-sandstorm/package.js @@ -6,8 +6,8 @@ Package.describe({ }); Package.onUse(function(api) { - api.use([ 'ecmascript', 'rocketchat:lib', 'jalik:ufs' ]); + api.use([ 'ecmascript', 'rocketchat:lib', 'jalik:ufs', 'kadira:flow-router']); api.addFiles([ 'server/lib.js', 'server/events.js', 'server/powerbox.js' ], 'server'); - api.addFiles([ 'client/powerboxListener.js' ], 'client'); + api.addFiles([ 'client/powerboxListener.js', 'client/setPath.js' ], 'client'); }); diff --git a/packages/rocketchat-slackbridge/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-slackbridge/.npm/package/npm-shrinkwrap.json index 20e2ee7276f401899ccaba32f839ea1121e6ec32..093df3799e757e8b95a0eb92dcc76948d15b92fa 100644 --- a/packages/rocketchat-slackbridge/.npm/package/npm-shrinkwrap.json +++ b/packages/rocketchat-slackbridge/.npm/package/npm-shrinkwrap.json @@ -91,8 +91,8 @@ "from": "cycle@>=1.0.0 <1.1.0" }, "dashdash": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "from": "dashdash@>=1.12.0 <2.0.0", "dependencies": { "assert-plus": { @@ -103,8 +103,8 @@ } }, "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "from": "debug@>=2.0.0 <3.0.0" }, "delayed-stream": { @@ -148,8 +148,8 @@ "from": "forever-agent@>=0.6.1 <0.7.0" }, "form-data": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.1.tgz", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz", "from": "form-data@>=2.1.1 <2.2.0" }, "generate-function": { @@ -270,24 +270,19 @@ "from": "lodash@>=3.10.1 <4.0.0" }, "mime-db": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz", - "from": "mime-db@>=1.24.0 <1.25.0" + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", + "from": "mime-db@>=1.25.0 <1.26.0" }, "mime-types": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz", + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", "from": "mime-types@>=2.1.7 <2.2.0" }, "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "from": "ms@0.7.1" - }, - "node-uuid": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", - "from": "node-uuid@>=1.4.7 <1.5.0" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "from": "ms@0.7.2" }, "oauth-sign": { "version": "0.8.2", @@ -309,11 +304,6 @@ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "from": "pinkie-promise@>=2.0.0 <3.0.0" }, - "pkginfo": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", - "from": "pkginfo@>=0.3.0 <0.4.0" - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -325,8 +315,8 @@ "from": "qs@>=6.3.0 <6.4.0" }, "request": { - "version": "2.76.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.76.0.tgz", + "version": "2.79.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", "from": "request@>=2.64.0 <3.0.0" }, "retry": { @@ -340,9 +330,9 @@ "from": "semver@>=5.0.1 <5.1.0" }, "slack-client": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/slack-client/-/slack-client-2.0.4.tgz", - "from": "slack-client@2.0.4" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/slack-client/-/slack-client-2.0.6.tgz", + "from": "slack-client@2.0.6" }, "sntp": { "version": "1.0.9", @@ -392,8 +382,8 @@ "from": "tunnel-agent@>=0.4.1 <0.5.0" }, "tweetnacl": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz", + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.4.tgz", "from": "tweetnacl@>=0.14.0 <0.15.0" }, "ultron": { @@ -406,14 +396,19 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", "from": "url-join@0.0.1" }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "from": "uuid@>=3.0.0 <4.0.0" + }, "verror": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", "from": "verror@1.3.6" }, "winston": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.2.0.tgz", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.0.tgz", "from": "winston@>=2.1.1 <3.0.0", "dependencies": { "async": { diff --git a/packages/rocketchat-slackbridge/README.md b/packages/rocketchat-slackbridge/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5ce00d638c508b078a97e1bcc6b8aa4bf841918f 100644 --- a/packages/rocketchat-slackbridge/README.md +++ b/packages/rocketchat-slackbridge/README.md @@ -0,0 +1,40 @@ +## Rocket.Chat Slack Bridge + +This package creates a bi-directional bridge between a single Slack installation and your Rocket.Chat installation. + +* Configure Rocket.Chat with your Slack API token +* Invite 'rocketbot' to your Slack channel + +### Settings + +The following can be configured in your Rocket.Chat Administration SlackBridge panel. + +#### Enabled + +#### API Token + +#### Alias format + +#### Exclude bots + +#### SlackBridge Out Enabled + +#### SlackBridge Out All + +#### SlackBridge Out Channels + +### Features + +#### Group Chat Messages +* Send and receive basic messages +* Delete messages +* Edit messages (Slack doesn't allow editing of BOT messages, so can't edit a Rocket msg in Slack) +* React to messages (as BOT in Slack) + +#### Files + +#### Imports + +### TODO +* Support multiple slack installations +* Private IM's diff --git a/packages/rocketchat-slackbridge/package.js b/packages/rocketchat-slackbridge/package.js index 391129f9f38528aeb5f733d72ee0aef874801f0d..fc18d4da1b779a6ce2c48d59b27ae21f99838f25 100644 --- a/packages/rocketchat-slackbridge/package.js +++ b/packages/rocketchat-slackbridge/package.js @@ -20,5 +20,5 @@ Package.onUse(function(api) { }); Npm.depends({ - 'slack-client': '2.0.4' + 'slack-client': '2.0.6' }); diff --git a/packages/rocketchat-slackbridge/slackbridge.js b/packages/rocketchat-slackbridge/slackbridge.js index ebb041d2918e7172b39efc5f20a5d2a8fb305428..cae745e0407f8e3e9921546e60f6548425619154 100644 --- a/packages/rocketchat-slackbridge/slackbridge.js +++ b/packages/rocketchat-slackbridge/slackbridge.js @@ -1,6 +1,7 @@ /* globals logger */ class SlackBridge { + constructor() { this.util = Npm.require('util'); this.slackClient = Npm.require('slack-client'); @@ -10,23 +11,16 @@ class SlackBridge { this.rtm = {}; this.connected = false; this.userTags = {}; - this.channelMap = {}; + this.slackChannelMap = {}; + this.reactionsMap = new Map(); RocketChat.settings.get('SlackBridge_APIToken', (key, value) => { - this.apiToken = value; - if (this.connected) { - this.disconnect(); - this.connect(); - } else if (RocketChat.settings.get('SlackBridge_Enabled')) { - this.connect(); - } - }); - - RocketChat.settings.get('SlackBridge_Enabled', (key, value) => { - if (value && this.apiToken) { - this.connect(); - } else { - this.disconnect(); + if (value !== this.apiToken) { + this.apiToken = value; + if (this.connected) { + this.disconnect(); + this.connect(); + } } }); @@ -37,6 +31,14 @@ class SlackBridge { RocketChat.settings.get('SlackBridge_ExcludeBotnames', (key, value) => { this.excludeBotnames = value; }); + + RocketChat.settings.get('SlackBridge_Enabled', (key, value) => { + if (value && this.apiToken) { + this.connect(); + } else { + this.disconnect(); + } + }); } connect() { @@ -46,16 +48,21 @@ class SlackBridge { var RtmClient = this.slackClient.RtmClient; this.rtm = new RtmClient(this.apiToken); this.rtm.start(); - this.setEvents(); + this.registerForSlackEvents(); RocketChat.settings.get('SlackBridge_Out_Enabled', (key, value) => { if (value) { - RocketChat.callbacks.add('afterSaveMessage', this.slackBridgeOut.bind(this), RocketChat.callbacks.priority.LOW, 'SlackBridge_Out'); + this.registerForRocketEvents(); } else { - RocketChat.callbacks.remove('afterSaveMessage', 'SlackBridge_Out'); + this.unregisterForRocketEvents(); } }); Meteor.startup(() => { - this.populateChannelMap(); // If run outside of Meteor.startup, HTTP is not defined + try { + this.populateSlackChannelMap(); // If run outside of Meteor.startup, HTTP is not defined + } catch (err) { + logger.class.error('Error attempting to connect to Slack', err); + this.disconnect(); + } }); } } @@ -65,170 +72,174 @@ class SlackBridge { this.connected = false; this.rtm.disconnect && this.rtm.disconnect(); logger.connection.info('Disconnected'); - RocketChat.callbacks.remove('afterSaveMessage', 'SlackBridge_Out'); + this.unregisterForRocketEvents(); } } - convertSlackMessageToRocketChat(message) { - if (!_.isEmpty(message)) { - message = message.replace(/<!everyone>/g, '@all'); - message = message.replace(/<!channel>/g, '@all'); - message = message.replace(/>/g, '<'); - message = message.replace(/</g, '>'); - message = message.replace(/&/g, '&'); - message = message.replace(/:simple_smile:/g, ':smile:'); - message = message.replace(/:memo:/g, ':pencil:'); - message = message.replace(/:piggy:/g, ':pig:'); - message = message.replace(/:uk:/g, ':gb:'); - message = message.replace(/<(http[s]?:[^>]*)>/g, '$1'); - - message.replace(/(?:<@)([a-zA-Z0-9]+)(?:\|.+)?(?:>)/g, (match, userId) => { + convertSlackMsgTxtToRocketTxtFormat(slackMsgTxt) { + if (!_.isEmpty(slackMsgTxt)) { + slackMsgTxt = slackMsgTxt.replace(/<!everyone>/g, '@all'); + slackMsgTxt = slackMsgTxt.replace(/<!channel>/g, '@all'); + slackMsgTxt = slackMsgTxt.replace(/>/g, '<'); + slackMsgTxt = slackMsgTxt.replace(/</g, '>'); + slackMsgTxt = slackMsgTxt.replace(/&/g, '&'); + slackMsgTxt = slackMsgTxt.replace(/:simple_smile:/g, ':smile:'); + slackMsgTxt = slackMsgTxt.replace(/:memo:/g, ':pencil:'); + slackMsgTxt = slackMsgTxt.replace(/:piggy:/g, ':pig:'); + slackMsgTxt = slackMsgTxt.replace(/:uk:/g, ':gb:'); + slackMsgTxt = slackMsgTxt.replace(/<(http[s]?:[^>]*)>/g, '$1'); + + slackMsgTxt.replace(/(?:<@)([a-zA-Z0-9]+)(?:\|.+)?(?:>)/g, (match, userId) => { if (!this.userTags[userId]) { - this.findUser(userId) || this.addUser(userId); // This adds userTags for the userId + this.findRocketUser(userId) || this.addRocketUser(userId); // This adds userTags for the userId } let userTags = this.userTags[userId]; if (userTags) { - message = message.replace(userTags.slack, userTags.rocket); + slackMsgTxt = slackMsgTxt.replace(userTags.slack, userTags.rocket); } }); } else { - message = ''; + slackMsgTxt = ''; } - return message; + return slackMsgTxt; } - findChannel(channelId) { - logger.class.debug('Searching for Rocket.Chat channel', channelId); - return RocketChat.models.Rooms.findOneByImportId(channelId); + findRocketChannel(slackChannelId) { + return RocketChat.models.Rooms.findOneByImportId(slackChannelId); } - addChannel(channelId, hasRetried = false) { - logger.class.debug('Adding channel from Slack', channelId); - let data = null; + addRocketChannel(slackChannelID, hasRetried = false) { + logger.class.debug('Adding Rocket.Chat channel from Slack', slackChannelID); + let slackResults = null; let isGroup = false; - if (channelId.charAt(0) === 'C') { - data = HTTP.get('https://slack.com/api/channels.info', { params: { token: this.apiToken, channel: channelId } }); - } else if (channelId.charAt(0) === 'G') { - data = HTTP.get('https://slack.com/api/groups.info', { params: { token: this.apiToken, channel: channelId } }); + if (slackChannelID.charAt(0) === 'C') { + slackResults = HTTP.get('https://slack.com/api/channels.info', { params: { token: this.apiToken, channel: slackChannelID } }); + } else if (slackChannelID.charAt(0) === 'G') { + slackResults = HTTP.get('https://slack.com/api/groups.info', { params: { token: this.apiToken, channel: slackChannelID } }); isGroup = true; } - if (data && data.data && data.data.ok === true) { - let channelData = isGroup ? data.data.group : data.data.channel; - let existingRoom = RocketChat.models.Rooms.findOneByName(channelData.name); + if (slackResults && slackResults.data && slackResults.data.ok === true) { + let rocketChannelData = isGroup ? slackResults.data.group : slackResults.data.channel; + let existingRocketRoom = RocketChat.models.Rooms.findOneByName(rocketChannelData.name); // If the room exists, make sure we have its id in importIds - if (existingRoom || channelData.is_general) { - channelData.rocketId = channelData.is_general ? 'GENERAL' : existingRoom._id; - RocketChat.models.Rooms.addImportIds(channelData.rocketId, channelData.id); + if (existingRocketRoom || rocketChannelData.is_general) { + rocketChannelData.rocketId = rocketChannelData.is_general ? 'GENERAL' : existingRocketRoom._id; + RocketChat.models.Rooms.addImportIds(rocketChannelData.rocketId, rocketChannelData.id); } else { - let users = []; - for (let member of channelData.members) { - if (member !== channelData.creator) { - let user = this.findUser(member) || this.addUser(member); - if (user && user.username) { - users.push(user.username); + let rocketUsers = []; + for (let member of rocketChannelData.members) { + if (member !== rocketChannelData.creator) { + let rocketUser = this.findRocketUser(member) || this.addRocketUser(member); + if (rocketUser && rocketUser.username) { + rocketUsers.push(rocketUser.username); } } } - let creator = channelData.creator ? this.findUser(channelData.creator) || this.addUser(channelData.creator) : null; - if (!creator) { - logger.class.error('Could not fetch room creator information', channelData.creator); + let rocketUserCreator = rocketChannelData.creator ? this.findRocketUser(rocketChannelData.creator) || this.addRocketUser(rocketChannelData.creator) : null; + if (!rocketUserCreator) { + logger.class.error('Could not fetch room creator information', rocketChannelData.creator); return; } try { - let channel = RocketChat.createRoom(isGroup ? 'p' : 'c', channelData.name, creator.username, users); - channelData.rocketId = channel.rid; + let rocketChannel = RocketChat.createRoom(isGroup ? 'p' : 'c', rocketChannelData.name, rocketUserCreator.username, rocketUsers); + rocketChannelData.rocketId = rocketChannel.rid; } catch (e) { if (!hasRetried) { logger.class.debug('Error adding channel from Slack. Will retry in 1s.', e.message); // If first time trying to create channel fails, could be because of multiple messages received at the same time. Try again once after 1s. Meteor._sleepForMs(1000); - return this.findChannel(channelId) || this.addChannel(channelId, true); + return this.findRocketChannel(slackChannelID) || this.addRocketChannel(slackChannelID, true); } else { console.log(e.message); } } let roomUpdate = { - ts: new Date(channelData.created * 1000) + ts: new Date(rocketChannelData.created * 1000) }; let lastSetTopic = 0; - if (!_.isEmpty(channelData.topic && channelData.topic.value)) { - roomUpdate.topic = channelData.topic.value; - lastSetTopic = channelData.topic.last_set; + if (!_.isEmpty(rocketChannelData.topic && rocketChannelData.topic.value)) { + roomUpdate.topic = rocketChannelData.topic.value; + lastSetTopic = rocketChannelData.topic.last_set; } - if (!_.isEmpty(channelData.purpose && channelData.purpose.value) && channelData.purpose.last_set > lastSetTopic) { - roomUpdate.topic = channelData.purpose.value; + if (!_.isEmpty(rocketChannelData.purpose && rocketChannelData.purpose.value) && rocketChannelData.purpose.last_set > lastSetTopic) { + roomUpdate.topic = rocketChannelData.purpose.value; } - RocketChat.models.Rooms.addImportIds(channelData.rocketId, channelData.id); - this.channelMap[channelData.rocketId] = { id: channelId, family: channelId.charAt(0) === 'C' ? 'channels' : 'groups' }; + RocketChat.models.Rooms.addImportIds(rocketChannelData.rocketId, rocketChannelData.id); + this.slackChannelMap[rocketChannelData.rocketId] = { id: slackChannelID, family: slackChannelID.charAt(0) === 'C' ? 'channels' : 'groups' }; } - return RocketChat.models.Rooms.findOneById(channelData.rocketId); + return RocketChat.models.Rooms.findOneById(rocketChannelData.rocketId); } - + logger.class.debug('Channel not added'); return; } - findUser(userId) { - logger.class.debug('Searching for Rocket.Chat user', userId); - let user = RocketChat.models.Users.findOneByImportId(userId); - if (user && !this.userTags[userId]) { - this.userTags[userId] = { slack: `<@${userId}>`, rocket: `@${user.username}` }; + findRocketUser(slackUserID) { + let rocketUser = RocketChat.models.Users.findOneByImportId(slackUserID); + if (rocketUser && !this.userTags[slackUserID]) { + this.userTags[slackUserID] = { slack: `<@${slackUserID}>`, rocket: `@${rocketUser.username}` }; } - return user; + return rocketUser; } - addUser(userId) { - logger.class.debug('Adding user from Slack', userId); - let data = HTTP.get('https://slack.com/api/users.info', { params: { token: this.apiToken, user: userId } }); - if (data && data.data && data.data.ok === true && data.data.user) { - let userData = data.data.user; - let isBot = userData.is_bot === true; - let email = userData.profile && userData.profile.email || ''; - let existingUser; + addRocketUser(slackUserID) { + logger.class.debug('Adding Rocket.Chat user from Slack', slackUserID); + let slackResults = HTTP.get('https://slack.com/api/users.info', { params: { token: this.apiToken, user: slackUserID } }); + if (slackResults && slackResults.data && slackResults.data.ok === true && slackResults.data.user) { + let rocketUserData = slackResults.data.user; + let isBot = rocketUserData.is_bot === true; + let email = rocketUserData.profile && rocketUserData.profile.email || ''; + let existingRocketUser; if (!isBot) { - existingUser = RocketChat.models.Users.findOneByEmailAddress(email) || RocketChat.models.Users.findOneByUsername(userData.name); + existingRocketUser = RocketChat.models.Users.findOneByEmailAddress(email) || RocketChat.models.Users.findOneByUsername(rocketUserData.name); } else { - existingUser = RocketChat.models.Users.findOneByUsername(userData.name); + existingRocketUser = RocketChat.models.Users.findOneByUsername(rocketUserData.name); } - if (existingUser) { - userData.rocketId = existingUser._id; - userData.name = existingUser.username; + if (existingRocketUser) { + rocketUserData.rocketId = existingRocketUser._id; + rocketUserData.name = existingRocketUser.username; } else { - let newUser = { password: Random.id() }; - if (isBot || !email) { - newUser.username = userData.name; - } else { + let newUser = { + password: Random.id(), + username: rocketUserData.name + }; + + if (!isBot && email) { newUser.email = email; } - userData.rocketId = Accounts.createUser(newUser); + + if (isBot) { + newUser.joinDefaultChannels = false; + } + + rocketUserData.rocketId = Accounts.createUser(newUser); let userUpdate = { - username: userData.name, - utcOffset: userData.tz_offset / 3600, // Slack's is -18000 which translates to Rocket.Chat's after dividing by 3600, + utcOffset: rocketUserData.tz_offset / 3600, // Slack's is -18000 which translates to Rocket.Chat's after dividing by 3600, roles: isBot ? [ 'bot' ] : [ 'user' ] }; - if (userData.profile && userData.profile.real_name) { - userUpdate['name'] = userData.profile.real_name; + if (rocketUserData.profile && rocketUserData.profile.real_name) { + userUpdate['name'] = rocketUserData.profile.real_name; } - if (userData.deleted) { + if (rocketUserData.deleted) { userUpdate['active'] = false; userUpdate['services.resume.loginTokens'] = []; } - RocketChat.models.Users.update({ _id: userData.rocketId }, { $set: userUpdate }); + RocketChat.models.Users.update({ _id: rocketUserData.rocketId }, { $set: userUpdate }); - let user = RocketChat.models.Users.findOneById(userData.rocketId); + let user = RocketChat.models.Users.findOneById(rocketUserData.rocketId); let url = null; - if (userData.profile) { - if (userData.profile.image_original) { - url = userData.profile.image_original; - } else if (userData.profile.image_512) { - url = userData.profile.image_512; + if (rocketUserData.profile) { + if (rocketUserData.profile.image_original) { + url = rocketUserData.profile.image_original; + } else if (rocketUserData.profile.image_512) { + url = rocketUserData.profile.image_512; } } if (url) { @@ -238,162 +249,224 @@ class SlackBridge { logger.class.debug('Error setting user avatar', error.message); } } - RocketChat.addUserToDefaultChannels(user, true); } - let importIds = [ userData.id ]; - if (isBot && userData.profile && userData.profile.bot_id) { - importIds.push(userData.profile.bot_id); + let importIds = [ rocketUserData.id ]; + if (isBot && rocketUserData.profile && rocketUserData.profile.bot_id) { + importIds.push(rocketUserData.profile.bot_id); } - RocketChat.models.Users.addImportIds(userData.rocketId, importIds); - if (!this.userTags[userId]) { - this.userTags[userId] = { slack: `<@${userId}>`, rocket: `@${userData.name}` }; + RocketChat.models.Users.addImportIds(rocketUserData.rocketId, importIds); + if (!this.userTags[slackUserID]) { + this.userTags[slackUserID] = { slack: `<@${slackUserID}>`, rocket: `@${rocketUserData.name}` }; } - logger.class.debug('User: ', userData.rocketId); - return RocketChat.models.Users.findOneById(userData.rocketId); + return RocketChat.models.Users.findOneById(rocketUserData.rocketId); } logger.class.debug('User not added'); return; } - addAlias(username, msgObj) { + addAliasToRocketMsg(rocketUserName, rocketMsgObj) { if (this.aliasFormat) { - var alias = this.util.format(this.aliasFormat, username); + var alias = this.util.format(this.aliasFormat, rocketUserName); - if (alias !== username) { - msgObj.alias = alias; + if (alias !== rocketUserName) { + rocketMsgObj.alias = alias; } } - return msgObj; + return rocketMsgObj; } - sendMessage(room, user, message, msgDataDefaults, importing) { - if (message.type === 'message') { - let msgObj = {}; - if (!_.isEmpty(message.subtype)) { - msgObj = this.processSubtypedMessage(room, user, message, importing); - if (!msgObj) { + createAndSaveRocketMessage(rocketChannel, rocketUser, slackMessage, rocketMsgDataDefaults, isImporting) { + if (slackMessage.type === 'message') { + let rocketMsgObj = {}; + if (!_.isEmpty(slackMessage.subtype)) { + rocketMsgObj = this.processSlackSubtypedMessage(rocketChannel, rocketUser, slackMessage, isImporting); + if (!rocketMsgObj) { return; } } else { - msgObj = { - msg: this.convertSlackMessageToRocketChat(message.text), - rid: room._id, + rocketMsgObj = { + msg: this.convertSlackMsgTxtToRocketTxtFormat(slackMessage.text), + rid: rocketChannel._id, u: { - _id: user._id, - username: user.username + _id: rocketUser._id, + username: rocketUser.username } }; - this.addAlias(user.username, msgObj); + this.addAliasToRocketMsg(rocketUser.username, rocketMsgObj); } - _.extend(msgObj, msgDataDefaults); - if (message.edited) { - msgObj.editedAt = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000); + _.extend(rocketMsgObj, rocketMsgDataDefaults); + if (slackMessage.edited) { + rocketMsgObj.editedAt = new Date(parseInt(slackMessage.edited.ts.split('.')[0]) * 1000); } - if (message.subtype === 'bot_message') { - user = RocketChat.models.Users.findOneById('rocket.cat', { fields: { username: 1 } }); + if (slackMessage.subtype === 'bot_message') { + rocketUser = RocketChat.models.Users.findOneById('rocket.cat', { fields: { username: 1 } }); } - if (message.pinned_to && message.pinned_to.indexOf(message.channel) !== -1) { - msgObj.pinned = true; - msgObj.pinnedAt = Date.now; - msgObj.pinnedBy = _.pick(user, '_id', 'username'); + if (slackMessage.pinned_to && slackMessage.pinned_to.indexOf(slackMessage.channel) !== -1) { + rocketMsgObj.pinned = true; + rocketMsgObj.pinnedAt = Date.now; + rocketMsgObj.pinnedBy = _.pick(rocketUser, '_id', 'username'); } - if (message.subtype === 'bot_message') { + if (slackMessage.subtype === 'bot_message') { Meteor.setTimeout(() => { - if (message.bot_id && message.ts && !RocketChat.models.Messages.findOneBySlackBotIdAndSlackTs(message.bot_id, message.ts)) { - RocketChat.sendMessage(user, msgObj, room, true); + if (slackMessage.bot_id && slackMessage.ts && !RocketChat.models.Messages.findOneBySlackBotIdAndSlackTs(slackMessage.bot_id, slackMessage.ts)) { + RocketChat.sendMessage(rocketUser, rocketMsgObj, rocketChannel, true); } }, 500); } else { - RocketChat.sendMessage(user, msgObj, room, true); + logger.class.debug('Send message to Rocket.Chat'); + RocketChat.sendMessage(rocketUser, rocketMsgObj, rocketChannel, true); } } } - saveMessage(message, importing) { - let channel = message.channel ? this.findChannel(message.channel) || this.addChannel(message.channel) : null; - let user = null; - if (message.subtype === 'message_deleted' || message.subtype === 'message_changed') { - user = message.previous_message.user ? this.findUser(message.previous_message.user) || this.addUser(message.previous_message.user) : null; - } else if (message.subtype === 'bot_message') { - user = RocketChat.models.Users.findOneById('rocket.cat', { fields: { username: 1 } }); - } else { - user = message.user ? this.findUser(message.user) || this.addUser(message.user) : null; - } - if (channel && user) { - let msgDataDefaults = { - _id: `slack-${message.channel}-${message.ts.replace(/\./g, '-')}`, - ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) - }; - if (importing) { - msgDataDefaults['imported'] = 'slackbridge'; + /* + https://api.slack.com/events/reaction_removed + */ + onSlackReactionRemoved(slackReactionMsg) { + if (slackReactionMsg) { + let rocketUser = this.getRocketUser(slackReactionMsg.user); + //Lets find our Rocket originated message + let rocketMsg = RocketChat.models.Messages.findOneBySlackTs(slackReactionMsg.item.ts); + + if (!rocketMsg) { + //Must have originated from Slack + let rocketID = this.createRocketID(slackReactionMsg.item.channel, slackReactionMsg.item.ts); + rocketMsg = RocketChat.models.Messages.findOneById(rocketID); } - try { - this.sendMessage(channel, user, message, msgDataDefaults, importing); - } catch (e) { - // http://www.mongodb.org/about/contributors/error-codes/ - // 11000 == duplicate key error - if (e.name === 'MongoError' && e.code === 11000) { + + if (rocketMsg && rocketUser) { + let rocketReaction = ':' + slackReactionMsg.reaction + ':'; + + //If the Rocket user has already been removed, then this is an echo back from slack + if (rocketMsg.reactions) { + let theReaction = rocketMsg.reactions[rocketReaction]; + if (theReaction) { + if (theReaction.usernames.indexOf(rocketUser.username) === -1) { + return; //Reaction already removed + } + } + } else { + //Reaction already removed return; } - throw e; + //Stash this away to key off it later so we don't send it back to Slack + this.reactionsMap.set('unset'+rocketMsg._id+rocketReaction, rocketUser); + logger.class.debug('Removing reaction from Slack'); + Meteor.runAsUser(rocketUser._id, () => { + Meteor.call('setReaction', rocketReaction, rocketMsg._id); + }); } } } - processSubtypedMessage(room, user, message, importing) { - let msgObj = null; - switch (message.subtype) { + /* + https://api.slack.com/events/reaction_added + */ + onSlackReactionAdded(slackReactionMsg) { + if (slackReactionMsg) { + let rocketUser = this.getRocketUser(slackReactionMsg.user); + + if (rocketUser.roles.includes('bot')) { + return; + } + + //Lets find our Rocket originated message + let rocketMsg = RocketChat.models.Messages.findOneBySlackTs(slackReactionMsg.item.ts); + + if (!rocketMsg) { + //Must have originated from Slack + let rocketID = this.createRocketID(slackReactionMsg.item.channel, slackReactionMsg.item.ts); + rocketMsg = RocketChat.models.Messages.findOneById(rocketID); + } + + if (rocketMsg && rocketUser) { + let rocketReaction = ':' + slackReactionMsg.reaction + ':'; + + //If the Rocket user has already reacted, then this is Slack echoing back to us + if (rocketMsg.reactions) { + let theReaction = rocketMsg.reactions[rocketReaction]; + if (theReaction) { + if (theReaction.usernames.indexOf(rocketUser.username) !== -1) { + return; //Already reacted + } + } + } + + //Stash this away to key off it later so we don't send it back to Slack + this.reactionsMap.set('set'+rocketMsg._id+rocketReaction, rocketUser); + logger.class.debug('Adding reaction from Slack'); + Meteor.runAsUser(rocketUser._id, () => { + Meteor.call('setReaction', rocketReaction, rocketMsg._id); + }); + } + } + } + + /** + * We have received a message from slack and we need to save/delete/update it into rocket + * https://api.slack.com/events/message + */ + onSlackMessage(slackMessage, isImporting) { + if (slackMessage.subtype) { + switch (slackMessage.subtype) { + case 'message_deleted': + this.processSlackMessageDeleted(slackMessage); + break; + case 'message_changed': + this.processSlackMessageChanged(slackMessage); + break; + default: + //Keeping backwards compatability for now, refactor later + this.processSlackNewMessage(slackMessage, isImporting); + } + } else { + //Simple message + this.processSlackNewMessage(slackMessage, isImporting); + } + } + + processSlackSubtypedMessage(rocketChannel, rocketUser, slackMessage, isImporting) { + let rocketMsgObj = null; + switch (slackMessage.subtype) { case 'bot_message': - if (message.username !== undefined && this.excludeBotnames && message.username.match(this.excludeBotnames)) { + if (slackMessage.username !== undefined && this.excludeBotnames && slackMessage.username.match(this.excludeBotnames)) { return; } - msgObj = { - msg: this.convertSlackMessageToRocketChat(message.text), - rid: room._id, + rocketMsgObj = { + msg: this.convertSlackMsgTxtToRocketTxtFormat(slackMessage.text), + rid: rocketChannel._id, bot: true, - attachments: message.attachments, - username: message.username || message.bot_id + attachments: slackMessage.attachments, + username: slackMessage.username || slackMessage.bot_id }; - this.addAlias(message.username || message.bot_id, msgObj); - if (message.icons) { - msgObj.emoji = message.icons.emoji; + this.addAliasToRocketMsg(slackMessage.username || slackMessage.bot_id, rocketMsgObj); + if (slackMessage.icons) { + rocketMsgObj.emoji = slackMessage.icons.emoji; } - return msgObj; + return rocketMsgObj; case 'me_message': - return this.addAlias(user.username, { - msg: `_${this.convertSlackMessageToRocketChat(message.text)}_` + return this.addAliasToRocketMsg(rocketUser.username, { + msg: `_${this.convertSlackMsgTxtToRocketTxtFormat(slackMessage.text)}_` }); - case 'message_changed': - this.editMessage(room, user, message); - return; - case 'message_deleted': - if (message.previous_message) { - let _id = `slack-${message.channel}-${message.previous_message.ts.replace(/\./g, '-')}`; - msgObj = RocketChat.models.Messages.findOneById(_id); - if (msgObj) { - RocketChat.deleteMessage(msgObj, user); - } - } - return; case 'channel_join': - if (importing) { - RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(room._id, user, { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); + if (isImporting) { + RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(rocketChannel._id, rocketUser, { ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); } else { - RocketChat.addUserToRoom(room._id, user); + RocketChat.addUserToRoom(rocketChannel._id, rocketUser); } return; case 'group_join': - if (message.inviter) { - let inviter = message.inviter ? this.findUser(message.inviter) || this.addUser(message.inviter) : null; - if (importing) { - RocketChat.models.Messages.createUserAddedWithRoomIdAndUser(room._id, user, { - ts: new Date(parseInt(message.ts.split('.')[0]) * 1000), + if (slackMessage.inviter) { + let inviter = slackMessage.inviter ? this.findRocketUser(slackMessage.inviter) || this.addRocketUser(slackMessage.inviter) : null; + if (isImporting) { + RocketChat.models.Messages.createUserAddedWithRoomIdAndUser(rocketChannel._id, rocketUser, { + ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), u: { _id: inviter._id, username: inviter.username @@ -401,67 +474,67 @@ class SlackBridge { imported: 'slackbridge' }); } else { - RocketChat.addUserToRoom(room._id, user, inviter); + RocketChat.addUserToRoom(rocketChannel._id, rocketUser, inviter); } } return; case 'channel_leave': case 'group_leave': - if (importing) { - RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(room._id, user, { - ts: new Date(parseInt(message.ts.split('.')[0]) * 1000), + if (isImporting) { + RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(rocketChannel._id, rocketUser, { + ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); } else { - RocketChat.removeUserFromRoom(room._id, user); + RocketChat.removeUserFromRoom(rocketChannel._id, rocketUser); } return; case 'channel_topic': case 'group_topic': - if (importing) { - RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_topic', room._id, message.topic, user, { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); + if (isImporting) { + RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_topic', rocketChannel._id, slackMessage.topic, rocketUser, { ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); } else { - RocketChat.saveRoomTopic(room._id, message.topic, user, false); + RocketChat.saveRoomTopic(rocketChannel._id, slackMessage.topic, rocketUser, false); } return; case 'channel_purpose': case 'group_purpose': - if (importing) { - RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_topic', room._id, message.purpose, user, { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); + if (isImporting) { + RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_topic', rocketChannel._id, slackMessage.purpose, rocketUser, { ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); } else { - RocketChat.saveRoomTopic(room._id, message.purpose, user, false); + RocketChat.saveRoomTopic(rocketChannel._id, slackMessage.purpose, rocketUser, false); } return; case 'channel_name': case 'group_name': - if (importing) { - RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser(room._id, message.name, user, { ts: new Date(parseInt(message.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); + if (isImporting) { + RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser(rocketChannel._id, slackMessage.name, rocketUser, { ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), imported: 'slackbridge' }); } else { - RocketChat.saveRoomName(room._id, message.name, user, false); + RocketChat.saveRoomName(rocketChannel._id, slackMessage.name, rocketUser, false); } return; case 'channel_archive': case 'group_archive': - if (!importing) { - RocketChat.archiveRoom(room); + if (!isImporting) { + RocketChat.archiveRoom(rocketChannel); } return; case 'channel_unarchive': case 'group_unarchive': - if (!importing) { - RocketChat.unarchiveRoom(room); + if (!isImporting) { + RocketChat.unarchiveRoom(rocketChannel); } return; case 'file_share': - if (message.file && message.file.url_private_download !== undefined) { + if (slackMessage.file && slackMessage.file.url_private_download !== undefined) { let details = { - message_id: `slack-${message.ts.replace(/\./g, '-')}`, - name: message.file.name, - size: message.file.size, - type: message.file.mimetype, - rid: room._id + message_id: `slack-${slackMessage.ts.replace(/\./g, '-')}`, + name: slackMessage.file.name, + size: slackMessage.file.size, + type: slackMessage.file.mimetype, + rid: rocketChannel._id }; - return this.uploadFile(details, message.file.url_private_download, user, room, new Date(parseInt(message.ts.split('.')[0]) * 1000), importing); + return this.uploadFileFromSlack(details, slackMessage.file.url_private_download, rocketUser, rocketChannel, new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), isImporting); } break; case 'file_comment': @@ -471,28 +544,28 @@ class SlackBridge { logger.class.error('File mentioned not implemented'); return; case 'pinned_item': - if (message.attachments && message.attachments[0] && message.attachments[0].text) { - msgObj = { - rid: room._id, + if (slackMessage.attachments && slackMessage.attachments[0] && slackMessage.attachments[0].text) { + rocketMsgObj = { + rid: rocketChannel._id, t: 'message_pinned', msg: '', u: { - _id: user._id, - username: user.username + _id: rocketUser._id, + username: rocketUser.username }, attachments: [{ - 'text' : this.convertSlackMessageToRocketChat(message.attachments[0].text), - 'author_name' : message.attachments[0].author_subname, - 'author_icon' : getAvatarUrlFromUsername(message.attachments[0].author_subname), - 'ts' : new Date(parseInt(message.attachments[0].ts.split('.')[0]) * 1000) + 'text' : this.convertSlackMsgTxtToRocketTxtFormat(slackMessage.attachments[0].text), + 'author_name' : slackMessage.attachments[0].author_subname, + 'author_icon' : getAvatarUrlFromUsername(slackMessage.attachments[0].author_subname), + 'ts' : new Date(parseInt(slackMessage.attachments[0].ts.split('.')[0]) * 1000) }] }; - if (!importing) { - RocketChat.models.Messages.setPinnedByIdAndUserId(`slack-${message.attachments[0].channel_id}-${message.attachments[0].ts.replace(/\./g, '-')}`, msgObj.u, true, new Date(parseInt(message.ts.split('.')[0]) * 1000)); + if (!isImporting) { + RocketChat.models.Messages.setPinnedByIdAndUserId(`slack-${slackMessage.attachments[0].channel_id}-${slackMessage.attachments[0].ts.replace(/\./g, '-')}`, rocketMsgObj.u, true, new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000)); } - return msgObj; + return rocketMsgObj; } else { logger.class.error('Pinned item with no attachment'); } @@ -503,20 +576,6 @@ class SlackBridge { } } - /** - * Edits a message - **/ - editMessage(room, user, message) { - let msgObj = { - //@TODO _id - _id: `slack-${message.channel}-${message.message.ts.replace(/\./g, '-')}`, - rid: room._id, - msg: this.convertSlackMessageToRocketChat(message.message.text) - }; - - RocketChat.updateMessage(msgObj, user); - } - /** Uploads the file to the storage. @param [Object] details an object with details about the upload. name, size, type, and rid @@ -525,10 +584,11 @@ class SlackBridge { @param [Object] room the Rocket.Chat room @param [Date] timeStamp the timestamp the file was uploaded **/ - uploadFile(details, fileUrl, user, room, timeStamp, importing) { + //details, slackMessage.file.url_private_download, rocketUser, rocketChannel, new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000), isImporting); + uploadFileFromSlack(details, slackFileURL, rocketUser, rocketChannel, timeStamp, isImporting) { let url = Npm.require('url'); - let requestModule = /https/i.test(fileUrl) ? Npm.require('https') : Npm.require('http'); - var parsedUrl = url.parse(fileUrl, true); + let requestModule = /https/i.test(slackFileURL) ? Npm.require('https') : Npm.require('http'); + var parsedUrl = url.parse(slackFileURL, true); parsedUrl.headers = { 'Authorization': 'Bearer ' + this.apiToken }; requestModule.get(parsedUrl, Meteor.bindEnvironment((stream) => { let fileId = Meteor.fileStore.create(details); @@ -572,7 +632,7 @@ class SlackBridge { attachments: [attachment] }; - if (importing) { + if (isImporting) { msg.imported = 'slackbridge'; } @@ -580,17 +640,31 @@ class SlackBridge { msg['_id'] = details.message_id; } - return RocketChat.sendMessage(user, msg, room, true); + return RocketChat.sendMessage(rocketUser, msg, rocketChannel, true); } }); } })); } - setEvents() { + registerForRocketEvents() { + RocketChat.callbacks.add('afterSaveMessage', this.onRocketMessage.bind(this), RocketChat.callbacks.priority.LOW, 'SlackBridge_Out'); + RocketChat.callbacks.add('afterDeleteMessage', this.onRocketMessageDelete.bind(this), RocketChat.callbacks.priority.LOW, 'SlackBridge_Delete'); + RocketChat.callbacks.add('setReaction', this.onRocketSetReaction.bind(this), RocketChat.callbacks.priority.LOW, 'SlackBridge_SetReaction'); + RocketChat.callbacks.add('unsetReaction', this.onRocketUnSetReaction.bind(this), RocketChat.callbacks.priority.LOW, 'SlackBridge_UnSetReaction'); + } + + unregisterForRocketEvents() { + RocketChat.callbacks.remove('afterSaveMessage', 'SlackBridge_Out'); + RocketChat.callbacks.remove('afterDeleteMessage', 'SlackBridge_Delete'); + RocketChat.callbacks.remove('setReaction', 'SlackBridge_SetReaction'); + RocketChat.callbacks.remove('unsetReaction', 'SlackBridge_UnSetReaction'); + } + + registerForSlackEvents() { var CLIENT_EVENTS = this.slackClient.CLIENT_EVENTS; this.rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, () => { - logger.connection.info('Connected'); + logger.connection.info('Connected to Slack'); }); this.rtm.on(CLIENT_EVENTS.RTM.UNABLE_TO_RTM_START, () => { @@ -616,10 +690,24 @@ class SlackBridge { * inviter: [message_subtype = 'group_join|channel_join' -> user_id] * } **/ - this.rtm.on(RTM_EVENTS.MESSAGE, Meteor.bindEnvironment((message) => { - logger.events.debug('MESSAGE: ', message); - if (message) { - this.saveMessage(message); + this.rtm.on(RTM_EVENTS.MESSAGE, Meteor.bindEnvironment((slackMessage) => { + logger.events.debug('OnSlackEvent-MESSAGE: ', slackMessage); + if (slackMessage) { + this.onSlackMessage(slackMessage); + } + })); + + this.rtm.on(RTM_EVENTS.REACTION_ADDED, Meteor.bindEnvironment((reactionMsg) => { + logger.events.debug('OnSlackEvent-REACTION_ADDED: ', reactionMsg); + if (reactionMsg) { + this.onSlackReactionAdded(reactionMsg); + } + })); + + this.rtm.on(RTM_EVENTS.REACTION_REMOVED, Meteor.bindEnvironment((reactionMsg) => { + logger.events.debug('OnSlackEvent-REACTION_REMOVED: ', reactionMsg); + if (reactionMsg) { + this.onSlackReactionRemoved(reactionMsg); } })); @@ -809,12 +897,12 @@ class SlackBridge { this.rtm.on(RTM_EVENTS.TEAM_JOIN, Meteor.bindEnvironment(() => {})); } - findSlackChannel(name) { - logger.class.debug('Searching for Slack channel or group', name); + findSlackChannel(rocketChannelName) { + logger.class.debug('Searching for Slack channel or group', rocketChannelName); let response = HTTP.get('https://slack.com/api/channels.list', { params: { token: this.apiToken } }); if (response && response.data && _.isArray(response.data.channels) && response.data.channels.length > 0) { for (let channel of response.data.channels) { - if (channel.name === name && channel.is_member === true) { + if (channel.name === rocketChannelName && channel.is_member === true) { return channel; } } @@ -822,7 +910,7 @@ class SlackBridge { response = HTTP.get('https://slack.com/api/groups.list', { params: { token: this.apiToken } }); if (response && response.data && _.isArray(response.data.groups) && response.data.groups.length > 0) { for (let group of response.data.groups) { - if (group.name === name) { + if (group.name === rocketChannelName) { return group; } } @@ -840,20 +928,20 @@ class SlackBridge { latest = message.ts; } message.channel = options.channel; - this.saveMessage(message, true); + this.onSlackMessage(message, true); } return { has_more: response.data.has_more, ts: latest }; } } - copyChannelInfo(rid, channelMap) { + copySlackChannelInfo(rid, channelMap) { logger.class.debug('Copying users from Slack channel to Rocket.Chat', channelMap.id, rid); let response = HTTP.get('https://slack.com/api/' + channelMap.family + '.info', { params: { token: this.apiToken, channel: channelMap.id } }); if (response && response.data) { let data = channelMap.family === 'channels' ? response.data.channel : response.data.group; if (data && _.isArray(data.members) && data.members.length > 0) { for (let member of data.members) { - let user = this.findUser(member) || this.addUser(member); + let user = this.findRocketUser(member) || this.addRocketUser(member); if (user) { logger.class.debug('Adding user to room', user.username, rid); RocketChat.addUserToRoom(rid, user, null, true); @@ -883,7 +971,7 @@ class SlackBridge { } if (topic) { - let creator = this.findUser(topic_creator) || this.addUser(topic_creator); + let creator = this.findRocketUser(topic_creator) || this.addRocketUser(topic_creator); logger.class.debug('Setting room topic', rid, topic, creator.username); RocketChat.saveRoomTopic(rid, topic, creator, false); } @@ -895,7 +983,7 @@ class SlackBridge { if (response && response.data && _.isArray(response.data.items) && response.data.items.length > 0) { for (let pin of response.data.items) { if (pin.message) { - let user = this.findUser(pin.message.user); + let user = this.findRocketUser(pin.message.user); let msgObj = { rid: rid, t: 'message_pinned', @@ -905,7 +993,7 @@ class SlackBridge { username: user.username }, attachments: [{ - 'text' : this.convertSlackMessageToRocketChat(pin.message.text), + 'text' : this.convertSlackMsgTxtToRocketTxtFormat(pin.message.text), 'author_name' : user.username, 'author_icon' : getAvatarUrlFromUsername(user.username), 'ts' : new Date(parseInt(pin.message.ts.split('.')[0]) * 1000) @@ -922,23 +1010,23 @@ class SlackBridge { logger.class.info('importMessages: ', rid); let rocketchat_room = RocketChat.models.Rooms.findOneById(rid); if (rocketchat_room) { - if (this.channelMap[rid]) { - this.copyChannelInfo(rid, this.channelMap[rid]); + if (this.slackChannelMap[rid]) { + this.copySlackChannelInfo(rid, this.slackChannelMap[rid]); - logger.class.debug('Importing messages from Slack to Rocket.Chat', this.channelMap[rid], rid); - let results = this.importFromHistory(this.channelMap[rid].family, { channel: this.channelMap[rid].id, oldest: 1 }); + logger.class.debug('Importing messages from Slack to Rocket.Chat', this.slackChannelMap[rid], rid); + let results = this.importFromHistory(this.slackChannelMap[rid].family, { channel: this.slackChannelMap[rid].id, oldest: 1 }); while (results && results.has_more) { - results = this.importFromHistory(this.channelMap[rid].family, { channel: this.channelMap[rid].id, oldest: results.ts }); + results = this.importFromHistory(this.slackChannelMap[rid].family, { channel: this.slackChannelMap[rid].id, oldest: results.ts }); } - logger.class.debug('Pinning Slack channel messages to Rocket.Chat', this.channelMap[rid], rid); - this.copyPins(rid, this.channelMap[rid]); + logger.class.debug('Pinning Slack channel messages to Rocket.Chat', this.slackChannelMap[rid], rid); + this.copyPins(rid, this.slackChannelMap[rid]); return callback(); } else { let slack_room = this.findSlackChannel(rocketchat_room.name); if (slack_room) { - this.channelMap[rid] = { id: slack_room.id, family: slack_room.id.charAt(0) === 'C' ? 'channels' : 'groups' }; + this.slackChannelMap[rid] = { id: slack_room.id, family: slack_room.id.charAt(0) === 'C' ? 'channels' : 'groups' }; return this.importMessages(rid, callback); } else { logger.class.error('Could not find Slack room with specified name', rocketchat_room.name); @@ -951,63 +1039,327 @@ class SlackBridge { } } - populateChannelMap() { + populateSlackChannelMap() { logger.class.debug('Populating channel map'); let response = HTTP.get('https://slack.com/api/channels.list', { params: { token: this.apiToken } }); if (response && response.data && _.isArray(response.data.channels) && response.data.channels.length > 0) { - for (let channel of response.data.channels) { - let rocketchat_room = RocketChat.models.Rooms.findOneByName(channel.name, { fields: { _id: 1 } }); + for (let slackChannel of response.data.channels) { + let rocketchat_room = RocketChat.models.Rooms.findOneByName(slackChannel.name, { fields: { _id: 1 } }); if (rocketchat_room) { - this.channelMap[rocketchat_room._id] = { id: channel.id, family: channel.id.charAt(0) === 'C' ? 'channels' : 'groups' }; + this.slackChannelMap[rocketchat_room._id] = { id: slackChannel.id, family: slackChannel.id.charAt(0) === 'C' ? 'channels' : 'groups' }; } } } response = HTTP.get('https://slack.com/api/groups.list', { params: { token: this.apiToken } }); if (response && response.data && _.isArray(response.data.groups) && response.data.groups.length > 0) { - for (let group of response.data.groups) { - let rocketchat_room = RocketChat.models.Rooms.findOneByName(group.name, { fields: { _id: 1 } }); + for (let slackGroup of response.data.groups) { + let rocketchat_room = RocketChat.models.Rooms.findOneByName(slackGroup.name, { fields: { _id: 1 } }); if (rocketchat_room) { - this.channelMap[rocketchat_room._id] = { id: group.id, family: group.id.charAt(0) === 'C' ? 'channels' : 'groups' }; + this.slackChannelMap[rocketchat_room._id] = { id: slackGroup.id, family: slackGroup.id.charAt(0) === 'C' ? 'channels' : 'groups' }; } } } } - slackBridgeOut(message) { + onRocketMessageDelete(rocketMessageDeleted) { + logger.class.debug('onRocketMessageDelete', rocketMessageDeleted); + + this.postDeleteMessageToSlack(rocketMessageDeleted); + } + + onRocketSetReaction(rocketMsgID, reaction) { + logger.class.debug('onRocketSetReaction'); + + if (rocketMsgID && reaction) { + if (this.reactionsMap.delete('set'+rocketMsgID+reaction)) { + //This was a Slack reaction, we don't need to tell Slack about it + return; + } + let rocketMsg = RocketChat.models.Messages.findOneById(rocketMsgID); + if (rocketMsg) { + let slackChannel = this.slackChannelMap[rocketMsg.rid].id; + let slackTS = this.getSlackTS(rocketMsg); + this.postReactionAddedToSlack(reaction.replace(/:/g, ''), slackChannel, slackTS); + } + } + } + + onRocketUnSetReaction(rocketMsgID, reaction) { + logger.class.debug('onRocketUnSetReaction'); + + if (rocketMsgID && reaction) { + if (this.reactionsMap.delete('unset'+rocketMsgID+reaction)) { + //This was a Slack unset reaction, we don't need to tell Slack about it + return; + } + + let rocketMsg = RocketChat.models.Messages.findOneById(rocketMsgID); + if (rocketMsg) { + let slackChannel = this.slackChannelMap[rocketMsg.rid].id; + let slackTS = this.getSlackTS(rocketMsg); + this.postReactionRemoveToSlack(reaction.replace(/:/g, ''), slackChannel, slackTS); + } + } + } + + onRocketMessage(rocketMessage) { + logger.class.debug('onRocketMessage', rocketMessage); + + if (rocketMessage.editedAt) { + //This is an Edit Event + this.processRocketMessageChanged(rocketMessage); + return rocketMessage; + } // Ignore messages originating from Slack - if (message._id.indexOf('slack-') === 0) { - return message; + if (rocketMessage._id.indexOf('slack-') === 0) { + return rocketMessage; + } + + //Probably a new message from Rocket.Chat + let outSlackChannels = RocketChat.settings.get('SlackBridge_Out_All') ? _.keys(this.slackChannelMap) : _.pluck(RocketChat.settings.get('SlackBridge_Out_Channels'), '_id') || []; + //logger.class.debug('Out SlackChannels: ', outSlackChannels); + if (outSlackChannels.indexOf(rocketMessage.rid) !== -1) { + this.postMessageToSlack(this.slackChannelMap[rocketMessage.rid], rocketMessage); } - let outChannels = RocketChat.settings.get('SlackBridge_Out_All') ? _.keys(this.channelMap) : _.pluck(RocketChat.settings.get('SlackBridge_Out_Channels'), '_id') || []; - logger.class.debug('Out Channels: ', outChannels); - if (outChannels.indexOf(message.rid) !== -1) { - logger.class.debug('Message out', message); - this.postMessage(this.channelMap[message.rid], message); + return rocketMessage; + } + + /* + https://api.slack.com/methods/reactions.add + */ + postReactionAddedToSlack(reaction, slackChannel, slackTS) { + if (reaction && slackChannel && slackTS) { + let data = { + token: this.apiToken, + name: reaction, + channel: slackChannel, + timestamp: slackTS + }; + + logger.class.debug('Posting Add Reaction to Slack'); + const postResult = HTTP.post('https://slack.com/api/reactions.add', { params: data }); + if (postResult.statusCode === 200 && postResult.data && postResult.data.ok === true) { + logger.class.debug('Reaction added to Slack'); + } } - return message; } - postMessage(room, message) { - if (room && room.id) { - let iconUrl = getAvatarUrlFromUsername(message.u && message.u.username); + /* + https://api.slack.com/methods/reactions.remove + */ + postReactionRemoveToSlack(reaction, slackChannel, slackTS) { + if (reaction && slackChannel && slackTS) { + let data = { + token: this.apiToken, + name: reaction, + channel: slackChannel, + timestamp: slackTS + }; + + logger.class.debug('Posting Remove Reaction to Slack'); + const postResult = HTTP.post('https://slack.com/api/reactions.remove', { params: data }); + if (postResult.statusCode === 200 && postResult.data && postResult.data.ok === true) { + logger.class.debug('Reaction removed from Slack'); + } + } + } + + postDeleteMessageToSlack(rocketMessage) { + if (rocketMessage) { + let data = { + token: this.apiToken, + ts: this.getSlackTS(rocketMessage), + channel: this.slackChannelMap[rocketMessage.rid].id, + as_user: true + }; + + logger.class.debug('Post Delete Message to Slack', data); + const postResult = HTTP.post('https://slack.com/api/chat.delete', { params: data }); + if (postResult.statusCode === 200 && postResult.data && postResult.data.ok === true) { + logger.class.debug('Message deleted on Slack'); + } + } + } + + postMessageToSlack(slackChannel, rocketMessage) { + if (slackChannel && slackChannel.id) { + let iconUrl = getAvatarUrlFromUsername(rocketMessage.u && rocketMessage.u.username); if (iconUrl) { iconUrl = Meteor.absoluteUrl().replace(/\/$/, '') + iconUrl; } let data = { token: this.apiToken, - text: message.msg, - channel: room.id, - username: message.u && message.u.username, + text: rocketMessage.msg, + channel: slackChannel.id, + username: rocketMessage.u && rocketMessage.u.username, icon_url: iconUrl, link_names: 1 }; - logger.class.debug('Post Message', data); + logger.class.debug('Post Message To Slack', data); const postResult = HTTP.post('https://slack.com/api/chat.postMessage', { params: data }); if (postResult.statusCode === 200 && postResult.data && postResult.data.message && postResult.data.message.bot_id && postResult.data.message.ts) { - RocketChat.models.Messages.setSlackBotIdAndSlackTs(message._id, postResult.data.message.bot_id, postResult.data.message.ts); + RocketChat.models.Messages.setSlackBotIdAndSlackTs(rocketMessage._id, postResult.data.message.bot_id, postResult.data.message.ts); + logger.class.debug('RocketMsgID=' + rocketMessage._id + ' SlackMsgID=' + postResult.data.message.ts + ' SlackBotID=' + postResult.data.message.bot_id); + } + } + } + + /* + https://api.slack.com/methods/chat.update + */ + postMessageUpdateToSlack(slackChannel, rocketMessage) { + if (slackChannel && slackChannel.id) { + let data = { + token: this.apiToken, + ts: this.getSlackTS(rocketMessage), + channel: slackChannel.id, + text: rocketMessage.msg, + as_user: true + }; + logger.class.debug('Post UpdateMessage To Slack', data); + const postResult = HTTP.post('https://slack.com/api/chat.update', { params: data }); + if (postResult.statusCode === 200 && postResult.data && postResult.data.ok === true) { + logger.class.debug('Message updated on Slack'); + } + } + } + + processRocketMessageChanged(rocketMessage) { + if (rocketMessage) { + if (rocketMessage.updatedBySlack) { + //We have already processed this + delete rocketMessage.updatedBySlack; + return; } + + //This was a change from Rocket.Chat + let slackChannel = this.slackChannelMap[rocketMessage.rid]; + this.postMessageUpdateToSlack(slackChannel, rocketMessage); } } + + /* + https://api.slack.com/events/message/message_deleted + */ + processSlackMessageDeleted(slackMessage) { + if (slackMessage.previous_message) { + let rocketChannel = this.getRocketChannel(slackMessage); + let rocketUser = RocketChat.models.Users.findOneById('rocket.cat', { fields: { username: 1 } }); + + if (rocketChannel && rocketUser) { + //Find the Rocket message to delete + let rocketMsgObj = RocketChat.models.Messages + .findOneBySlackBotIdAndSlackTs(slackMessage.previous_message.bot_id, slackMessage.previous_message.ts); + + if (!rocketMsgObj) { + //Must have been a Slack originated msg + let _id = this.createRocketID(slackMessage.channel, slackMessage.previous_message.ts); + rocketMsgObj = RocketChat.models.Messages.findOneById(_id); + } + + if (rocketMsgObj) { + RocketChat.deleteMessage(rocketMsgObj, rocketUser); + logger.class.debug('Rocket message deleted by Slack'); + } + } + } + } + + /* + https://api.slack.com/events/message/message_changed + */ + processSlackMessageChanged(slackMessage) { + if (slackMessage.previous_message) { + let currentMsg = RocketChat.models.Messages.findOneById(this.createRocketID(slackMessage.channel, slackMessage.message.ts)); + + //Only process this change, if its an actual update (not just Slack repeating back our Rocket original change) + if (currentMsg && (slackMessage.message.text !== currentMsg.msg)) { + let rocketChannel = this.getRocketChannel(slackMessage); + let rocketUser = slackMessage.previous_message.user ? this.findRocketUser(slackMessage.previous_message.user) || this.addRocketUser(slackMessage.previous_message.user) : null; + + let rocketMsgObj = { + //@TODO _id + _id: this.createRocketID(slackMessage.channel, slackMessage.previous_message.ts), + rid: rocketChannel._id, + msg: this.convertSlackMsgTxtToRocketTxtFormat(slackMessage.message.text), + updatedBySlack: true //We don't want to notify slack about this change since Slack initiated it + }; + + RocketChat.updateMessage(rocketMsgObj, rocketUser); + logger.class.debug('Rocket message updated by Slack'); + } + } + } + + /* + This method will get refactored and broken down into single responsibilities + */ + processSlackNewMessage(slackMessage, isImporting) { + let rocketChannel = this.getRocketChannel(slackMessage); + let rocketUser = null; + if (slackMessage.subtype === 'bot_message') { + rocketUser = RocketChat.models.Users.findOneById('rocket.cat', { fields: { username: 1 } }); + } else { + rocketUser = slackMessage.user ? this.findRocketUser(slackMessage.user) || this.addRocketUser(slackMessage.user) : null; + } + if (rocketChannel && rocketUser) { + let msgDataDefaults = { + _id: this.createRocketID(slackMessage.channel, slackMessage.ts), + ts: new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000) + }; + if (isImporting) { + msgDataDefaults['imported'] = 'slackbridge'; + } + try { + this.createAndSaveRocketMessage(rocketChannel, rocketUser, slackMessage, msgDataDefaults, isImporting); + } catch (e) { + // http://www.mongodb.org/about/contributors/error-codes/ + // 11000 == duplicate key error + if (e.name === 'MongoError' && e.code === 11000) { + return; + } + + throw e; + } + } + } + + /** + * Retrieves the Slack TS from a Rocket msg that originated from Slack + * @param rocketMsg + * @returns Slack TS or undefined if not a message that originated from slack + * @private + */ + getSlackTS(rocketMsg) { + //slack-G3KJGGE15-1483081061-000169 + let slackTS; + let index = rocketMsg._id.indexOf('slack-'); + if (index === 0) { + //This is a msg that originated from Slack + slackTS = rocketMsg._id.substr(6, rocketMsg._id.length); + index = slackTS.indexOf('-'); + slackTS = slackTS.substr(index+1, slackTS.length); + slackTS = slackTS.replace('-', '.'); + } else { + //This probably originated as a Rocket msg, but has been sent to Slack + slackTS = rocketMsg.slackTs; + } + + return slackTS; + } + + getRocketChannel(slackMessage) { + return slackMessage.channel ? this.findRocketChannel(slackMessage.channel) || this.addRocketChannel(slackMessage.channel) : null; + } + + getRocketUser(slackUser) { + return slackUser ? this.findRocketUser(slackUser) || this.addRocketUser(slackUser) : null; + } + + createRocketID(slackChannel, ts) { + return `slack-${slackChannel}-${ts.replace(/\./g, '-')}`; + } + } RocketChat.SlackBridge = new SlackBridge; diff --git a/packages/rocketchat-slashcommands-invite/server.coffee b/packages/rocketchat-slashcommands-invite/server.coffee index f9adc943e1d288e0908ef644237786f16904f3e2..138e3da0047c44fe28a7d1ac731c25c3621fde97 100644 --- a/packages/rocketchat-slashcommands-invite/server.coffee +++ b/packages/rocketchat-slashcommands-invite/server.coffee @@ -25,7 +25,7 @@ class Invite return usernames = usernames.filter((username) -> - if not RocketChat.models.Rooms.findOneByIdContainigUsername(item.rid, username)? + if not RocketChat.models.Rooms.findOneByIdContainingUsername(item.rid, username)? return true # Cancel if user is already in this room diff --git a/packages/rocketchat-slashcommands-join/server.coffee b/packages/rocketchat-slashcommands-join/server.coffee index e196447f02481b8c00710585b9cba1bd9dea073b..bf66a39d62d892a7b302cf87bfd40ae00a35a66f 100644 --- a/packages/rocketchat-slashcommands-join/server.coffee +++ b/packages/rocketchat-slashcommands-join/server.coffee @@ -15,7 +15,7 @@ class Join channel = channel.replace('#', '') user = Meteor.users.findOne Meteor.userId() - room = RocketChat.models.Rooms.findOneByNameAndTypeNotContainigUsername(channel, 'c', user.username) + room = RocketChat.models.Rooms.findOneByNameAndTypeNotContainingUsername(channel, 'c', user.username) if not room? RocketChat.Notifications.notifyUser Meteor.userId(), 'message', { diff --git a/packages/rocketchat-slashcommands-open/client.js b/packages/rocketchat-slashcommands-open/client.js index 51376c2d95cc301b21c41d119118f44592934af8..10d74e9b2f0e16ba9ba8329cf0c7be77f5a20a41 100644 --- a/packages/rocketchat-slashcommands-open/client.js +++ b/packages/rocketchat-slashcommands-open/client.js @@ -22,7 +22,7 @@ function Open(command, params/*, item*/) { subscription = ChatSubscription.findOne(query); if (subscription) { - FlowRouter.go(RocketChat.roomTypes.getRouteLink(subscription.t, subscription), null, FlowRouter.current().queryParams); + RocketChat.roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); } } diff --git a/packages/rocketchat-spotify/lib/client/oembedSpotifyWidget.html b/packages/rocketchat-spotify/lib/client/oembedSpotifyWidget.html index afc14afc6f169a53ba6b193b7016051c3a2bea95..7ea5a42a82732cb539d56ef653827bd03af1badd 100644 --- a/packages/rocketchat-spotify/lib/client/oembedSpotifyWidget.html +++ b/packages/rocketchat-spotify/lib/client/oembedSpotifyWidget.html @@ -1,6 +1,6 @@ <template name="oembedSpotifyWidget"> {{#if parsedUrl}} - <blockquote> + <blockquote class="background-transparent-darker-before"> <a href="https://www.spotify.com" style="color: #9e9ea6">Spotify</a><br/> {{#if match meta.ogAudio "spotify:artist:\\S+"}} <a href="{{url}}">{{{meta.ogTitle}}}</a> diff --git a/packages/rocketchat-theme/assets/stylesheets/animation.css b/packages/rocketchat-theme/assets/stylesheets/animation.css deleted file mode 100644 index 027632fde28acdceaa5208fefa57ee52fb873d84..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/assets/stylesheets/animation.css +++ /dev/null @@ -1,94 +0,0 @@ -/* - Animation example, for spinners -*/ - -.animate-spin { - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; - display: inline-block; -} - -.animate-pulse { - -moz-animation: spin 1s infinite steps(8); - -o-animation: spin 1s infinite steps(8); - -webkit-animation: spin 1s infinite steps(8); - animation: spin 1s infinite steps(8); - display: inline-block; -} - -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} - -@-webkit-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} - -@-o-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} - -@-ms-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} - -@keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_chatops.less b/packages/rocketchat-theme/assets/stylesheets/utils/_chatops.less deleted file mode 100644 index dadbe4efea946d4d1902b049f5006446c1460c3b..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_chatops.less +++ /dev/null @@ -1,19 +0,0 @@ -li.chatops-message { - background-color: #f8f8f8; -} - -.chatops-message div.body { - font-size: 12px; - font-weight: bold; - color: #7a7a7a; -} - -li.chatops-message.sequential { - margin-top: -5px !important; -} - -.github-tagline { - padding-top: 3px; - font-weight: 200; - color: white; -} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less b/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less deleted file mode 100755 index ab142cdcd0cc677e0c925466a48e522278c53937..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_colors.import.less +++ /dev/null @@ -1,785 +0,0 @@ -/** ---------------------------------------------------------------------------- - * Derivative colours (fixed variants of inherited variables) - */ - -@default-action-color: darken(@secondary-background-color, 15%); -@default-action-contrast: contrast(@default-action-color, #444); -@primary-background-contrast: contrast(@primary-background-color, #444); -@primary-action-contrast: contrast(@primary-action-color, #444); -@secondary-action-contrast: contrast(@secondary-action-color, #444); -@selection-background: lighten(@selection-color, 30%); -@success-background: lighten(@success-color, 45%); -@success-border: lighten(@success-color, 30%); -@error-background: lighten(@error-color, 45%); -@error-border: lighten(@error-color, 30%); -@error-contrast: contrast(@error-color); -@pending-background: lighten(@pending-color, 45%); -@pending-border: lighten(@pending-color, 30%); -@window-border-color: @component-color; - -/** ---------------------------------------------------------------------------- - * Document components - */ - -html { - .custom-scroll(transparent, @custom-scrollbar-color, 0); -} - -.flex-nav, -.flex-tab .content, -.messages-container .wrapper, -.list-view, -.page-container .content, -.rooms-list, -.scrollable, -.user-view { - .custom-scroll(transparent, @custom-scrollbar-color); -} - -body { - color: @primary-font-color; - background-color: @content-background-color; - * { - border-color: @component-color; - } -} - -code { - background-color: @transparent-dark; - border-color: @component-color; - color: @secondary-font-color; -} - -blockquote:before { - background-color: @transparent-darker; -} - -.main-content, -.cms-page, -.page-container { - background-color: @content-background-color; - header { - background-color: @content-background-color; - border-color: @component-color; - } -} - -.page-container table { - overflow: hidden; - background-color: @secondary-background-color; - th, - td { - border-color: @component-color; - } - th { - background-color: @content-background-color; - } - tr { - background-color: @transparent-light; - &:nth-of-type(even) { - background-color: @transparent-lighter; - } - } - tr:hover td { - background-color: @content-background-color; - } -} - -.burger i { - background-color: @primary-font-color; -} - - -/** ---------------------------------------------------------------------------- - * Admin and settings styles - */ - -.page-settings { - .content { - background-color: @transparent-dark; - } - .section { - background-color: @content-background-color; - } - .terminal { - background-color: #444; - color: white; - .time { - color: @secondary-font-color; - } - } -} - -.page-list, -.page-settings { - a { - color: @primary-font-color; - &:hover { - color: @primary-action-color; - } - } -} - -.settings-file-preview .preview.no-file { - background-color: @transparent-light; - color: @secondary-font-color; -} - -.new-logs { - background: @primary-action-contrast; - color: @primary-action-color; -} - -.code-error-box { - .title { - background-color: @error-color; - color: white; - } - pre { - color: @error-color; - border-color: @error-border; - background-color: @transparent-lighter; - } -} - -.code-mirror-box { - .buttons { - background-color: #f7f7f7; // matches plugin styles - } - &.code-mirror-box-fullscreen { - background-color: @content-background-color; - } -} - -.avatar-suggestion-item { - background-color: @tertiary-background-color; - .avatar { - background-size: cover; - background-color: @tertiary-background-color; - } - .question-mark::before { - color: @secondary-font-color; - } -} - - -/** ---------------------------------------------------------------------------- - * Asides (external to main application views) - */ - -.spotlight { - background-color: @transparent-darker; - > .spotlight-input { - background-color: @secondary-background-color; - color: @secondary-font-color; - > i { - color: @secondary-font-color; - } - } -} - -.mobile-message-menu { - background-color: @transparent-dark; -} - -.full-page, -.page-loading { - .gradient(shade(@primary-background-color), @primary-background-color); - color: @tertiary-font-color; - a { - color: @tertiary-font-color; - } - a:hover { - color: @primary-background-contrast; - } - .spinner > div { - background-color: @primary-background-contrast; - } -} - -#login-card { - color: @primary-font-color; - background-color: @content-background-color; - .input-shade(@primary-font-color, @content-background-color); -} - -.dropzone.over .dropzone-overlay { - background-color: @transparent-darker; - color: @content-background-color; - > div { - background-color: @transparent-darker; - } -} - - -/** ---------------------------------------------------------------------------- - * Room components - */ - -.new-message { - color: @primary-action-contrast; - background-color: @primary-action-color; -} - -.ticks-bar { - .tick { - background-color: @primary-action-color; - } - .tick-all { - background-color: @selection-color; - } -} - -.room-not-found { - color: @error-color; -} - -.favorite-room { - color: @pending-color; -} - -.toggle-favorite { - color: @component-color; -} - -.container-bars { - .upload-progress { - background-color: @success-background; - color: @success-color; - &.upload-error { - background-color: @error-background; - border-color: @error-border; - } - .upload-progress-progress { - background-color: @success-background; - } - } - .unread-bar { - background-color: @component-color; - color: @primary-action-color; - } -} - -.messages-container { - .edit-room-title { - .linkColors(@secondary-font-color, @primary-font-color); - } - .footer { - background: @content-background-color; - } - .message-form { - color: @secondary-font-color; - .input-message-container { - background-color: @content-background-color; - } - .message-buttons { - .buttonColors(@primary-font-color, @transparent-dark); - } - } - .new-message { - color: @primary-action-contrast; - background-color: @primary-action-color; - } - .messages-box { - .jump-recent { - background-color: @component-color; - } - &.selectable .selected { - background-color: @selection-background; - } - .load-more span { - background-color: @secondary-background-color; - } - } -} - -.message-popup { - background: @content-background-color; -} - -.message-popup-title { - background-color: @secondary-background-color; - // border-bottom-color: #E7E7E7; -} - -.popup-item { - // color: @secondary-font-color; - &.selected { - color: @primary-action-contrast; - background-color: @primary-action-color; - } - span { - color: @secondary-font-color; - } -} - - -.messages-box { - .start { - color: @info-font-color; - } - .editing .body, - textarea.editing { - background-color: @selection-background; - } -} - - -/** ---------------------------------------------------------------------------- - * Message content - */ - -.message { - .body { - color: @primary-font-color; - } - &.system .body { - color: @info-font-color; - } - &:hover { - background-color: @transparent-dark; - } - .toggle-options { - color: @secondary-font-color; - } - .message-dropdown, - .options-menu { - color: @secondary-font-color; - background-color: @content-background-color; - ul li:hover { - color: @secondary-action-contrast; - background-color: @secondary-action-color; - } - .message-dropdown-close { - color: @content-background-color; - background-color: @info-font-color; - } - } - &.new-day::before { - background-color: @content-background-color; - } - &.new-day::after, - .info, - .user-view, - .message-alias { - color: @info-font-color; - border-color: @component-color; - } - .user { - color: @primary-font-color; - } - .is-bot, - .role-tag { - background-color: @info-font-color; - color: contrast(@info-font-color); - } - a { - .linkColors(@link-font-color, darken(@link-font-color, 10%)); - } - a.mention-link { - background-color: @secondary-action-color; - color: @secondary-action-contrast; - &.mention-link-all, - &.mention-link-me { - color: @primary-action-contrast; - background-color: @primary-action-color; - } - &.mention-link-all { - color: contrast(@selection-color); - background-color: @selection-color; - } - } - .highlight-text { - background-color: @selection-background; - } - .attachment { - .attachment-block-border { - background-color: @info-font-color; - } - } -} - - -/** ---------------------------------------------------------------------------- - * Misc typography variants - */ - -a { - color: @primary-font-color; -} - -a:active, -a:hover { - color: @primary-action-color; -} - -.message, -.flex-tab { - a i, - a[class^="icon-"] { - color: @primary-font-color; - &:hover { - color: darken(@primary-font-color, 10%); - } - } -} - -.load-more .load-more-loading, -.secondary-text { - color: @secondary-font-color; -} - -.alert-icon { - color: @success-color; -} - -.error { - background-color: @error-background; - border-color: @error-color; - color: @error-color; -} - - -/** ---------------------------------------------------------------------------- - * Side nav - */ - -.side-nav { - color: @tertiary-font-color; - background-color: @primary-background-color; - * { - border-color: @transparent-darker; - } - a, - .info { - .linkColors(@tertiary-font-color, @tertiary-font-color); - } - .arrow:before, - .arrow:after { - background-color: @tertiary-font-color; - } - .info, - .options, - .content, - footer, - header { - background-color: @primary-background-color; - } - .rooms-list { - background-color: lighten(@primary-background-color, 2.5%); - } - .more:hover, - h3:hover, - .selected-users li { - background-color: @transparent-darker; - } - li.active .opt { - color: @tertiary-font-color; - } - .active a { - color: @primary-background-contrast; - background-color: @transparent-lighter; - } - .open-room:hover { - background-color: @transparent-lighter; - } - .has-alert a { - color: @primary-background-contrast; - } - .unread { - background-color: @success-color; - } - .unread-rooms { - background-color: @primary-action-color; - color: @primary-action-contrast; - } -} - - -/** ---------------------------------------------------------------------------- - * Flex tabs / admin fly-out panels - */ - -.flex-tab { - background-color: @secondary-background-color; - .content, .user-view, .list-view { - background-color: @secondary-background-color; - } - .message { - background-color: @transparent-lighter; - &.new-day::before { - background-color: @secondary-background-color; - } - } -} - -.flex-tab-bar { - background-color: @content-background-color; - border-color: @component-color; - .tab-button { - &:hover { - background-color: @secondary-background-color; - } - &.active { - background-color: @secondary-background-color; - border-right-color: @selection-color; - } - &.attention { - .blink(@selection-color); - } - } - .counter { - background: @secondary-font-color; - color: white; - } -} - -.webrtc-video { - &.webrtc-video-overlay, - .main-video, - .state-overlay::before, - .videos .video-item { - background-color: @transparent-darker; - color: white; - } - .main-video > div, - .video-muted-overlay, - .videos .video-item > div { - background-color: @transparent-darker; - } - video { - background-color: black; - } -} - - -/** ---------------------------------------------------------------------------- - * User status / user meta - */ - -i.status-online { - color: @status-online; -} - -.account-box .status-online .thumb:after, -.account-box .status.online:after, -.options .status .online:after, -.popup-user-status-online, -.status-online:after, -.user-image.status-online .avatar:after { - background-color: @status-online; - border-color: darken(@status-online, 10%); -} - -i.status-away { - color: @status-away; -} - -.account-box .status-away .thumb:after, -.account-box .status.away:after, -.options .status .online:after, -.popup-user-status-away, -.status-pending:after, -.user-image.status-away .avatar:after { - background-color: @status-away; - border-color: darken(@status-away, 10%); -} - -i.status-busy { - color: @status-busy; -} - -.account-box .status-busy .thumb:after, -.account-box .status.busy:after, -.options .status .online:after, -.popup-user-status-busy, -.status-busy:after, -.user-image.status-busy .avatar:after { - background-color: @status-busy; - border-color: darken(@status-busy, 10%); -} - -i.status-offline { - color: @status-offline; -} - -.account-box .status-offline .thumb:after, -.account-box .status.offline:after, -.options .status .online:after, -.popup-user-status-offline, -.status-offline:after, -.user-image.status-offline .avatar:after { - background-color: @status-offline; - border-color: darken(@status-offline, 10%); -} - -.popup-user-status { - border-color: @transparent-dark; -} - -.popup-user-status-system { - border-color: transparent; -} - -.user-view { - p { - color: @secondary-font-color; - } - .box:after, - .stats li, - .tags li { - background-color: @component-color; - } -} - - -/** ---------------------------------------------------------------------------- - * Buttons! - */ - -.actionLinks li { - .buttonColors(@secondary-action-contrast, @secondary-action-color); -} - -// new layout buttons -.button { - .buttonColors(@default-action-contrast, @default-action-color); - &.primary { - .buttonColors(@primary-action-contrast, @primary-action-color); - } - &.secondary { - .buttonColors(@secondary-action-contrast, @secondary-action-color); - } - &.danger { - .buttonColors(@error-contrast, @error-color); - } - &.external-login { - color: white; - &.facebook { - background-color: #325c99; - } - &.twitter { - background-color: #02acec; - } - &.google { - background-color: #dd4b39; - } - &.github { - background-color: #4c4c4c; - } - &.gitlab { - background-color: #373d47; - } - &.trello { - background-color: #026aa7; - } - &.meteor-developer { - background-color: #de4f4f; - } - &.wordpress { - background-color: #1e8cbe; - } - &.linkedin { - background-color: #1b86bc; - } - } -} - -.side-nav { - .button { - .buttonColors(@tertiary-font-color, mix(@primary-action-color, @primary-background-color)); - } - .options button { - .buttonColors(@tertiary-font-color, @primary-background-color); - } -} - - -/** ---------------------------------------------------------------------------- - * Feedback and overlay content - */ - -.icon-ok, -.feedback-success { - color: @success-color; -} - -.feedback-warning { - color: @pending-color; -} - -.feedback-error { - color: @error-color; -} - -.livechat-form .error, -.offline .error { - background-color: @error-background; -} - -.alert-warning { - color: @pending-color; - background-color: @pending-background; - border-color: @pending-border; -} - -.alert-danger { - color: @error-color; - background-color: @error-background; - border-color: @error-border; -} - -.side-nav .input-error, -label.required:after { - color: @error-color; -} - -.burger .unread-burger-alert { - background-color: @error-color; - color: @error-contrast; -} - - -/** ---------------------------------------------------------------------------- - * Forms - */ - -.input-shade(@primary-font-color, @content-background-color); -.flex-nav { - .input-shade(@primary-background-contrast, @primary-background-color); - label, - legend { - color: @tertiary-font-color; - } -} - -.flex-tab { - input, - select, - textarea { - background-color: @content-background-color; - } -} - -.input-line { - &.setting-changed > label { - color: @selection-color; - } - i, - .settings-description { - color: @secondary-font-color; - } - .settings-alert { - background-color: @pending-background; - border-color: @pending-border; - color: @pending-color; - } -} - -input:-webkit-autofill { - color: @primary-font-color !important; -} - -input:-webkit-autofill { - background-color: transparent !important; -} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_lesshat.import.less b/packages/rocketchat-theme/assets/stylesheets/utils/_lesshat.import.less deleted file mode 100644 index 1ce1fc07fded5e103b603921e82f78964e9790f5..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_lesshat.import.less +++ /dev/null @@ -1,884 +0,0 @@ -// lesshat - The best mixin library in the world -// -// version: v4.1.0 (2016-07-19) - -// TABLE OF MIXINS: - // align-content - // align-items - // align-self - // animation - // animation-delay - // animation-direction - // animation-duration - // animation-fill-mode - // animation-iteration-count - // animation-name - // animation-play-state - // animation-timing-function - // appearance - // backface-visibility - // background-clip - // background-image - // background-origin - // background-size - // blur - // border-bottom-left-radius - // border-bottom-right-radius - // border-image - // border-radius - // border-top-left-radius - // border-top-right-radius - // box-shadow - // box-sizing - // brightness - // calc - // column-count - // column-gap - // column-rule - // column-width - // columns - // contrast - // display - // drop-shadow - // filter - // flex - // flex-basis - // flex-direction - // flex-grow - // flex-shrink - // flex-wrap - // font-face - // grayscale - // hue-rotate - // hyphens - // invert - // justify-content - // keyframes - // opacity - // order - // perspective - // perspective-origin - // placeholder - // rotate - // rotate3d - // rotateX - // rotateY - // rotateZ - // saturate - // scale - // scale3d - // scaleX - // scaleY - // scaleZ - // selection - // sepia - // size - // skew - // skewX - // skewY - // transform - // transform-origin - // transform-style - // transition - // transition-delay - // transition-duration - // transition-property - // transition-timing-function - // translate - // translate3d - // translateX - // translateY - // translateZ - // user-select - -.align-content(...) { - @process: ~`(function(t){return t=t||"stretch"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(e){return e=e||"stretch","flex-start"==e?e="start":"flex-end"==e?e="end":"space-between"==e?e="justify":"space-around"==e&&(e="distribute"),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-align-content: @process; - -ms-flex-line-pack: @process_ms; - align-content: @process; -} - -.align-items(...) { - @process_olderwebkit: ~`(function(t){return t=t||"stretch","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(t){return t=t||"stretch","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){return t=t||"stretch"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(t){return t=t||"stretch","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-align: @process_olderwebkit; - -moz-box-align: @process_moz; - -webkit-align-items: @process; - -ms-flex-align: @process_ms; - align-items: @process; -} - -.align-self(...) { - @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(t){return t=t||"auto","flex-start"==t?t="start":"flex-end"==t&&(t="end"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-align-self: @process; - -ms-flex-item-align: @process_ms; - align-self: @process; -} - -.animation(...) { - @process: ~`(function(e){return e=e||"none",/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation: @process; - -moz-animation: @process; - -o-animation: @process; - animation: @process; -} - -.animation-delay(...) { - @process: ~`(function(r){r=r||"0";var s=/(?:\d)(?:ms|s)/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-delay: @process; - -moz-animation-delay: @process; - -o-animation-delay: @process; - animation-delay: @process; -} - -.animation-direction(...) { - @process: ~`(function(n){return n||"normal"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-direction: @process; - -moz-animation-direction: @process; - -o-animation-direction: @process; - animation-direction: @process; -} - -.animation-duration(...) { - @process: ~`(function(r){r=r||"0";var s=/ms|s/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-duration: @process; - -moz-animation-duration: @process; - -o-animation-duration: @process; - animation-duration: @process; -} - -.animation-fill-mode(...) { - @process: ~`(function(n){return n||"none"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-fill-mode: @process; - -moz-animation-fill-mode: @process; - -o-animation-fill-mode: @process; - animation-fill-mode: @process; -} - -.animation-iteration-count(...) { - @process: ~`(function(n){return n||"0"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-iteration-count: @process; - -moz-animation-iteration-count: @process; - -o-animation-iteration-count: @process; - animation-iteration-count: @process; -} - -.animation-name(...) { - @process: ~`(function(n){return n||"none"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-name: @process; - -moz-animation-name: @process; - -o-animation-name: @process; - animation-name: @process; -} - -.animation-play-state(...) { - @process: ~`(function(n){return n||"running"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-play-state: @process; - -moz-animation-play-state: @process; - -o-animation-play-state: @process; - animation-play-state: @process; -} - -.animation-timing-function(...) { - @process: ~`(function(e){return e||"ease"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-animation-timing-function: @process; - -moz-animation-timing-function: @process; - -o-animation-timing-function: @process; - animation-timing-function: @process; -} - -.appearance(...) { - @process: ~`(function(n){return n||"none"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-appearance: @process; - -moz-appearance: @process; - appearance: @process; -} - -.backface-visibility(...) { - @process: ~`(function(i){return i||"visible"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-backface-visibility: @process; - -moz-backface-visibility: @process; - -ms-backface-visibility: @process; - -o-backface-visibility: @process; - backface-visibility: @process; -} - -.background-clip(...) { - @process: ~`(function(r){return r||"border-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-background-clip: @process; - -moz-background-clip: @process; - background-clip: @process; -} - -.background-image(...) { - @process_ms: ~`(function(t){function e(t){var e,r,s,a,n,i,o,c,g="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",d=0,l=0,f="",h=[];if(!t)return t;do e=t.charCodeAt(d++),r=t.charCodeAt(d++),s=t.charCodeAt(d++),c=e<<16|r<<8|s,a=c>>18&63,n=c>>12&63,i=c>>6&63,o=63&c,h[l++]=g.charAt(a)+g.charAt(n)+g.charAt(i)+g.charAt(o);while(d<t.length);f=h.join("");var u=t.length%3;return(u?f.slice(0,u-3):f)+"===".slice(u||3)}if(t=t||8121991,8121991==t)return t;var r=/linear|radial/g.test(t)&&t.split(/,(?=\s*(?:linear|radial|url))/g),s=[],a={"to bottom":'x1="0%" y1="0%" x2="0%" y2="100%"',"to left":'x1="100%" y1="0%" x2="0%" y2="0%"',"to top":'x1="0%" y1="100%" x2="0%" y2="0%"',"to right":'x1="0%" y1="0%" x2="100%" y2="0%"',get"top"(){return this["to bottom"]},get"180deg"(){return this["to bottom"]},get"right"(){return this["to left"]},get"270deg"(){return this["to left"]},get"bottom"(){return this["to top"]},get"90deg"(){return this["to right"]},get"0deg"(){return this["to top"]},get"left"(){return this["to right"]},"-45deg":'x1="0%" y1="0%" x2="100%" y2="100%"',"45deg":'x1="0%" y1="100%" x2="100%" y2="0%"',"ellipse at center":'cx="50%" cy="50%" r="75%"',get"135deg"(){return this["-45deg"]}},n={uri_data:"url(data:image/svg+xml;base64,",xml:'<?xml version="1.0" ?>',svg_start:'<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">',linear_gradient_start:'<linearGradient id="lesshat-generated" gradientUnits="userSpaceOnUse"',radial_gradient_start:'<radialGradient id="lesshat-generated" gradientUnits="userSpaceOnUse"',linear_gradient_end:"</linearGradient>",radial_gradient_end:"</radialGradient>",rect_linear:'<rect x="0" y="0" width="1" height="1" fill="url(#lesshat-generated)" />',rect_radial:'<rect x="-50" y="-50" width="101" height="101" fill="url(#lesshat-generated)" />',svg_end:"</svg>"};if(r.length){r.forEach(function(t,e){var r={};if(Object.keys(a).some(function(e){return t.indexOf(e)>=0?(r.svg_direction=a[e],!0):void(r.svg_direction=!1)}),/linear/.test(t))r.svg_type="linear";else if(/radial/.test(t))r.svg_type="radial";else if(!/linear/.test(t)&&!/radial/.test(t))return r.url=t.trim(),r.svg_type="url",r.svg_direction=!0,s.push(r),!1;var n=t.match(/rgb|#[a-zA-Z0-9]|hsl/g).length;r.svg_stops=[],t=t.replace(/transparent/g,"rgba(0,0,0,0)"),t.match(/#[a-zA-Z0-9]/g)&&t.match(/(#[a-zA-Z0-9]+)\s*(\d+%)?/g).forEach(function(t){t=t.split(" "),r.svg_stops.push('<stop offset="'+(t[1]||!1)+'" stop-color="'+t[0]+'" stop-opacity="1"/>')}),t.match(/rgba?\(\d+,\s*\d+,\s*\d+(?:,\s*(0|1|\.\d+|0\.\d+))?\)/g)&&t.replace(/rgba?\((\d+,\s*\d+,\s*\d+)(?:,\s*(0|1|\.\d+|0\.\d+))?\)\s*(\d+%)?/g,function(t,e,s,a){r.svg_stops.push('<stop offset="'+(a||!1)+'" stop-color="rgb('+e+')" stop-opacity="'+(s||1)+'"/>')}),t.match(/hsla?\((\d+,\s*\d+%,\s*\d+%),\s*(0|1|\.\d+|0\.\d+)\)/g)&&t.replace(/hsla?\((\d+,\s*\d+%,\s*\d+%),\s*(0|1|\.\d+|0\.\d+)\)\s*(\d+%)?/g,function(t,e,s,a){r.svg_stops.push('<stop offset="'+(a||!1)+'" stop-color="hsl('+e+')" stop-opacity="'+(s||1)+'"/>')});var i=Math.floor(100/(n-1));r.svg_stops.forEach(function(t,e){/offset="false"/.test(t)&&(r.svg_stops[e]=t.replace(/offset="false"/,'offset="'+i*e+'%"'))}),r.svg_stops.sort(function(t,e){if(t=t.match(/offset="(\d+)%"/),e=e.match(/offset="(\d+)%"/),2==t.length&&2==e.length)return t[1]-e[1]}),s.push(r)});var i=[],o=s.every(function(t){for(var e in t)if(0==t[e]||0==t[e].length)return!1;return!0});if(!o)return 8121991;s.forEach(function(t,e){"linear"!=t.svg_type&&"radial"!=t.svg_type||(i[e]=n.xml+n.svg_start),"linear"==t.svg_type?(i[e]+=n.linear_gradient_start+" "+t.svg_direction+">",t.svg_stops.forEach(function(t){i[e]+=t}),i[e]+=n.linear_gradient_end,i[e]+=n.rect_linear,i[e]+=n.svg_end):"radial"==t.svg_type?(i[e]+=n.radial_gradient_start+" "+t.svg_direction+">",t.svg_stops.forEach(function(t){i[e]+=t}),i[e]+=n.radial_gradient_end,i[e]+=n.rect_radial,i[e]+=n.svg_end):"url"==t.svg_type&&(i[e]=t.url)}),i.forEach(function(t,r){/<\?xml version="1.0" \?>/g.test(t)&&(i[r]=n.uri_data+e(t)+")")}),t=i.join(",")}return t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_webkit: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r={"to bottom":"top","to left":"right","to top":"bottom","to right":"left","ellipse at center":"center, ellipse cover","circle closest-side":"center center, circle contain","circle farthest-corner":"center center, circle cover","circle farthest-side":"center center, circle cover","ellipse closest-side":"center center, ellipse contain","ellipse farthest-corner":"center center, ellipse cover","ellipse farthest-side":"center center, ellipse cover"},t=/(radial-gradient\()([a-z- ]+)at\s+(\w+%?)\s*(\w*%?)/g,c=Object.keys(r);return c.some(function(c){return e.indexOf(c)>=0?(e=e.replace(new RegExp(c+"(?![ a-z0-9])","g"),r[c]),!0):void(t.test(e)&&(e=e.replace(t,function(e,r,t,c,i){return r.trim()+c.trim()+" "+i.trim()+","+t.replace(/closest-side/g,"contain").replace(/farthest-corner/g,"cover").trim()})))}),e=e.replace(/(\d+)\s*deg/g,function(e,r){return 90-r+"deg"}).replace(/(linear|radial)-gradient/g,"-webkit-$1-gradient")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r={"to bottom":"top","to left":"right","to top":"bottom","to right":"left","ellipse at center":"center, ellipse cover","circle closest-side":"center center, circle contain","circle farthest-corner":"center center, circle cover","circle farthest-side":"center center, circle cover","ellipse closest-side":"center center, ellipse contain","ellipse farthest-corner":"center center, ellipse cover","ellipse farthest-side":"center center, ellipse cover"},t=/(radial-gradient\()([a-z- ]+)at\s+(\w+%?)\s*(\w*%?)/g,c=Object.keys(r);return c.some(function(c){return e.indexOf(c)>=0?(e=e.replace(new RegExp(c+"(?![ a-z0-9])","g"),r[c]),!0):void(t.test(e)&&(e=e.replace(t,function(e,r,t,c,n){return r.trim()+c.trim()+" "+n.trim()+","+t.replace(/closest-side/g,"contain").replace(/farthest-corner/g,"cover").trim()})))}),e=e.replace(/(\d+)\s*deg/g,function(e,r){return 90-r+"deg"}).replace(/(linear|radial)-gradient/g,"-moz-$1-gradient")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_opera: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r={"to bottom":"top","to left":"right","to top":"bottom","to right":"left","ellipse at center":"center, ellipse cover","circle closest-side":"center center, circle contain","circle farthest-corner":"center center, circle cover","circle farthest-side":"center center, circle cover","ellipse closest-side":"center center, ellipse contain","ellipse farthest-corner":"center center, ellipse cover","ellipse farthest-side":"center center, ellipse cover"},t=/(radial-gradient\()([a-z- ]+)at\s+(\w+%?)\s*(\w*%?)/g,c=Object.keys(r);return c.some(function(c){return e.indexOf(c)>=0?(e=e.replace(new RegExp(c+"(?![ a-z0-9])","g"),r[c]),!0):void(t.test(e)&&(e=e.replace(t,function(e,r,t,c,n){return r.trim()+c.trim()+" "+n.trim()+","+t.replace(/closest-side/g,"contain").replace(/farthest-corner/g,"cover").trim()})))}),e=e.replace(/(\d+)\s*deg/g,function(e,r){return 90-r+"deg"}).replace(/(linear|radial)-gradient/g,"-o-$1-gradient")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){if(t=t||8121991,8121991==t)return t;var e={top:"to bottom",right:"to left",bottom:"to top",left:"to right"},o=Object.keys(e);return o.some(function(o){if(t.indexOf(o)>=0&&!new RegExp("to\\s+"+o+"|at\\s+"+o,"g").test(t))return t=t.replace(new RegExp(o),e[o]),!0}),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - background-image: @process_ms; - background-image: @process_webkit; - background-image: @process_moz; - background-image: @process_opera; - background-image: @process; -} - -.background-origin(...) { - @process: ~`(function(n){return n||"padding-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-background-origin: @process; - -moz-background-origin: @process; - background-origin: @process; -} - -.background-size(...) { - @process: ~`(function(t){t=t||"auto auto";var e=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(t)&&(t=t.replace(/(?:,)(?![^(]*\))/g,"")),e.test(t)&&(t=t.replace(r,function(t){return 0==t&&t||t+"px"})),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-background-size: @process; - -moz-background-size: @process; - background-size: @process; -} - -.blur(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: blur(@process); - -moz-filter: blur(@process); - -ms-filter: blur(@process); - filter: blur(@process); -} - -.border-bottom-left-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-bottom-left-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-bottomleft: @process; -moz-background-clip: padding; - border-bottom-left-radius: @process; background-clip: padding-box; -} - -.border-bottom-right-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-bottom-right-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-bottomright: @process; -moz-background-clip: padding; - border-bottom-right-radius: @process; background-clip: padding-box; -} - -.border-image(...) { - @process: ~`(function(e){return e=e||8121991,/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-image: @process; - -moz-border-image: @process; - -o-border-image: @process; - border-image: @process; -} - -.border-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius: @process; -moz-background-clip: padding; - border-radius: @process; background-clip: padding-box; -} - -.border-top-left-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-top-left-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-topleft: @process; -moz-background-clip: padding; - border-top-left-radius: @process; background-clip: padding-box; -} - -.border-top-right-radius(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-border-top-right-radius: @process; -webkit-background-clip: padding-box; - -moz-border-radius-topright: @process; -moz-background-clip: padding; - border-top-right-radius: @process; background-clip: padding-box; -} - -.box-shadow(...) { - @process: ~`(function(e){e=e||"0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-shadow: @process; - -moz-box-shadow: @process; - box-shadow: @process; -} - -.box-sizing(...) { - @process: ~`(function(n){return n=n||"content-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-sizing: @process; - -moz-box-sizing: @process; - box-sizing: @process; -} - -.brightness(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: brightness(@process); - -moz-filter: brightness(@process); - -ms-filter: brightness(@process); - filter: brightness(@process); -} - -.calc(...) { - @process: ~`(function(a){function c(c,t){var r=");\n",s=e.split(","),l=s[0]+":"+c+"("+(s[1].trim()||0)+r;"start"==t?a="0;\n"+l:a+=l}a=a||8121991;var t="@{state}",e=a;if(8121991==a)return a;switch(t){case"1":c("-webkit-calc","start"),c("-moz-calc"),c("calc");break;case"2":c("-webkit-calc","start"),c("-moz-calc");break;case"3":c("-webkit-calc","start"),c("calc");break;case"4":c("-webkit-calc","start");break;case"5":c("-moz-calc","start"),c("calc");break;case"6":c("-moz-calc","start");break;case"7":c("calc","start")}return a=a.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @state: 1; -lh-property: @process; - -} - -.column-count(...) { - @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-count: @process; - -moz-column-count: @process; - column-count: @process; -} - -.column-gap(...) { - @process: ~`(function(n){n=n||"normal";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-gap: @process; - -moz-column-gap: @process; - column-gap: @process; -} - -.column-rule(...) { - @process: ~`(function(e){e=e||"medium none black";var n=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),n.test(e)&&(e=e.replace(t,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-rule: @process; - -moz-column-rule: @process; - column-rule: @process; -} - -.column-width(...) { - @process: ~`(function(t){t=t||"auto";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(t)&&(t=t.replace(r,function(t){return 0==t&&t||t+"px"})),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-column-width: @process; - -moz-column-width: @process; - column-width: @process; -} - -.columns(...) { - @process: ~`(function(t){t=t||"auto auto";var e=/^\d+$/;return/^[^, ]*,/.test(t)&&(t=t.replace(/(?:,)(?![^(]*\))/g,""),t=t.split(" ")),e.test(t[0])&&(t[0]=t[0]+"px"),t.join(" ")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-columns: @process; - -moz-columns: @process; - columns: @process; -} - -.contrast(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: ~"contrast(@{process})"; - -moz-filter: ~"contrast(@{process})"; - -ms-filter: ~"contrast(@{process})"; - filter: ~"contrast(@{process})"; -} - -.display(...) { - @process_oldwebkit: ~`(function(e){return e="flex"==e||"inline-flex"==e?"-webkit-box":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(n){return n="flex"==n||"inline-flex"==n?"-moz-box":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_webkit: ~`(function(e){return e="flex"==e||"inline-flex"==e?"-webkit-"+e:8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(e){return e="flex"==e?"-ms-flexbox":"inline-flex"==e?"-ms-inline-flexbox":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){return"flex"!=n&&"inline-flex"!=n&&(n=8121991),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - display: @process_oldwebkit; - display: @process_moz; - display: @process_webkit; - display: @process_ms; - display: @process; -} - -.drop-shadow(...) { - @process: ~`(function(e){if(e=e||8121991,8121991==e)return e;var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),r.test(e)&&(e=e.replace(t,function(e){return 0==e&&e||e+"px"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: drop-shadow(@process); - -moz-filter: drop-shadow(@process); - -ms-filter: drop-shadow(@process); - filter: drop-shadow(@process); -} - -.filter(...) { - @process: ~`(function(e){return e=e||"none",/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: @process; - -moz-filter: @process; - -ms-filter: @process; - filter: @process; -} - -.flex(...) { - @process_olderwebkit: ~`(function(t){return/^\d+/.test(t)?t=t.match(/^\d+/)[0]:""==t&&(t="0"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(t){return/^\d+/.test(t)?t=t.match(/^\d+/)[0]:""==t&&(t="0"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){return t=t||"0 1 auto",/^[^, ]*,/.test(t)&&(t=t.replace(/(?:,)(?![^(]*\))/g,"")),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-flex: @process_olderwebkit; - -moz-box-flex: @process_moz; - -webkit-flex: @process; - -ms-flex: @process; - flex: @process; -} - -.flex-basis(...) { - @process: ~`(function(t){t=t||"auto";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(t)&&(t=t.replace(r,function(t){return 0==t&&t||t+"px"})),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-basis: @process; - flex-basis: @process; -} - -.flex-direction(...) { - @process_oldestwebkit: ~`(function(r){return r="row"==r||"column"==r?"normal":"row-reverse"==r||"column-reverse"==r?"reverse":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_oldermoz: ~`(function(r){return r="row"==r||"column"==r?"normal":"row-reverse"==r||"column-reverse"==r?"reverse":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_olderwebkit: ~`(function(r){return r="row"==r||"row-reverse"==r?"horizontal":"column"==r||"column-reverse"==r?"vertical":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(r){return r="row"==r||"row-reverse"==r?"horizontal":"column"==r||"column-reverse"==r?"vertical":8121991})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){return n=n||"row"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-direction: @process_oldestwebkit; - -moz-box-direction: @process_oldermoz; - -webkit-box-orient: @process_olderwebkit; - -moz-box-orient: @process_moz; - -webkit-flex-direction: @process; - -ms-flex-direction: @process; - flex-direction: @process; -} - -.flex-grow(...) { - @process: ~`(function(n){return n=n||"0"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-grow: @process; - flex-grow: @process; -} - -.flex-shrink(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-shrink: @process; - flex-shrink: @process; -} - -.flex-wrap(...) { - @process: ~`(function(n){return n=n||"nowrap"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-flex-wrap: @process; - -ms-flex-wrap: @process; - flex-wrap: @process; -} - -.font-face(@fontname, @fontfile, @fontweight:normal, @fontstyle:normal) { - font-family: "@{fontname}"; - src: url("@{fontfile}.eot"); - src: url("@{fontfile}.eot?#iefix") format("embedded-opentype"), - url("@{fontfile}.woff") format("woff"), - url("@{fontfile}.ttf") format("truetype"), - url("@{fontfile}.svg#@{fontname}") format("svg"); - font-weight: @fontweight; - font-style: @fontstyle; -} - -.grayscale(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: grayscale(@process); - -moz-filter: grayscale(@process); - -ms-filter: grayscale(@process); - filter: grayscale(@process); -} - -.hue-rotate(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: hue-rotate(@process); - -moz-filter: hue-rotate(@process); - -ms-filter: hue-rotate(@process); - filter: hue-rotate(@process); -} - -.hyphens(...) { - @process: ~`(function(n){return n=n||"manual"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-hyphens: @process; - -moz-hyphens: @process; - -ms-hyphens: @process; - hyphens: @process; -} - -.invert(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: invert(@process); - -moz-filter: invert(@process); - -ms-filter: invert(@process); - filter: invert(@process); -} - -.justify-content(...) { - @process_oldestWebkit: ~`(function(e){return e=e||"start","flex-start"==e?e="start":"flex-end"==e?e="end":"space-between"!=e&&"space-around"!=e||(e="justify"),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(e){return e=e||"start","flex-start"==e?e="start":"flex-end"==e?e="end":"space-between"!=e&&"space-around"!=e||(e="justify"),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_ms: ~`(function(t){return t=t||"start","flex-start"==t?t="start":"flex-end"==t?t="end":"space-between"==t?t="justify":"space-around"==t&&(t="distribute"),t})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(t){return t=t||"flex-start"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-pack: @process_oldestWebkit; - -moz-box-pack: @process_moz; - -ms-flex-pack: @process_ms; - -webkit-justify-content: @process; - justify-content: @process; -} - -.keyframes(...) { - @process: ~`(function(e){function a(a,r,k){var n="}\n",m=t.split(/(^[a-zA-Z0-9-]+),/g),o=r+" "+m[1]+"{",f=["-webkit-","-moz-","-ms-",""];k?s.forEach(function(a,r){e.indexOf(a)!==-1&&(m[2]=m[2].replace(new RegExp(a,"g"),function(e){return k+e}))}):m[2]=m[2].replace(/{([^}]+)}/g,function(e,a){var r=a.split(";");r.forEach(function(e,a){s.forEach(function(t){e.indexOf(t)!==-1&&(r[a]="",f.forEach(function(s){r[a]+=e.trim().replace(new RegExp(t,"g"),function(e){return s+e})+";"}))})});var t=r.join(";").replace(/;;/g,";");return e.replace(a,t)}),o+=m[2]+n,"start"==a?e="0; } \n"+o:"startend"==a?e="0; } \n"+o.replace(n,""):e+="end"==a?o.replace(n,""):o}e=e||8121991;var r="@{state}",t=e;if(8121991==e)return e;var s=["animation","transform","filter"];switch(r){case"1":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-moz-keyframes","-moz-"),a(null,"@-o-keyframes","-o-"),a("end","@keyframes");break;case"2":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-moz-keyframes","-moz-"),a("end","@keyframes");break;case"3":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-moz-keyframes","-moz-"),a("end","@-o-keyframes","-o-");break;case"4":a("start","@-webkit-keyframes","-webkit-"),a(null,"@-o-keyframes","-o-"),a("end","@keyframes");break;case"5":a("start","@-webkit-keyframes","-webkit-"),a("end","@-moz-keyframes","-moz-");break;case"6":a("start","@-webkit-keyframes","-webkit-"),a("end","@-o-keyframes","-o-");break;case"7":a("start","@-webkit-keyframes","-webkit-"),a("end","@keyframes");break;case"8":a("startend","@-webkit-keyframes","-webkit-");break;case"9":a("start","@-moz-keyframes","-moz-"),a(null,"@-o-keyframes","-o-"),a("end","@keyframes");break;case"10":a("start","@-moz-keyframes","-moz-"),a("end","@-o-keyframes","-o-");break;case"11":a("start","@-moz-keyframes","-moz-"),a("end","@keyframes");break;case"12":a("startend","@-moz-keyframes","-moz-");break;case"13":a("start","@-o-keyframes","-o-"),a("end","@keyframes");break;case"14":a("startend","@-o-keyframes","-o-");break;case"15":a("startend","@keyframes")}return e+"}\n[not-existing] {\n zoom: 1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @state: 1; lesshat-selector { -lh-property: @process; } - - - -} - -.opacity(...) { - @process_ms: ~`(function(a){return a=a||"filter: alpha(opacity=100)","alpha(opacity="+Math.floor(100*a)+")"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - zoom: 1; filter: @process_ms; - -webkit-opacity: @process; - -moz-opacity: @process; - opacity: @process; -} - -.order(...) { - @process: ~`(function(n){return n=n||"0"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-box-ordinal-group: @process; - -moz-box-ordinal-group: @process; - -ms-flex-order: @process; - -webkit-order: @process; - order: @process; -} - -.perspective(...) { - @process: ~`(function(n){n=n||"none";var e=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return e.test(n)&&(n=n.replace(r,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-perspective: @process; - -moz-perspective: @process; - perspective: @process; -} - -.perspective-origin(...) { - @process: ~`(function(e){e=e||"50% 50%";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"%"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-perspective-origin: @process; - -moz-perspective-origin: @process; - perspective-origin: @process; -} - -.placeholder(@color:#aaa, @element: 08121991) { - .inception (@arguments) when not (@element = 08121991) { - @{element}::-webkit-input-placeholder { - color: @color; - } - @{element}:-moz-placeholder { - color: @color; - } - @{element}::-moz-placeholder { - color: @color; - } - @{element}:-ms-input-placeholder { - color: @color; - } - } - .inception (@arguments) when (@element = 08121991) { - &::-webkit-input-placeholder { - color: @color; - } - &:-moz-placeholder { - color: @color; - } - &::-moz-placeholder { - color: @color; - } - &:-ms-input-placeholder { - color: @color; - } - } - .inception(@arguments); -} - -.rotate(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotate(@process); - -moz-transform: rotate(@process); - -ms-transform: rotate(@process); - -o-transform: rotate(@process); - transform: rotate(@process); -} - -.rotate3d(...) { - @process: ~`(function(n){return n=n||"0, 0, 0, 0",n=n.replace(/,\s*\d+$/,function(n){return n+"deg"})})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotate3d(@process); - -moz-transform: rotate3d(@process); - -ms-transform: rotate3d(@process); - -o-transform: rotate3d(@process); - transform: rotate3d(@process); -} - -.rotateX(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotateX(@process); - -moz-transform: rotateX(@process); - -ms-transform: rotateX(@process); - -o-transform: rotateX(@process); - transform: rotateX(@process); -} - -.rotateY(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotateY(@process); - -moz-transform: rotateY(@process); - -ms-transform: rotateY(@process); - -o-transform: rotateY(@process); - transform: rotateY(@process); -} - -.rotateZ(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: rotateZ(@process); - -moz-transform: rotateZ(@process); - -ms-transform: rotateZ(@process); - -o-transform: rotateZ(@process); - transform: rotateZ(@process); -} - -.saturate(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: ~"saturate(@{process})"; - -moz-filter: ~"saturate(@{process})"; - -ms-filter: ~"saturate(@{process})"; - filter: ~"saturate(@{process})"; -} - -.scale(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scale(@process); - -moz-transform: scale(@process); - -ms-transform: scale(@process); - -o-transform: scale(@process); - transform: scale(@process); -} - -.scale3d(...) { - @process: ~`(function(n){return n=n||"1, 1, 1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scale3d(@process); - -moz-transform: scale3d(@process); - -ms-transform: scale3d(@process); - -o-transform: scale3d(@process); - transform: scale3d(@process); -} - -.scaleX(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scaleX(@process); - -moz-transform: scaleX(@process); - -ms-transform: scaleX(@process); - -o-transform: scaleX(@process); - transform: scaleX(@process); -} - -.scaleY(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scaleY(@process); - -moz-transform: scaleY(@process); - -ms-transform: scaleY(@process); - -o-transform: scaleY(@process); - transform: scaleY(@process); -} - -.scaleZ(...) { - @process: ~`(function(n){return n=n||"1"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: scaleZ(@process); - -moz-transform: scaleZ(@process); - -ms-transform: scaleZ(@process); - -o-transform: scaleZ(@process); - transform: scaleZ(@process); -} - -.selection(...) { - @process: ~`(function(e){function t(t,n){var r="}\n",s=a.split(","),c=(s[1]||"")+n+"{"+s[0]+r;"start"==t?e="0; } \n"+c:"startend"==t?e="0; } \n"+c.replace(r,""):e+="end"==t?c.replace(r,""):c}e=e||8121991;var n="@{state}",a=e;if(8121991==e)return e;switch(n){case"1":t("start","::selection"),t("end","::-moz-selection");break;case"2":t("startend","::selection");break;case"3":t("startend","::-moz-selection")}return e=e.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @state: 1; lesshat-selector { -lh-property: @process; } - -} - -.sepia(...) { - @process: ~`(function(n){n=n||"100%";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"%"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-filter: sepia(@process); - -moz-filter: sepia(@process); - -ms-filter: sepia(@process); - filter: sepia(@process); -} - -.size(@square) { - @unit: 'px'; - .process(@square) when (ispixel(@square)), (isem(@square)), (ispercentage(@square)), (iskeyword(@square)) { - width: @square; - height: @square; - } - - .process(@square) when not (ispixel(@square)) and not (isem(@square)) and not (ispercentage(@square)) and not (isstring(@square)) and not (iskeyword(@square)) { - width: ~`@{square} + @{unit}`; - height: ~`@{square} + @{unit}`; - } - - .process(@square); - -} - -.size(@width, @height) { - @unit: 'px'; - .process(@width, @height) when (ispixel(@width)), (isem(@width)), (ispercentage(@width)), (iskeyword(@width)) { - .kittens(@height) when (ispixel(@height)), (isem(@height)), (ispercentage(@height)), (iskeyword(@height)) { - width: @width; - height: @height; - } - .kittens(@height) when not (ispixel(@height)) and not (isem(@height)) and not (ispercentage(@height)) and not (iskeyword(@height)) { - width: @width; - height: ~`@{height} + @{unit}`; - } - .kittens(@height); - } - - .process(@width, @height) when (ispixel(@height)), (isem(@height)), (ispercentage(@height)), (iskeyword(@height)) { - .kittens(@width) when (ispixel(@width)), (isem(@width)), (ispercentage(@width)), (iskeyword(@width)) {} - .kittens(@width) when not (ispixel(@width)) and not (isem(@width)) and not (ispercentage(@width)) and not (iskeyword(@width)) { - width: ~`@{width} + @{unit}`; - height: @height; - } - .kittens(@width); - } - - .process(@width, @height) when not (ispixel(@width)) and not (isem(@width)) and not (ispercentage(@width)) and not (iskeyword(@width)) and not (ispixel(@height)) and not (isem(@height)) and not (ispercentage(@height)) and not (iskeyword(@height)) { - width: ~`@{width} + @{unit}`; - height: ~`@{height} + @{unit}`; - } - - .process(@width, @height); - -} - -.skew(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: skew(@process); - -moz-transform: skew(@process); - -ms-transform: skew(@process); - -o-transform: skew(@process); - transform: skew(@process); -} - -.skewX(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: skewX(@process); - -moz-transform: skewX(@process); - -ms-transform: skewX(@process); - -o-transform: skewX(@process); - transform: skewX(@process); -} - -.skewY(...) { - @process: ~`(function(e){e=e||"0";var n=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return n.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"deg"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: skewY(@process); - -moz-transform: skewY(@process); - -ms-transform: skewY(@process); - -o-transform: skewY(@process); - transform: skewY(@process); -} - -.transform(...) { - @process: ~`(function(e){e=e||"none";var r={translate:"px",rotate:"deg",rotate3d:"deg",skew:"deg"};/^\w*\(?[a-z0-9.]*\)?/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,""));for(var t in r)e.indexOf(t)>=0&&(e=e.replace(new RegExp(t+"[\\w]?\\([a-z0-9, %]*\\)"),function(e){var n=/(\d+\.?\d*)(?!\w|%)/g;return"rotate3d"==t&&(n=/,\s*\d+$/),e.replace(n,function(e){return e+r[t]})}));return e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: @process; - -moz-transform: @process; - -ms-transform: @process; - -o-transform: @process; - transform: @process; -} - -.transform-origin(...) { - @process: ~`(function(e){e=e||"50% 50% 0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"%"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform-origin: @process; - -moz-transform-origin: @process; - -ms-transform-origin: @process; - -o-transform-origin: @process; - transform-origin: @process; -} - -.transform-style(...) { - @process: ~`(function(n){return n=n||"flat"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform-style: @process; - -moz-transform-style: @process; - -ms-transform-style: @process; - -o-transform-style: @process; - transform-style: @process; -} - -.transition(...) { - @process_webkit: ~`(function(r){r=r||"all 0 ease 0";var e=["background-size","border-radius","border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","box-shadow","column","transform","filter"],t="-webkit-",o=/(?:\d)(?:ms|s)/gi,a=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;return/^[^, ]*,/.test(r)&&(r=r.replace(/(?:,)(?![^(]*\))/g,"")),e.forEach(function(e,o){r.indexOf(e)!==-1&&(r=r.replace(new RegExp(e,"g"),function(r){return t+r}))}),o.test(r)||"0"===r||(r=r.replace(a,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(e){e=e||"all 0 ease 0";var n=["background-size","box-shadow","column","transform","filter"],r="-moz-",t=/(?:\d)(?:ms|s)/gi,a=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),n.forEach(function(n,t){e.indexOf(n)!==-1&&(e=e.replace(new RegExp(n,"g"),function(e){return r+e}))}),t.test(e)||"0"===e||(e=e.replace(a,function(e){return e+=parseFloat(e,10)>10?"ms":"s"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_opera: ~`(function(e){e=e||"all 0 ease 0";var n=["transform"],r="-o-",t=/(?:\d)(?:ms|s)/gi,a=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),n.forEach(function(n,t){e.indexOf(n)!==-1&&(e=e.replace(new RegExp(n,"g"),function(e){return r+e}))}),t.test(e)||"0"===e||(e=e.replace(a,function(e){return e+=parseFloat(e,10)>10?"ms":"s"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){n=n||"all 0 ease 0";var e=["-webkit-","-moz-","-o-",""],t=["column","transform","filter"],r=/(?:\d)(?:ms|s)/gi,o=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%)/gi;/^[^, ]*,/.test(n)&&(n=n.replace(/(?:,)(?![^(]*\))/g,""));var i=n.split(/(?:,)(?![^(]*\))/g);return i.forEach(function(n,r){t.forEach(function(t){n.indexOf(t)!==-1&&(i[r]="",e.forEach(function(o,a){i[r]+=n.trim().replace(new RegExp(t,"g"),function(n){return o+n}),a<e.length-1&&(i[r]+=",")}))})}),n=i.join(","),r.test(n)||"0"===n||(n=n.replace(o,function(n){return n+=parseFloat(n,10)>10?"ms":"s"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition: @process_webkit; - -moz-transition: @process_moz; - -o-transition: @process_opera; - transition: @process; -} - -.transition-delay(...) { - @process: ~`(function(r){r=r||"0";var s=/(?:\d)(?:ms|s)/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-delay: @process; - -moz-transition-delay: @process; - -o-transition-delay: @process; - transition-delay: @process; -} - -.transition-duration(...) { - @process: ~`(function(r){r=r||"0";var s=/ms|s/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return s.test(r)||"0"===r||(r=r.replace(t,function(r){return r+=parseFloat(r,10)>10?"ms":"s"})),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-duration: @process; - -moz-transition-duration: @process; - -o-transition-duration: @process; - transition-duration: @process; -} - -.transition-property(...) { - @process_webkit: ~`(function(r){r=r||"all";var o=["background-size","border-radius","border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","box-shadow","column","transform","filter"],t="-webkit-";return o.forEach(function(o,e){r.indexOf(o)!==-1&&(r=r.replace(new RegExp(o,"g"),function(r){return t+r}))}),r})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_moz: ~`(function(n){n=n||"all";var r=["background-size","box-shadow","column","transform","filter"],o="-moz-";return r.forEach(function(r,e){n.indexOf(r)!==-1&&(n=n.replace(new RegExp(r,"g"),function(n){return o+n}))}),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process_opera: ~`(function(n){n=n||"all";var r=["transform"],e="-o-";return r.forEach(function(r,f){n.indexOf(r)!==-1&&(n=n.replace(new RegExp(r,"g"),function(n){return e+n}))}),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - @process: ~`(function(n){n=n||"all";var o=["-webkit-","-moz-","-o-",""],r=["column","transform","filter"],t=n.split(/(?:,)(?![^(]*\))/g);return t.forEach(function(n,f){r.forEach(function(r){n.indexOf(r)!==-1&&(t[f]="",o.forEach(function(i,c){t[f]+=n.trim().replace(new RegExp(r,"g"),function(n){return i+n}),c<o.length-1&&(t[f]+=",")}))})}),n=t.join(",")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-property: @process_webkit; - -moz-transition-property: @process_moz; - -o-transition-property: @process_opera; - transition-property: @process; -} - -.transition-timing-function(...) { - @process: ~`(function(e){return e=e||"ease"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transition-timing-function: @process; - -moz-transition-timing-function: @process; - -o-transition-timing-function: @process; - transition-timing-function: @process; -} - -.translate(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translate(@process); - -moz-transform: translate(@process); - -ms-transform: translate(@process); - -o-transform: translate(@process); - transform: translate(@process); -} - -.translate3d(...) { - @process: ~`(function(n){n=n||"0, 0, 0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translate3d(@process); - -moz-transform: translate3d(@process); - -ms-transform: translate3d(@process); - -o-transform: translate3d(@process); - transform: translate3d(@process); -} - -.translateX(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translateX(@process); - -moz-transform: translateX(@process); - -ms-transform: translateX(@process); - -o-transform: translateX(@process); - transform: translateX(@process); -} - -.translateY(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translateY(@process); - -moz-transform: translateY(@process); - -ms-transform: translateY(@process); - -o-transform: translateY(@process); - transform: translateY(@process); -} - -.translateZ(...) { - @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-transform: translateZ(@process); - -moz-transform: translateZ(@process); - -ms-transform: translateZ(@process); - -o-transform: translateZ(@process); - transform: translateZ(@process); -} - -.user-select(...) { - @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; - -webkit-user-select: @process; - -moz-user-select: @process; - -ms-user-select: @process; - user-select: @process; -} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_mixins.import.less b/packages/rocketchat-theme/assets/stylesheets/utils/_mixins.import.less deleted file mode 100644 index 6737decfc2786241da9bc6d9e3c5563c2db4257f..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_mixins.import.less +++ /dev/null @@ -1,98 +0,0 @@ -.custom-scroll(@background, @thumb, @width: 8px, @height: 8px) { - -webkit-overflow-scrolling: touch; - &::-webkit-scrollbar { - height: @height; - width: @width; - background: @background; - } - &::-webkit-scrollbar-thumb { - background-color: @thumb; - -webkit-border-radius: 50px; - } - &::-webkit-scrollbar-corner { - background-color: @background; - } -} - -.gradient(@startColor: #eee, @endColor: white) { - background-color: @startColor; - background: -webkit-gradient(linear, left top, left bottom, from(@startColor), to(@endColor)); - background: -webkit-linear-gradient(top, @startColor, @endColor); - background: -moz-linear-gradient(top, @startColor, @endColor); - background: -ms-linear-gradient(top, @startColor, @endColor); - background: -o-linear-gradient(top, @startColor, @endColor); -} - -.input-shade(@color, @bg) { - input, - select, - textarea { - color: @color; - background-color: transparent; - border-color: mix(contrast(@bg), @bg, 10%); - border-style: solid; - &::placeholder { - color: mix(@color, @bg, 75%); - } - &[disabled] { - background-color: mix(contrast(@bg), @bg, 10%); - } - } - .diabled label, - [disabled] label { - color: mix(@color, @bg, 75%); - } - .-autocomplete-container { - background-color: mix(contrast(@bg), @bg, 10%); - } - .-autocomplete-item.selected { - background-color: mix(contrast(@bg), @bg, 20%); - } - input[type="button"], - input[type="submit"] { - color: @color; - background: mix(contrast(@bg), @bg, 10%); - border-color: mix(contrast(@bg), @bg, 10%); - } -} - -.blink(@color) { - animation-duration: 1000ms; - animation-name: blink; - animation-iteration-count: infinite; - animation-direction: alternate; - @keyframes blink { - from { - color: @color; - } - to { - opacity: inherit; - } - } - @-webkit-keyframes blink { - from { - color: @color; - } - to { - color: inherit; - } - } -} - -.linkColors(@color, @hover) { - transition: color 0.2s ease-out; - color: @color; - &:hover { - color: @hover; - } -} - -.buttonColors(@color, @bg) { - transition: color 0.2s ease-out, background-color 0.2s ease-out; - color: @color; - background-color: @bg; - &:hover { - color: lighten(@color, 10%); - background-color: darken(@bg, 5%); - } -} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_preloader.import.less b/packages/rocketchat-theme/assets/stylesheets/utils/_preloader.import.less deleted file mode 100644 index ec780839b802bd6b8b355181e361c0f5a552eb6b..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_preloader.import.less +++ /dev/null @@ -1,152 +0,0 @@ -@-webkit-keyframes scaled { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.55)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@-moz-keyframes scaled { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.55)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@-o-keyframes scaled { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.55)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@keyframes scaled { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.55)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@-webkit-keyframes minor { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.08)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@-moz-keyframes minor { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.08)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@-o-keyframes minor { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.08)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -@keyframes minor { - 0% { - .transform(scale(1)); - } - 20% { - .transform(scale(1.08)); - } - 50% { - .transform(scale(1)); - } - 100% { - .transform(scale(1)); - } -} - -.rocket-loader { - position: fixed; - top: 50%; - left: 50%; - margin-left: -75px; - margin-top: -75px; - z-index: 9999; - width: 150px; - height: 150px; - .inner { - .animation(minor 1s ease-out infinite 1.625s); - .transform-origin(100px, 100px); - } - .outer { - .animation(minor 1s ease-out infinite 1.625s); - .transform-origin(100px, 100px); - } - circle { - &:nth-child(1) { - .animation(scaled 1s ease-out infinite .3s); - .transform-origin(135px, 100px); - } - &:nth-child(2) { - .animation(scaled 1s ease-out infinite .15s); - .transform-origin(108px, 100px); - } - &:nth-child(3) { - .animation(scaled 1s ease-out infinite); - .transform-origin(81px, 100px); - } - } -} diff --git a/packages/rocketchat-theme/assets/stylesheets/base.less b/packages/rocketchat-theme/client/imports/base.less similarity index 84% rename from packages/rocketchat-theme/assets/stylesheets/base.less rename to packages/rocketchat-theme/client/imports/base.less index 2180e15a49ae173c2b9fe2339f2cbe3a9a8c5fbd..b8a72a6e11449af41f83406bffb2d90bd510e790 100644 --- a/packages/rocketchat-theme/assets/stylesheets/base.less +++ b/packages/rocketchat-theme/client/imports/base.less @@ -1,5 +1,6 @@ .clearfix { clear: both; + &::after { content: ""; display: table; @@ -8,14 +9,14 @@ } *, -*:before, -*:after { +*::before, +*::after { .box-sizing(border-box); } *:not(input):not(textarea), -*:not(input):not(textarea):before, -*:not(input):not(textarea):after { +*:not(input):not(textarea)::before, +*:not(input):not(textarea)::after { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -25,7 +26,7 @@ button { background: none; border-width: 0; - padding: 0px; + padding: 0; text-align: left; cursor: pointer; text-transform: inherit; @@ -65,6 +66,7 @@ button { a { cursor: pointer; text-decoration: none; + &:hover, &:active { text-decoration: none; @@ -86,6 +88,7 @@ code { unicode-bidi: embed; direction: ltr; font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + &.inline { display: inline; padding: 0 0.5em; @@ -105,21 +108,24 @@ pre { blockquote { .clearfix; - margin: .5em 0; + margin: 0.5em 0; + &:first-child { margin-top: 0; } + &:last-child { margin-bottom: 0; } padding-left: 10px; position: relative; - &:before { + + &::before { content: ' '; width: 4px; position: absolute; border-radius: 2px; - left: 0px; + left: 0; top: -1px; bottom: -1px; } @@ -131,6 +137,7 @@ blockquote { padding: 10px; max-width: 100%; margin: auto; + a { font-weight: bold !important; text-decoration: underline; @@ -139,6 +146,7 @@ blockquote { .upload-preview { padding: 1rem; + .upload-preview-file { height: 200px; background-size: contain; @@ -173,29 +181,39 @@ blockquote { user-select: none; } -.first-unread { +.first-unread, +.cozy .first-unread, +.compact .first-unread { + &.message, + &.sequential.message { + padding-top: 20px; + } + .body { &::before { content: ""; display: block; - height: 1px; position: absolute; - right: 0px; - left: 20px; - top: 0px; - .transition(background-color, .5s, linear); + right: 0; + left: 0; + top: 0; + transition: background-color, 0.5s, linear; + height: 16px; } + &::after { content: "unread messages"; display: block; position: absolute; - right: 0px; - top: -4px; + right: 0; + top: 0; text-transform: uppercase; - font-size: 8px; - line-height: 10px; + font-size: 12px; + line-height: 16px; padding: 0 5px; - .transition(color, .5s, linear); + transition: color, 0.5s, linear; + left: 0; + text-align: center; } } } @@ -209,7 +227,7 @@ blockquote { position: absolute; width: 100%; z-index: 1000000; - border-radius: 0px; + border-radius: 0; } .alert { @@ -249,52 +267,87 @@ blockquote { -webkit-overflow-scrolling: touch; } -.rocket-form { - max-width: 620px; - width: 90%; - legend { - margin-bottom: 23px; - position: relative; - width: 100%; - display: block; - h3 { - margin-bottom: 5px !important; - } - &:after { - content: " "; - height: 1px; - display: block; - position: absolute; - width: 100%; - bottom: -10px; - left: 0; - } - } - fieldset { - display: block; - margin-bottom: 40px; - small { - font-size: 11px; - } - } - .logoutOthers { - text-align: right; - } - .submit { - margin-top: 20px; - text-align: right; - } - &.request-password { - margin: 0 auto; +.page-container { + &:extend(.fill-all); + overflow-y: hidden; + + .content { + &:extend(.fill-all); + padding: 25px 40px; + overflow-y: scroll; + margin-top: 60px; + -webkit-overflow-scrolling: touch; + .calc(height, ~'100% - 60px'); + fieldset { - margin-top: 20px; - label { + margin-bottom: 1em; + } + + .rocket-form { + fieldset { + display: block; + margin: 1em 0 1.5em; + + small { + font-size: 11px; + } + } + + legend { + margin: 12px 0; + position: relative; + width: 100%; display: block; + font-weight: bold; + + h3 { + margin-bottom: 5px !important; + } + } + + .logoutOthers { + text-align: right; + } + + .submit { margin-top: 20px; + text-align: right; + } + + &.request-password { + margin: 0 auto; + + fieldset { + margin-top: 20px; + + label { + display: block; + margin-top: 20px; + } + } + + .submit { + text-align: center; + } } } - .submit { - text-align: center; + } + + table { + overflow: hidden; + margin-bottom: 30px; + width: 100%; + + th, + td { + vertical-align: middle; + padding: 0.6rem 0.7rem; + text-align: left; + border-width: 0 0 1px; + } + + th { + white-space: nowrap; } } } @@ -303,14 +356,21 @@ blockquote { .clearfix; display: block; margin-bottom: 12px; + &:nth-last-child(1) { margin-bottom: 0; } + &.search { - .icon-spin { + i { position: absolute; - right: 5px; top: 10px; + left: 7px; + } + + .icon-spin { + right: 5px; + left: auto; font-weight: 400; -webkit-animation-name: spin; -webkit-animation-duration: 2000ms; @@ -329,25 +389,20 @@ blockquote { animation-iteration-count: infinite; animation-timing-function: linear; } - .icon-search, - .icon-right-open-small, - .icon-sort-alt-up, - .icon-lock, - .icon-comment { - position: absolute; - left: 2px; - top: 10px; - } + input { padding-left: 30px; } } + > label { display: block; margin-bottom: 4px; } + > div { position: relative; + .right { position: absolute; right: 10px; @@ -355,32 +410,38 @@ blockquote { z-index: 10; } } + > div.-autocomplete-container { position: absolute; } + input[type='text'] { display: block; } + &.double-col { > label { width: 30%; float: left; margin-bottom: 0; - padding-right: 20px; text-align: right; line-height: 15px; padding: 10px 20px 10px 0; } + > div { float: left; width: 70%; + label { display: inline-block; margin-right: 4px; line-height: 35px; + &:nth-last-child(1) { margin-right: 0; } + input { margin-right: 4px; } @@ -393,6 +454,7 @@ blockquote { from { -ms-transform: rotate(0deg); } + to { -ms-transform: rotate(360deg); } @@ -402,6 +464,7 @@ blockquote { from { -moz-transform: rotate(0deg); } + to { -moz-transform: rotate(360deg); } @@ -411,6 +474,7 @@ blockquote { from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(360deg); } @@ -420,6 +484,7 @@ blockquote { from { transform: rotate(0deg); } + to { transform: rotate(360deg); } @@ -451,13 +516,13 @@ blockquote { html { overflow-y: scroll; height: 100%; + &.noscroll { overflow: hidden; } } body { - font-family: @body-font-family; font-size: 0.875rem; height: 100%; width: 100%; @@ -469,6 +534,7 @@ body { } // input & form styles + input, button, select, @@ -505,9 +571,9 @@ input[type='password'] { } input.input-forward { - width: 0px; + width: 0; visibility: hidden; - .transition(width .5s ease-in); + transition: width 0.5s ease-in; } input.input-forward.show { @@ -516,7 +582,7 @@ input.input-forward.show { } input.search { - &:before { + &::before { content: " "; width: 30px; height: 30px; @@ -554,16 +620,18 @@ form.inline { } .-autocomplete-container { - box-shadow: 1px 1px 0px rgba(0, 0, 0, 0.2); + box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); border-width: 0; border-radius: 0; width: 100%; top: auto !important; + p { font-size: 14px; - padding: 8px 8px; + padding: 8px; } - .loading { + + .loading-animation { position: relative; min-height: 60px; } @@ -575,7 +643,7 @@ form.inline { cursor: pointer; } -label.required:after { +label.required::after { content: ' *'; } @@ -584,15 +652,18 @@ label.required:after { cursor: not-allowed; box-shadow: none !important; border-width: 0; + i { display: block; } + div { display: none; } } // new layout buttons + .button { &:extend(.unselectable); border-width: 0; @@ -606,15 +677,61 @@ label.required:after { line-height: 16px; position: relative; border-radius: 4px; + span { position: relative; z-index: 2; } + &.button-block { display: block; margin-bottom: 4px; width: 100%; } + + &[disabled] { + cursor: initial; + } + + &.external-login { + color: white; + + &.facebook { + background-color: #325c99; + } + + &.twitter { + background-color: #02acec; + } + + &.google { + background-color: #dd4b39; + } + + &.github { + background-color: #4c4c4c; + } + + &.gitlab { + background-color: #373d47; + } + + &.trello { + background-color: #026aa7; + } + + &.meteor-developer { + background-color: #de4f4f; + } + + &.wordpress { + background-color: #1e8cbe; + } + + &.linkedin { + background-color: #1b86bc; + } + } } .buttons-group { @@ -622,26 +739,36 @@ label.required:after { display: -moz-flex; display: flex; margin-bottom: 4px; + .button { margin-left: 4px; } + .button:first-child { - margin-left: 0px; - -webkit-flex-grow: 1; - -moz-flex-grow: 1; + margin-left: 0; flex-grow: 1; } } +.oauth-panel { + .buttons-group { + .button:first-child { + flex-grow: 0; + } + } +} + .sec-header { margin: 16px 0; text-align: center; + > * { display: inline-table; width: auto; vertical-align: middle; line-height: 35px; } + label { margin-left: 20px; } @@ -655,22 +782,19 @@ label.required:after { margin-left: 7px; position: absolute; top: 5px; - left: 0px; - .transition(transform .2s ease-out .1s); + left: 0; + will-change: transform; + transition: transform 0.2s ease-out 0.1s; + i { display: block; height: 2px; width: 20px; margin: 5px 0; - opacity: .8; - .transition(transform .2s ease-out, width .2s ease-out); - &:nth-child(1) { - // .transition-delay(.06s); - } - &:nth-child(3) { - .transition-delay(.1s); - } + opacity: 0.8; + transition: transform 0.2s ease-out; } + .unread-burger-alert { border-radius: 20px; position: absolute; @@ -683,7 +807,25 @@ label.required:after { top: 8px; right: 4px; z-index: 3; - padding: 0px 4px; + padding: 0 4px; + } + + &.menu-opened { + i { + &:nth-child(1), + &:nth-child(3) { + opacity: 1; + .transform-origin(50%, 50%, 0); + } + + &:nth-child(1) { + .transform(translate(-25%, 3px) rotate(-45deg) scale(0.5, 1)); + } + + &:nth-child(3) { + .transform(translate(-25%, -3px) rotate(45deg) scale(0.5, 1)); + } + } } } @@ -693,8 +835,9 @@ label.required:after { height: 25px; z-index: 100; .calc(top, ~"50% - 13px"); - &:before, - &:after { + + &::before, + &::after { content: " "; display: block; width: 2px; @@ -703,43 +846,53 @@ label.required:after { .calc(top, ~"50% - 5px"); .calc(left, ~"50% - 5px"); } - &:before { + + &::before { .transform(rotate(135deg) translateX(-4px)); - .transition(transform .185s ease-out, background .15s ease-out); + transition: transform 0.185s ease-out, background 0.15s ease-out; } - &:after { + + &::after { .transform(rotate(-135deg) translateX(-4px)); - .transition(transform .185s ease-out, background .15s ease-out); + transition: transform 0.185s ease-out, background 0.15s ease-out; } + &.left { - &:before { + &::before { .transform(rotate(45deg) translateY(-4px)); } - &:after { + + &::after { .transform(rotate(-45deg) translateY(4px)); } } + &.top { - &:before { + &::before { .transform(rotate(-135deg) translateX(2px) translateY(-2px)); } - &:after { + + &::after { .transform(rotate(135deg) translateX(-2px) translateY(-2px)); } } + &.bottom { - &:before { + &::before { .transform(rotate(-45deg) translateX(-2px) translateY(-2px)); } - &:after { + + &::after { .transform(rotate(45deg) translateX(2px) translateY(-2px)); } } + &.close { - &:before { + &::before { .transform(rotate(-135deg) translateX(0) translateY(0)); } - &:after { + + &::after { .transform(rotate(135deg) translateX(0) translateY(0)); } } @@ -751,28 +904,25 @@ label.required:after { overflow: hidden; position: relative; border-radius: 4px; + .emoji, .emojione { width: 100%; height: 100%; - margin: 0px; + margin: 0; } + .avatar-image { height: 100%; width: 100%; - min-height: 20px; - min-width: 20px; - display: block; - position: relative; background-size: cover; background-repeat: no-repeat; background-position: center; - border-radius: 4px; } - &[initials]:before { + + &[initials]::before { content: attr(initials); position: absolute; - position: absolute; font-size: 22px; text-align: center; width: 100%; @@ -795,11 +945,13 @@ label.required:after { height: auto; opacity: 1; visibility: visible; - .transition(opacity .2s ease-out); + transition: opacity 0.2s ease-out; + &.animated-hidden { visibility: hidden; opacity: 0; } + > .alert { margin-bottom: 0; padding: 5px; @@ -812,19 +964,21 @@ label.required:after { height: 100%; cursor: pointer; width: 100%; + .info { position: relative; height: 100%; - padding: 10px 0px 10px 18px; + padding: 10px 0 10px 18px; z-index: 100; + .thumb { float: left; - height: 100%; position: relative; width: 42px; padding: 0; height: 42px; - &:after { + + &::after { content: " "; display: block; width: 8px; @@ -835,10 +989,12 @@ label.required:after { top: 18px; left: -14px; } + .avatar-initials { line-height: 44px; } } + .data { float: left; position: relative; @@ -848,6 +1004,7 @@ label.required:after { flex-flow: row nowrap; .calc(width, ~"100% - 60px"); } + h4 { display: block; line-height: 18px; @@ -859,9 +1016,10 @@ label.required:after { position: relative; width: 130px; text-align: left; - .transition(color .15s ease-out); + transition: color 0.15s ease-out; } } + .options { position: fixed; top: @header-min-height; @@ -873,18 +1031,22 @@ label.required:after { -webkit-overflow-scrolling: touch; direction: rtl; .calc(height, ~'100% - ' @header-min-height + @footer-min-height); - .transition(transform .3s cubic-bezier(.5, 0, .1, 1)); + transition: transform 0.3s cubic-bezier(0.5, 0, 0.1, 1); z-index: 99; + &.animated-hidden { .transform(translateY(-100%) translateY(-50px)); } + > .wrapper { direction: ltr; } + .status { padding-left: 38px; position: relative; - &:after { + + &::after { content: " "; display: block; width: 13px; @@ -897,20 +1059,22 @@ label.required:after { .calc(top, ~"50% - 8px"); } } + span.soon { - // content: "em breve"; width: 100px; position: absolute; right: -30px; font-size: 10px; top: 17px; } + i { width: 26px; display: inline-block; text-align: center; margin-left: 0 -1px 0 1px; } + button, a { position: relative; @@ -919,17 +1083,20 @@ label.required:after { padding: 15px 12px; line-height: 1; text-decoration: none; + &:hover { text-decoration: none; } } + .icon-logout { - &:before { - margin-right: 0px; + &::before { + margin-right: 0; } } + .icon-camera { - &:before { + &::before { margin-left: 1px; } } @@ -937,6 +1104,7 @@ label.required:after { } // rooms-box + .flex-nav { position: fixed; top: 0; @@ -946,23 +1114,28 @@ label.required:after { overflow-y: auto; overflow-x: hidden; width: @rooms-box-width; - .transition(transform .15s cubic-bezier(.5, 0, .1, 1)); + transition: transform 0.15s cubic-bezier(0.5, 0, 0.1, 1); + &.animated-hidden { .transform(translateX(-100%)); + header, footer, .content { .transform(translateX(-100%)); } } + header, footer, .content { - .transition(transform .425s cubic-bezier(0, .8, .05, 1)); + transition: transform 0.425s cubic-bezier(0, 0.8, 0.05, 1); } + > section { &:extend(.fill-all); } + header { display: table; position: absolute; @@ -974,12 +1147,13 @@ label.required:after { min-height: @header-min-height; height: @header-min-height; padding-left: 15px; - .transition-delay(.05s); + > div { display: table-cell; vertical-align: middle; text-align: left; } + h4 { line-height: 24px; font-size: 20px; @@ -989,6 +1163,7 @@ label.required:after { text-overflow: ellipsis; position: relative; } + p { line-height: 18px; margin-top: 4px; @@ -996,6 +1171,7 @@ label.required:after { font-size: 13px; } } + footer { display: table; position: absolute; @@ -1006,13 +1182,14 @@ label.required:after { z-index: 120; text-align: left; height: @footer-min-height; - .transition-delay(.22s); + > div { display: table-cell; vertical-align: middle; text-align: left; } } + .content { direction: rtl; position: absolute; @@ -1024,20 +1201,24 @@ label.required:after { display: block; -webkit-overflow-scrolling: touch; padding: 20px 10px; - .transition-delay(.135s); + &.no-shadow { box-shadow: 0 0 0; } + > .wrapper { direction: ltr; + .flex-control { margin-bottom: 30px; + .search { width: 100%; margin-bottom: 10px; } } } + h4 { margin-bottom: 30px; font-weight: 400; @@ -1045,30 +1226,29 @@ label.required:after { font-size: 13px; } } + .input-line { margin-bottom: 25px; + &:nth-last-child(1) { margin-bottom: 0; } + label { - text-transform: uppercase; font-weight: 400; margin-bottom: 0; } + input[type='text'], input[type='password'], select { - border-width: 0 0 1px 0; - padding: 0 8px; + padding: 0 8px 0 30px; box-shadow: 0 0 0; - border-radius: 0; - -webkit-appearance: none; - -moz-appearance: none; appearance: none; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - border-radius: 0px; + border-width: 1px; + border-radius: 4px; } + .inline-fields { input, label, @@ -1076,18 +1256,44 @@ label.required:after { display: inline-block; } } + + &.toggle { + font-size: 0; + + > span { + display: inline-block; + width: calc(~"100% - 40px"); + font-size: 14px; + vertical-align: top; + } + + > div { + width: 40px; + display: inline-block; + } + } + + &.no-icon { + input { + padding: 0 8px; + } + } } + .input-submit { margin: 35px 0 0 -4px; } + .selected-users { padding: 20px 0 0; + li { display: inline-block; padding: 5px; margin-right: 2px; margin-bottom: 2px; } + i { cursor: pointer; } @@ -1096,7 +1302,6 @@ label.required:after { .side-nav { position: fixed; - display: block; top: 0; bottom: 0; left: 0; @@ -1104,9 +1309,10 @@ label.required:after { height: auto; overflow: visible; z-index: 100; - padding: 12px 0 0 0; - .transition(transform .3s ease-out); - &:before { + padding: 12px 0 0; + will-change: transform; + + &::before { content: " "; height: 1px; width: 189px; @@ -1114,6 +1320,7 @@ label.required:after { position: absolute; top: 59px; } + .rooms-list { direction: rtl; position: absolute; @@ -1124,12 +1331,14 @@ label.required:after { overflow-y: auto; display: block; -webkit-overflow-scrolling: touch; + > .wrapper { direction: ltr; padding-left: 8px; padding-bottom: 1em; } } + .more { display: block; width: 100%; @@ -1137,22 +1346,25 @@ label.required:after { padding: 4px 0 4px 10px; margin-top: 2px; } + .input-error { - text-align: center; font-size: 12px; padding: 0; text-align: left; margin-bottom: -20px; margin-top: -12px; + strong { display: block; margin-bottom: 2px; } } + .empty { font-size: 11px; padding: 2px 10px; } + .header { position: absolute; top: 0; @@ -1163,6 +1375,7 @@ label.required:after { min-height: @header-min-height; height: @header-min-height; } + > .arrow { position: absolute; top: 18px; @@ -1170,24 +1383,28 @@ label.required:after { z-index: 1000; cursor: pointer; } + .footer { position: absolute; bottom: 0; left: 0; width: 100%; - padding: 10px 15px 0px 15px; + padding: 10px 15px 0; text-align: right; min-height: @footer-min-height; height: @footer-min-height; + .logo { display: block; width: 100%; height: 100%; margin-top: -1px; + &:hover { text-decoration: none; } } + small { font-size: 11px; width: 100%; @@ -1198,6 +1415,7 @@ label.required:after { padding-right: 4px; margin-top: 2px; } + img { display: inline-block; max-width: 222px; @@ -1205,24 +1423,23 @@ label.required:after { margin-bottom: -10px; } } + .search-form { - .search { - padding-left: 25px; - } > div { position: relative; } - margin-right: 20px; } + h3 { &:extend(.small-title); cursor: pointer; position: relative; text-transform: uppercase; font-weight: 500; - margin: 25px 0 0 0; + margin: 25px 0 0; line-height: 28px; padding-left: 10px; + &.add-room { i { position: absolute; @@ -1231,6 +1448,7 @@ label.required:after { } } } + .unread { min-width: 15px; padding: 0 2px; @@ -1243,15 +1461,18 @@ label.required:after { line-height: 16px; font-weight: 800; } + ul { position: relative; //left: 1px; + li { white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; + .remove, .erase { position: absolute; @@ -1260,25 +1481,29 @@ label.required:after { top: 2px; opacity: 0; .transform(translateX(-10px)); - .transition(opacity .15s ease .35s, transform .12s ease-out .35s); + transition: opacity 0.15s ease 0.35s, transform 0.12s ease-out 0.35s; } + &:hover { .opt { opacity: 1; .transform(translateX(0)); } } + &.has-unread { .opt { opacity: 0; } } + &.has-alert { .name { font-weight: bold; } } } + a { display: block; border-radius: 2px 0 0 2px; @@ -1292,13 +1517,16 @@ label.required:after { text-overflow: ellipsis; vertical-align: middle; text-decoration: none; + &:hover { text-decoration: none; } + .archived { font-style: italic; } } + .opt { position: absolute; right: 0; @@ -1308,34 +1536,41 @@ label.required:after { opacity: 0; display: block; top: 7px; - .transition(opacity .12s ease); + transition: opacity 0.12s ease; + i { margin: 0 1px; } - .icon-cancel-circled:before { + + .icon-cancel-circled::before { margin-left: 2px; } + .icon-logout { margin-left: 1px; } + &.fixed { opacity: 1; .transform(translateX(0)); } } + i { font-size: 14px; width: 16px; display: inline-block; } + input[type="text"] { width: 100%; font-size: 12px; } } + .unread-rooms { position: absolute; - z-index: 1000; + z-index: 1; width: 100%; text-align: center; line-height: 24px; @@ -1347,23 +1582,28 @@ label.required:after { -webkit-align-items: center; justify-content: center; -webkit-justify-content: center; + &.top-unread-rooms { top: 60px; } + &.bottom-unread-rooms { bottom: 70px; } + i { margin-left: 5px; font-size: 12px; } } + .unread-rooms-mode { max-height: 0; opacity: 0; overflow: hidden; + &.has-unread { - .transition(max-height 1s ease-in, opacity .5s linear); + transition: max-height 1s ease-in, opacity 0.5s linear; max-height: 5000px; opacity: 1; } @@ -1377,38 +1617,24 @@ label.required:after { animation: highlight 2s infinite; } -.page-container { - &:extend(.fill-all); - overflow-y: hidden; - .content { - &:extend(.fill-all); - padding: 25px 40px; - overflow-y: scroll; - margin-top: 60px; - -webkit-overflow-scrolling: touch; - .calc(height, ~'100% - 60px'); - fieldset { - margin-bottom: 1em; - } - } -} - .fixed-title { position: absolute; .flex-center; flex-flow: row nowrap; padding: 0 10px 0 20px; - border-width: 0 0 1px 0; + border-width: 0 0 1px; z-index: 100; top: 0; left: 0; width: 100%; height: @header-min-height+1px; + &.visible { h2 { overflow: visible; } } + h2 { width: 100%; overflow: hidden; @@ -1417,20 +1643,33 @@ label.required:after { font-size: 22px; font-weight: 500; line-height: 29px; + .icon-at, .icon-hash, .icon-lock { margin-right: -7px; } + .icon-star, .icon-star-empty { margin-right: -4px; } } + + .submit { + display: flex; + + .button { + white-space: nowrap; + margin-left: 1rem; + } + } + .animated-hidden { visibility: hidden; display: none; } + input[type='text'] { .calc(width, ~'100% - 100px'); vertical-align: top; @@ -1438,6 +1677,7 @@ label.required:after { margin-left: -3px; font-size: 20px; } + .icon-pencil { vertical-align: text-top; margin-top: -7px; @@ -1451,14 +1691,14 @@ label.required:after { margin: 40px auto; padding: 20px; border-radius: 4px; - box-shadow: 1px 1px 4px rgba(0, 0, 0, .3); + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3); + .cms-page-close { margin-bottom: 10px; text-align: right; } } - .spotlight { position: fixed; top: 0; @@ -1470,6 +1710,7 @@ label.required:after { display: -webkit-flex; justify-content: center; padding: 0 40px; + > .spotlight-input { position: relative; width: 100%; @@ -1479,14 +1720,16 @@ label.required:after { margin-bottom: auto; border-radius: 5px; overflow: hidden; - box-shadow: 0px 15px 50px rgba(0, 0, 0, 0.5); + box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5); + > input { box-shadow: none; - border-width: 0px; + border-width: 0; line-height: 46px; height: 46px; padding: 0 10px 0 46px; } + > i { position: absolute; z-index: 10; @@ -1495,10 +1738,12 @@ label.required:after { text-align: center; font-weight: 100; } + .message-popup { position: relative; box-shadow: none; border-radius: 0; + .popup-item { border-top: 1px solid #eaeaea; line-height: 40px; @@ -1507,6 +1752,7 @@ label.required:after { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; + i { margin-right: 4px; line-height: 28px; @@ -1514,11 +1760,13 @@ label.required:after { border-radius: 20px; width: 28px; } + &.selected { i { box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2); } } + span { float: right; border-radius: 2px; @@ -1535,32 +1783,8 @@ label.required:after { } } -.mobile-message-menu { - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 1000; - justify-content: center; - padding: 0 40px; - > .buttons { - font-size: 24px; - position: absolute; - bottom: 0; - left: 10%; - right: 10%; - .button { - display: block; - text-align: center; - } - } - .mobile-menu-separator { - height: 10px; - } -} - // MAIN CONTENT + MAIN PAGES // + .main-content { position: fixed; top: 0; @@ -1569,35 +1793,38 @@ label.required:after { right: 40px; width: auto; height: auto; - .transition(right .25s cubic-bezier(.5, 0, .1, 1)); + will-change: transform; + transition: right 0.25s cubic-bezier(0.5, 0, 0.1, 1); + &.flex-opened { right: @flex-tab-width + 40px; - .flex-tab { - right: 40px; - .transform(translateX(0)); - } } + &.layout1 { right: @flex-tab-webrtc-width; + .flex-tab { max-width: @flex-tab-webrtc-width; - .transform(translateX(0)); } } + &.layout2 { right: @flex-tab-webrtc-2-width; + .flex-tab { max-width: @flex-tab-webrtc-2-width; - .transform(translateX(0)); } } + &.main-modal { - left: 0px; - margin-right: 0px; + left: 0; + margin-right: 0; } + .container-fluid { padding-top: 0; } + .history-date { margin-bottom: 20px; } @@ -1608,10 +1835,12 @@ label.required:after { h2 { margin-bottom: 2rem; } + h3 { margin-bottom: 1rem; } padding: 20px 0; + > .info { max-width: auto; line-height: 24px; @@ -1620,88 +1849,115 @@ label.required:after { font-weight: 500; } } + .section { - border: 1px solid #ddd; + border: 1px solid #dddddd; border-radius: 4px; - background-color: #fff; + background-color: #ffffff; padding: 20px; margin: 20px; + &.section-collapsed { .section-content { display: none; } } - .section-title { + } + + .section-title { + display: flex; + font-size: 24px; + font-weight: 600; + line-height: 40px; + + .section-title-text { + flex-grow: 1; + } + + .section-title-right { + line-height: 0; + } + } + + .section-content { + border: none !important; + border-radius: 0 !important; + padding: 20px 0 0 !important; + + .input-line { + border-bottom: 1px solid #eeeeee; + padding: 20px 0; + margin-bottom: 0; display: flex; - font-size: 24px; - font-weight: 600; - line-height: 40px; - .section-title-text { - flex-grow: 1; + align-items: flex-start; + + &:last-child { + border-bottom: none; + padding-bottom: 0; } - .section-title-right { - line-height: 0px; + + &:first-child { + padding-top: 0; } - } - .section-content { - border: none !important; - border-radius: 0px !important; - padding: 20px 0 0 0 !important; - .input-line { - border-bottom: 1px solid #eee; - padding: 20px 0; - margin-bottom: 0px; - &:last-child { - border-bottom: none; - padding-bottom: 0; - } - &:first-child { - padding-top: 0; - } - > label { - text-align: left; - font-weight: 500; - font-size: 16px; - line-height: 20px; - } - .settings-description { - padding: 5px; - line-height: 20px; - } - .settings-alert { - border-width: 1px; - font-weight: bold; - padding: 5px; - } - .horizontal { - display: flex; - } - .flex-grow-1 { - flex-grow: 1; - } - .color-editor { - width: 150px; - position: relative; - } + + .horizontal { + display: flex; } - .selected-rooms { - .remove-room { - cursor: pointer; - } + + .flex-grow-1 { + flex-grow: 1; + } + } + + .reset-setting { + margin-left: 20px; + } + + .setting-label { + text-align: left; + font-weight: 500; + font-size: 16px; + line-height: 20px; + width: 25%; + } + + .settings-description { + padding: 5px; + line-height: 20px; + } + + .settings-alert { + border-width: 1px; + font-weight: bold; + padding: 5px; + } + + .color-editor { + width: 150px; + position: relative; + } + + .selected-rooms { + .remove-room { + cursor: pointer; } } } + .settings-description { .allow-text-selection; } + .rocket-form { max-width: none; width: 100%; padding: 0; } + .settings-file-preview { display: flex; align-items: center; + input[type=file] { position: absolute !important; width: 100%; @@ -1711,19 +1967,22 @@ label.required:after { opacity: 0; z-index: 10000; cursor: pointer; + * { cursor: pointer; } } + .preview { height: 50px; width: 100px; border-radius: 4px; overflow: hidden; - box-shadow: 0 0 1px rgba(0, 0, 0, .5) inset; + box-shadow: 0 0 1px rgba(0, 0, 0, 0.5) inset; background-size: contain; background-position: center center; background-repeat: no-repeat; + &.no-file { display: flex; align-items: center; @@ -1738,16 +1997,20 @@ label.required:after { .content { > div { margin-bottom: 25px; + &:nth-last-child(1) { margin-bottom: 0; } } + p { margin-bottom: 12px; + &:nth-last-child(1) { margin-bottom: 0; } } + .section { h1 { font-size: 20px; @@ -1755,21 +2018,26 @@ label.required:after { padding: 0 0 0 10px; font-weight: 500; } + &:first-of-type > h1 { - margin-top: 0px; + margin-top: 0; } + .section-content { border-width: 1px; padding: 20px; border-radius: 4px; + .section-helper { padding: 20px 20px 40px; + pre { display: inline; } } } } + h1, h2, h3, @@ -1787,19 +2055,23 @@ label.required:after { overflow: visible; } } + .logo { display: block; margin: 10px 0; max-width: 325px; } + .info { max-width: 680px; line-height: 20px; } + .social { h4 { margin-bottom: 8px; } + nav { margin-left: -4px; } @@ -1810,38 +2082,46 @@ label.required:after { .search { margin-bottom: 12px; } + .results { padding: 10px 0; - border-width: 0 0 1px 0; + border-width: 0 0 1px; margin-bottom: 10px; font-weight: 300; + p { font-size: 12px; text-transform: uppercase; } } + .list { a { display: block; padding: 3px; margin-bottom: 5px; + .info { h3 { margin-bottom: 5px; } + ul { margin-left: 3px; } } } + .room-info { padding: 3px; margin-bottom: 5px; cursor: pointer; + h3 { margin-bottom: 5px; } } + .user-image { float: right; margin-left: 12px; @@ -1867,6 +2147,7 @@ label.required:after { justify-content: center; -webkit-justify-content: center; width: 200px; + i { font-size: 24px; } @@ -1882,10 +2163,12 @@ label.required:after { justify-content: center; -webkit-justify-content: center; font-size: 30px; + div { line-height: 40px; text-align: center; } + i { font-size: 100px; padding-bottom: 30px; @@ -1894,63 +2177,93 @@ label.required:after { .container-bars { position: absolute; - top: 60px; - width: 100%; - z-index: 11; + top: 55px; + z-index: 100; font-weight: bold; display: flex; flex-direction: column; + border-radius: 4px; + overflow: hidden; + font-size: 1em; + left: 10px; + right: 10px; + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); + transition: transform 0.4s ease, visibility 0.3s ease, opacity 0.3s ease; + .transform(translateY(-10px)); + opacity: 0; + visibility: hidden; + + &.show { + opacity: 1; + visibility: visible; + .transform(translateY(0px)); + } + > div { - line-height: 24px; + line-height: 28px; padding: 0 10px; - border-width: 1px 0 0 0; - &:last-child { - box-shadow: 0px 1px 2px rgba(0, 0, 0, .2); - } } - font-size: 12px; + .upload-progress { - height: 24px; + height: 28px; position: relative; + .upload-progress-progress { position: absolute; - left: 0px; + left: 0; height: 100%; width: 0%; z-index: 1; - .transition(width, 1s, ease-out); + transition: width, 1s, ease-out; } + .upload-progress-text { padding: 0 10px; position: absolute; - left: 0px; - right: 0px; + left: 0; + right: 0; height: 100%; z-index: 2; + > a { float: right; text-transform: uppercase; cursor: pointer; } } + + button { + float: right; + font-weight: bold; + text-transform: uppercase; + } } + .unread-bar { text-transform: uppercase; text-align: center; + > button.mark-read { float: right; + &:hover { cursor: pointer; } } + .unread-count { display: none; } + > button.jump-to { float: left; + .jump-to-small { display: none; } + &:hover { cursor: pointer; } @@ -1959,6 +2272,7 @@ label.required:after { } // change to page-messages + .messages-container { position: absolute; width: 100%; @@ -1966,15 +2280,18 @@ label.required:after { top: 0; left: 0; border-width: 0 1px 0 0; + .room-topic { font-size: 14px; opacity: 0.4; margin-left: 10px; } + .edit-room-title { margin-left: 4px; font-size: 16px; } + .wrapper { position: absolute; width: 100%; @@ -1986,32 +2303,39 @@ label.required:after { word-wrap: break-word; -webkit-overflow-scrolling: touch; } + .footer { position: absolute; - padding: 8px 20px 0px 20px; - border-width: 1px 0 0 0; + padding: 8px 20px 0; + border-width: 1px 0 0; z-index: 100; bottom: 0; left: 0; width: 100%; min-height: @footer-min-height; } + .message-form { + margin-bottom: 18px; + > .message-input { border-width: 1px; overflow: hidden; border-radius: 5px; position: relative; display: flex; + .input-message-container { position: relative; width: 100%; + .inner-left-toolbar { position: absolute; left: 13px; top: 9px; } } + > .message-buttons { flex: 0 0 35px; text-align: center; @@ -2021,10 +2345,12 @@ label.required:after { align-items: center; justify-content: center; position: relative; - .transition(background-color 0.1s linear, color 0.1s linear); + transition: background-color 0.1s linear, color 0.1s linear; + i { font-size: 18px; } + input { position: absolute; top: 0; @@ -2034,15 +2360,18 @@ label.required:after { overflow: hidden; opacity: 0; cursor: pointer; + width: 100%; } + input::-webkit-file-upload-button { cursor: pointer; } } } + textarea { display: block; - margin: 0px; + margin: 0; padding-top: 9px; padding-bottom: 9px; padding-left: 49px; @@ -2052,6 +2381,7 @@ label.required:after { line-height: 16px; border-width: 0 1px 0 0; } + .users-typing { float: left; height: 23px; @@ -2064,9 +2394,10 @@ label.required:after { max-width: 100%; z-index: 10; } + .formatting-tips { float: right; - height: 23px; + height: 25px; font-size: 11px; padding: 3px; display: -webkit-flex; @@ -2075,22 +2406,27 @@ label.required:after { overflow: hidden; position: absolute; right: 20px; - opacity: .5; + opacity: 0.5; white-space: nowrap; - .transition(opacity .2 linear); + transition: opacity 0.2 linear; + > * { margin: 0 3px; } + &:hover { opacity: 1; } + q { padding: 0 0 0 3px; border-width: 0 0 0 3px; - &:before { + + &::before { content: none !important; } } + code { line-height: 13px; overflow: hidden; @@ -2098,22 +2434,34 @@ label.required:after { font-size: 10px; white-space: nowrap; } + .hidden-br { display: inline-block; } - .icon-level-down:before { + + .icon-level-down::before { transform: rotate(90deg); } } + + .stream-info { + font-size: 12px; + height: 25px; + padding: 3px; + float: left; + } + .editing-commands { display: none; text-transform: lowercase; + .editing-commands-cancel { float: left; height: 23px; font-size: 11px; padding: 3px; } + .editing-commands-save { float: right; height: 23px; @@ -2121,16 +2469,23 @@ label.required:after { padding: 3px; } } + &.editing { .formatting-tips, .users-typing { display: none; } + .editing-commands { display: block; } + + .stream-info { + display: none; + } } } + .add-user-search { height: 100%; overflow: hidden; @@ -2138,6 +2493,7 @@ label.required:after { vertical-align: top; width: 100%; } + &.admin { .message:hover:not(.system) .message-action { display: inline-block; @@ -2145,27 +2501,6 @@ label.required:after { } } -.message-popup-items { - .loading { - display: none; - } -} - -.message-popup-results { - &.notready { - .message-popup-items { - position: relative; - height: 100px; - .loading { - display: flex; - } - } - .popup-item { - display: none; - } - } -} - .message-popup-position { position: relative; } @@ -2173,21 +2508,23 @@ label.required:after { .message-popup { position: absolute; z-index: 101; - bottom: 0px; - left: 0px; - right: 0px; + bottom: 0; + left: 0; + right: 0; overflow: hidden; - box-shadow: 0 -1px 10px 0 rgba(0, 0, 0, 0.2), 0 1px 1px rgba(0, 0, 0, 0.16); + box-shadow: + 0 -1px 10px 0 rgba(0, 0, 0, 0.2), + 0 1px 1px rgba(0, 0, 0, 0.16); border-radius: 5px; } .message-popup.popup-down { bottom: auto; - top: 0px; + top: 0; } .message-popup-title { - border-width: 0 0 1px 0; + border-width: 0 0 1px; padding: 0 20px; line-height: 32px; font-size: 14px; @@ -2224,20 +2561,32 @@ label.required:after { .messages-box { position: relative; - margin: 60px 20px 0px 0px; + margin: 60px 20px 0 0; overflow: hidden; width: 100%; .calc(height, ~'100% - 120px'); + + .message-cog-container { + .message-action { + &.jump-to-search-message { + display: none !important; + } + } + } + .wrapper.has-more-next { padding-bottom: 24px; } + ul { padding: 21px 0 10px; } + .start { text-align: center; margin-top: 12px; } + .new-message { .unselectable; margin: 0 -65px; @@ -2252,12 +2601,14 @@ label.required:after { bottom: 8px; left: 50%; z-index: 15; - .transition(transform 0.3s ease-out); + transition: transform 0.3s ease-out; .transform(translateY(0)); + &.not { .transform(translateY(150%)); } } + .jump-recent { z-index: 15; position: absolute; @@ -2271,21 +2622,25 @@ label.required:after { right: 20px; border-top-left-radius: 4px; border-top-right-radius: 4px; - button { - .unselectable; - cursor: pointer; - } - .transition(transform 0.3s ease-out); + transition: transform 0.3s ease-out; .transform(translateY(0)); + &.not { .transform(translateY(150%)); } + + button { + .unselectable; + cursor: pointer; + } } + .editing { .body { border-radius: 4px; } } + &.selectable .message { cursor: pointer; } @@ -2298,6 +2653,7 @@ label.required:after { height: 100%; z-index: 10; pointer-events: none; + .tick { height: 2px; width: 100%; @@ -2312,25 +2668,29 @@ label.required:after { position: relative; line-height: 20px; min-height: 40px; + &.highlight { -webkit-animation: highlight 3s; -moz-animation: highlight 3s; -o-animation: highlight 3s; animation: highlight 3s; } + &:nth-child(1) { margin-top: 0; } + .day-divider { height: 50px; display: none; text-align: center; left: 0; position: absolute; - top: 0px; + top: 0; right: 0; align-items: center; justify-content: center; + span { padding: 0 8px; z-index: 1; @@ -2338,7 +2698,8 @@ label.required:after { font-size: 12px; font-weight: 600; } - &:before { + + &::before { position: absolute; content: " "; display: block; @@ -2349,9 +2710,11 @@ label.required:after { right: 0; } } + &.new-day { margin-top: 60px; - &:before { + + &::before { content: attr(data-date); display: block; position: absolute; @@ -2365,46 +2728,55 @@ label.required:after { padding: 0 10px; min-width: 140px; } - &:after { + + &::after { content: " "; display: block; position: absolute; top: -20px; left: 0; - border-width: 1px 0 0 0; + border-width: 1px 0 0; width: 100%; } } + .message-action { display: none; cursor: pointer; } + &:hover:not(.system) .message-action { display: block; } + .message-cog-container { position: relative; display: inline-block; + .message-cog { visibility: hidden; cursor: pointer; } } + @keyframes dropdown-in { 0% { display: none; opacity: 0; } + 1% { display: block; opacity: 0; transform: scale(0); } + 100% { opacity: 1; transform: scale(1); } } + .message-dropdown { position: absolute; top: -5px; @@ -2413,95 +2785,116 @@ label.required:after { display: none; border-radius: 4px; overflow: hidden; - box-shadow: 0px 1px 1px 0 rgba(0, 0, 0, 0.2), 0 2px 10px 0 rgba(0, 0, 0, .16); - transition: transform .15s ease-in-out, opacity .15s ease-in-out; - animation: dropdown-in .15s ease-in-out; + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); + transition: transform 0.15s ease-in-out, opacity 0.15s ease-in-out; + animation: dropdown-in 0.15s ease-in-out; + ul { display: flex; display: -webkit-flex; - padding: 0px; + padding: 0; font-size: 14px; + li { display: block; - padding: 0px 8px; + padding: 0 8px; font-weight: 400; line-height: 26px; cursor: pointer; + &:first-child { padding-left: 6px; border-width: 0 1px 0 0; } + &:last-child { padding-right: 13px; } } } } + .user { display: inline-block; font-weight: 600; margin-right: 5px; } + .thumb { position: absolute; left: 20px; display: block; width: 40px; height: 40px; + &:not(.thumb-small) { .avatar { width: 40px; height: 40px; } } + &.thumb-small { position: initial; width: 20px; height: 20px; display: inline-block; vertical-align: bottom; + .avatar { width: 20px; height: 20px; } } } + .info { font-size: 12px; + .edited { border-left: 1px dotted; padding-left: 3px; margin-left: 3px; } + .is-bot, .role-tag { padding: 1px 4px; border-radius: 2px; } } + .private { margin-left: 10px; } + &.sequential { min-height: 20px; padding-top: 4px; padding-bottom: 4px; - margin-top: 0px; + margin-top: 0; + .user { display: none; } + .thumb:not(.thumb-small) { display: none; } + .info { position: absolute; text-align: right; left: 5px; width: 60px; + .time, .role-tag { display: none; } + .edited { display: block; border-left: 0; @@ -2509,100 +2902,115 @@ label.required:after { padding-left: 0; white-space: nowrap; } + .private { display: none; } + .message-action { float: left; margin-left: 1px; } } + .body { - margin-top: 0px; + margin-top: 0; } - // .message-dropdown { - // top: 100%; - // left: 0; - // } + &:hover { - .time { - // display: inline-block; - } .edited { display: none; } } } + &.system { .body { font-style: italic; + em { font-weight: 600; } + .attachment { font-style: normal; } } } + .avatar-initials { line-height: 40px; } + button { font-weight: 400; + &:hover { text-decoration: underline; } } + .body { opacity: 1; - .transition(opacity 1s linear); + transition: opacity 1s linear; margin-top: 2px; + .inline-image { background-size: contain; background-repeat: no-repeat; background-position: center left; display: inline-block; - line-height: 0px; - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1); + line-height: 0; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); border-radius: 3px; overflow: hidden; + img { max-height: 200px; max-width: 100%; opacity: 0; } } + > h1 { font-size: 3em; line-height: 1em; } + > h2 { font-size: 2.5em; line-height: 1em; } + > h3 { font-size: 2em; line-height: 1em; } + > h4 { font-size: 1.5em; line-height: 1em; } + blockquote.sandstorm-grain { img { width: 50px; } + label { cursor: pointer; } + button { display: block; } } } + &.temp .body { - opacity: .5; + opacity: 0.5; } + .message-alias { font-weight: 400; padding-left: 2px; @@ -2613,6 +3021,7 @@ body:not(.is-cordova) { .message:hover:not(.system) .message-cog { visibility: visible; } + .message { .body, .user.user-card-message, @@ -2621,6 +3030,7 @@ body:not(.is-cordova) { -moz-user-select: text; -ms-user-select: text; user-select: text; + * { -webkit-user-select: text; -moz-user-select: text; @@ -2634,73 +3044,11 @@ body:not(.is-cordova) { .cozy { .message { padding: 4px 20px 4px 70px; - .body { - margin-top: 0; - } - } -} -.compact { - .message { - min-height: 26px; - padding: 5px 15px 0 37px; .body { - display: inline; - .inline-image img { - max-height: 100px; - } - .inline-video { - max-height: 150px; - } - .attachment { - .attachment-title > a { - font-size: 0.9em; - } - .attachment-author img { - border-radius: 2px; - } - } - blockquote iframe { - height: 150px; - width: 266px; - } - } - .info { - .avatar-image { - border-radius: 2px; - } - .role-tag { - display: none; - } - } - .thumb { - height: 20px; - width: 20px; - left: 10px; - .avatar { - width: 20px; - height: 20px; - } - } - &.sequential { - .thumb:not(.thumb-small), - .user { - display: inline-block; - } - .info { - position: relative; - text-align: right; - left: 0; - .time, - .edited { - display: inline-block; - } - } + margin-top: 0; } } - .message-cog-container .message-cog { - visibility: visible; - } } .flex-tab-bar { @@ -2710,13 +3058,17 @@ body:not(.is-cordova) { top: 0; right: 0; z-index: 130; + will-change: transform; + .tab-button { position: relative; cursor: pointer; text-align: center; + button { height: 38px; } + .counter { position: absolute; font-size: 9px; @@ -2729,11 +3081,14 @@ body:not(.is-cordova) { top: 4px; text-align: center; } + &.active { border-right: 3px solid; + button { margin-left: 3px; } + .counter { margin-right: -3px; } @@ -2745,49 +3100,67 @@ body:not(.is-cordova) { .message { min-height: 26px; padding: 5px 15px 0 37px; + .body { display: inline; + .inline-image img { max-height: 100px; } + .inline-video { max-height: 150px; } + .attachment { .attachment-title > a { font-size: 0.9em; } + .attachment-author img { border-radius: 2px; } } + blockquote iframe { height: 150px; width: 266px; } } + .info { .avatar-image { border-radius: 2px; } + .role-tag { display: none; } } + .thumb { height: 20px; width: 20px; left: 10px; + + .avatar { + width: 20px; + height: 20px; + } } + &.sequential { - .thumb:not(.thumb-small), - .user { + .thumb:not(.thumb-small),.user { + + /* stylelint-disable-line */ display: inline-block; } + .info { position: relative; text-align: right; left: 0; + .time, .edited { display: inline-block; @@ -2795,62 +3168,70 @@ body:not(.is-cordova) { } } } + .message-cog-container .message-cog { visibility: visible; } } // FLEX-TAB and FLEX-TAB views + .flex-tab { - overflow-x: visible; position: fixed; - z-index: 110; - max-width: @flex-tab-width; - width: 90%; - bottom: 0; - right: 0; top: 0; - .transform(translateX(100%)); - .transition(transform .25s cubic-bezier(.5, 0, .1, 1)); + bottom: 0; + left: 100%; + width: 90%; + max-width: @flex-tab-width; + z-index: 110; + overflow-x: visible; + transition: transform 0.25s cubic-bezier(0.5, 0, 0.1, 1); + .control { .header { text-align: center; padding: 5px 30px 20px; margin: 5px 0 15px; + h2 { font-size: 20px; line-height: 25px; font-weight: 300; } } + .button { min-height: 36px; margin: 0 1px; } + .more { position: absolute; left: 0; - top: 0px; + top: 0; height: 60px; width: 30px; - border-width: 0 0 1px 0; + border-width: 0 0 1px; cursor: pointer; .transform(translateX(-27px)); - .transition(transform .25s ease-out .475s, background .075s ease-out .5s); + transition: transform 0.25s ease-out 0.475s, background 0.075s ease-out 0.5s; + i { .transform-origin(50%, 50%, 0); - .transition(transform .3s ease-out); + transition: transform 0.3s ease-out; height: 12.5px; vertical-align: top; margin-top: 1px; } + .flex-opened & { - .transition-delay(0s); .transform(translateX(0)); } } + .search-form { width: 100%; + .icon-plus { position: absolute; top: 11px; @@ -2858,12 +3239,14 @@ body:not(.is-cordova) { font-size: 13px; } } + .info-tabs { position: absolute; top: 0; text-align: right; height: 60px; right: 20px; + a { float: left; display: inline-block; @@ -2872,25 +3255,30 @@ body:not(.is-cordova) { line-height: 60px; vertical-align: middle; border-width: 0 0 0 1px; + &:last-child { border-width: 0 1px 0 0; } } } } + .content { &:extend(.fill-all); overflow-x: hidden; overflow-y: auto; top: auto; -webkit-overflow-scrolling: touch; + > div { - .transition(transform .45s cubic-bezier(.5, 0, 0, 1), opacity .125s ease-out .1s); + transition: transform 0.45s cubic-bezier(0.5, 0, 0, 1), opacity 0.125s ease-out 0.1s; } + > .animated-hidden { .transform(translateX(100%)); opacity: 0; } + > .animated { position: absolute; top: 0; @@ -2898,8 +3286,10 @@ body:not(.is-cordova) { width: 100%; height: 100%; } + > .title { height: @header-min-height; + h2 { padding: 0 20px; font-size: 20px; @@ -2908,18 +3298,21 @@ body:not(.is-cordova) { } } } + footer { position: absolute; bottom: 0; left: 0; width: 100%; - padding: 9px 15px 0px 15px; + padding: 9px 15px 0; z-index: 100; text-align: right; height: @footer-min-height; } + .social { text-align: center; + h4 { font-weight: 300; position: absolute; @@ -2928,13 +3321,16 @@ body:not(.is-cordova) { left: 0; font-size: 13px; } + .share { border-radius: 50%; min-height: 40px; line-height: 20px; - &:before { + + &::before { border-radius: 50%; } + span { display: none; } @@ -2948,43 +3344,53 @@ body:not(.is-cordova) { overflow-x: hidden; z-index: 10; padding: 20px; + .list { display: flex; flex-flow: column nowrap; position: relative; width: 100%; + .message { - padding: 8px 0px 4px 50px; + padding: 8px 0 4px 50px; } } + > .title { margin: 0 0 20px; + h2 { font-size: 20px; line-height: 25px; font-weight: 300; } + p { font-size: 12px; margin-top: 4px; } + b { font-weight: 600; } + .see-all { float: right; border-width: 0; text-decoration: underline; cursor: pointer; + &:hover { text-decoration: none; } } } + .show-more-users { - margin: 1em auto 0 auto; + margin: 1em auto 0; display: block; } + &.uploaded-files-list { a { &.file-name { @@ -2992,10 +3398,13 @@ body:not(.is-cordova) { padding: 10px 5px; border-bottom: 1px solid #eaeaea; display: block; + border-width: 0 0 1px; + &:hover { color: #006db0; text-decoration: underline; } + p { overflow: hidden; text-overflow: ellipsis; @@ -3003,18 +3412,16 @@ body:not(.is-cordova) { } } } - a.file-name { - padding: 10px 5px; - border-width: 0 0 1px 0; - display: block; - } + i { float: left; margin-right: 10px; + &.file-delete { float: right; padding-top: 10px; } + &.file-download { float: right; padding-top: 11px; @@ -3027,24 +3434,30 @@ body:not(.is-cordova) { z-index: 15; overflow-y: auto; overflow-x: hidden; + .about { width: 100%; margin-bottom: 20px; } + .thumb { width: 100%; height: 350px; padding: 20px; } + nav { padding: 0 20px; + .back { float: right; } } + .info { white-space: normal; padding: 0 20px; + h3 { -webkit-user-select: text; -moz-user-select: text; @@ -3057,7 +3470,8 @@ body:not(.is-cordova) { width: 100%; overflow: hidden; white-space: nowrap; - i:after { + + i::after { content: " "; display: inline-block; width: 8px; @@ -3066,6 +3480,7 @@ body:not(.is-cordova) { vertical-align: middle; } } + p { -webkit-user-select: text; -moz-user-select: text; @@ -3075,11 +3490,13 @@ body:not(.is-cordova) { font-size: 12px; font-weight: 300; } + .role-tag { padding: 1px 4px; border-radius: 2px; } } + .stats { li { margin-bottom: 3px; @@ -3089,15 +3506,18 @@ body:not(.is-cordova) { border-radius: 2px; } } + .box { position: relative; margin-bottom: 25px; font-size: 13px; + h4 { &:extend(.small-title); margin-bottom: 6px; } - &:after { + + &::after { content: " "; height: 1px; width: 100%; @@ -3106,6 +3526,7 @@ body:not(.is-cordova) { position: absolute; } } + .tags { li { display: inline-block; @@ -3113,11 +3534,13 @@ body:not(.is-cordova) { border-right: 2px; } } + .links { i { margin-right: 5px; font-size: 13px; } + a { white-space: nowrap; max-width: 100%; @@ -3128,8 +3551,9 @@ body:not(.is-cordova) { padding: 0 5px; line-height: 22px; position: relative; - .transition(background .18s ease, colo .18s ease); - &:before { + transition: background 0.18s ease, color 0.18s ease; + + &::before { content: attr(data-stats); position: absolute; right: 5px; @@ -3137,33 +3561,40 @@ body:not(.is-cordova) { font-size: 11px; opacity: 0; } + &:hover { padding-right: 34px; text-decoration: none; - &:before { + + &::before { opacity: 1; } } + span { font-weight: 300; } } } + .contact-code { margin: -5px 0 10px 0; font-size: 12px; } + .channels { h3 { font-size: 24px; margin-bottom: 8px; line-height: 22px; } + p { line-height: 18px; font-size: 12px; font-weight: 300; } + a { white-space: nowrap; max-width: 100%; @@ -3174,8 +3605,9 @@ body:not(.is-cordova) { padding: 0 5px; line-height: 22px; position: relative; - .transition(background .18s ease, colo .18s ease); - &:before { + transition: background 0.18s ease, color 0.18s ease; + + &::before { content: attr(data-stats); position: absolute; right: 5px; @@ -3183,51 +3615,63 @@ body:not(.is-cordova) { font-size: 11px; opacity: 0; } + span { font-weight: 300; } } } + .edit-form { - padding: 20px 20px 0 20px; + padding: 20px 20px 0; white-space: normal; + h3 { font-size: 24px; margin-bottom: 8px; line-height: 22px; } + p { line-height: 18px; font-size: 12px; font-weight: 300; } + > .input-line { margin-top: 20px; + #password { width: 70%; } } + nav { padding: 0; + &.buttons { margin-top: 2em; } } + .form-divisor { text-align: center; margin: 2em 0; height: 9px; + > span { padding: 0 1em; } } } + .room-info-content > div { - margin: 0 0 20px 0; + margin: 0 0 20px; } } @user-image-square: 20px; + .user-image { margin: 4px; height: @user-image-square; @@ -3236,22 +3680,24 @@ body:not(.is-cordova) { font-size: 12px; position: relative; display: inline-table; - .avatar { - &:before { - font-size: 10px; - } - } + &:hover, &.selected { .avatar { - &:after { + &::after { .transform(scaleX(1)) } } } + .avatar { overflow: visible; - &:after { + + &::before { + font-size: 10px; + } + + &::after { content: " "; height: 6px; width: 6px; @@ -3261,30 +3707,37 @@ body:not(.is-cordova) { top: 8px; border-radius: 3px; } + .avatar-initials { line-height: @user-image-square; } } + p { display: none; } + .lines & { width: 100%; margin: 0; - &:after { + + &::after { display: none; } + button { .clearfix; padding: 5px 0; height: 30px; display: block; + > div { float: left; width: @user-image-square; height: @user-image-square; } } + p { float: left; display: block; @@ -3298,6 +3751,7 @@ body:not(.is-cordova) { .calc(width, ~"100% - 45px"); } } + button { display: block; width: 100%; @@ -3308,26 +3762,32 @@ body:not(.is-cordova) { .user-profile { white-space: normal; overflow: hidden; + .thumb { float: left; width: 75px; + img { width: 60px; height: 60px; } } + .info { display: block; margin-left: 75px; + h3 { font-size: 14px; margin-bottom: 8px; font-weight: 600; } + p { font-size: 12px; margin-bottom: 6px; } + a { &:hover { text-decoration: none; @@ -3348,9 +3808,11 @@ body:not(.is-cordova) { height: 100%; z-index: 1000; visibility: hidden; + &.fluid { .modal { height: 100%; + main { position: absolute; overflow-y: scroll; @@ -3358,33 +3820,41 @@ body:not(.is-cordova) { } } } + &.opened { .animation(fadeIn .1s ease-out forwards); + .modal { .animation(modalEnter .35s cubic-bezier(.5, 0, .1, 1) forwards .1s); } } + &.closed { .animation(fadeOut .2s ease-out forwards); + .modal { .animation(modalExit .25s cubic-bezier(.5, 0, .1, 1) forwards); } } + &.overflow { .modal { overflow: visible; + main { overflow: visible; position: relative; } } } + .wrapper { display: table; height: 100%; width: 100%; position: relative; } + .window { display: table-cell; vertical-align: middle; @@ -3393,21 +3863,24 @@ body:not(.is-cordova) { height: 100%; position: relative; } + fieldset { margin-bottom: 8px; } + legend { - position: relative; z-index: 2; display: block; margin-bottom: 18px; position: relative; text-transform: uppercase; font-size: 13px; + i { margin-right: 4px; } - &:before { + + &::before { content: " "; width: 100%; height: 1px; @@ -3417,6 +3890,7 @@ body:not(.is-cordova) { z-index: 1; } } + .modal { display: block; max-width: 800px; @@ -3426,9 +3900,10 @@ body:not(.is-cordova) { overflow: hidden; text-align: left; border-radius: 4px; - box-shadow: 4px 4px 0 rgba(0, 0, 0, .15); + box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.15); padding: 56px 0; opacity: 0; + header { height: 55px; position: absolute; @@ -3439,6 +3914,7 @@ body:not(.is-cordova) { text-align-last: right; padding: 0 25px; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); + h3 { display: inline-block; margin: 0; @@ -3449,6 +3925,7 @@ body:not(.is-cordova) { text-overflow: ellipsis; overflow: hidden; } + .close { position: absolute; width: 20px; @@ -3456,11 +3933,13 @@ body:not(.is-cordova) { right: 20px; top: 16px; opacity: 1; + i { font-size: 24px; } } } + main { display: block; width: 100%; @@ -3469,6 +3948,7 @@ body:not(.is-cordova) { overflow-x: hidden; padding: 20px 25px; } + footer { height: 55px; position: absolute; @@ -3490,6 +3970,7 @@ body:not(.is-cordova) { font-weight: 300; } } + img { width: 200px; } @@ -3507,12 +3988,16 @@ body:not(.is-cordova) { max-width: 520px; padding: 20px; margin: 20px auto; - box-shadow: 0 0 6px 10px rgba(0, 0, 0, 0.1); - border-radius: 2px; + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); + border-radius: 4px; position: relative; z-index: 1; + header { padding: 18px 0 23px; + p { margin: 8px 0 0; font-size: 14px; @@ -3520,43 +4005,44 @@ body:not(.is-cordova) { font-weight: 300; } } + h2 { &:extend(.rocket-h2); line-height: 24px; margin: 0; } + h3 { &:extend(.rocket-h3); } + img { width: 200px; } + a { margin: 4px 0; display: inline-block; } + .options { display: none; width: 100%; font-size: 10px; } - .submit { - margin: 12px 0; - } - .remember { - float: left; - } - .remember input { - margin-right: 4px; - } - .forgot { - float: right; - line-height: 20px; + + .submit, + .register, + .forgot-password, + .back-to-login { + margin-top: 12px; } - .input-text { - margin: 0 0 14px 0; + + .input-line { + margin: 0 0 14px; position: relative; - &:before { + + &::before { content: " "; width: 100%; height: 40px; @@ -3565,49 +4051,41 @@ body:not(.is-cordova) { left: 0; cursor: text; } + &.active { - &:before { + &::before { visibility: hidden; } } + input, select { box-shadow: 0 0 0; position: relative; - margin-top: 10px; padding: 4px 8px; font-size: 18px; - border-width: 0 0 1px 0; + border-width: 0 0 1px; font-weight: 400; - border-radius: 0px; + border-radius: 0; + &:focus { border-color: #13679a !important; } } - .select-arrow { - right: 0px; - } + label { - position: absolute; - top: 20px; - left: 8px; + margin-left: 8px; display: block; - font-size: 18px; + font-size: 12px; text-align: left; color: #a9a9a9; transition: all 0.3s; } - &.focus label { - top: 0; - font-size: 12px; - } - input:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 1000px #f4f4f4 inset; - } + .input-error { text-align: left; color: #b40202; - padding-left: 4px; + padding-left: 8px; font-weight: bold; font-size: 14px; } @@ -3615,26 +4093,17 @@ body:not(.is-cordova) { } .social-login { - text-align: center; - position: relative; - z-index: 1; - display: -webkit-flex; - display: flex; - flex-wrap: wrap; - -webkit-flex-wrap: wrap; margin-bottom: 20px; + h3 { &:extend(.rocket-h3); margin-top: 0; margin-bottom: 12px; } + .button { - border-radius: 2px; - min-height: 40px; - line-height: 20px; + line-height: 22px; font-size: 18px; - margin: 2px; - padding: 0; -webkit-flex-grow: 1; flex-grow: 1; } @@ -3648,6 +4117,7 @@ body:not(.is-cordova) { width: 100%; min-height: 100%; z-index: 101; + .wrapper { text-align: center; z-index: 10; @@ -3655,8 +4125,10 @@ body:not(.is-cordova) { width: 100%; padding: 20px; } + .logo { display: block; + > img { display: inline-block; position: relative; @@ -3668,31 +4140,36 @@ body:not(.is-cordova) { max-width: 100%; } } + a { font-weight: 300; } + .cell { display: table-cell; vertical-align: middle; text-align: center; } + header { display: block; position: relative; z-index: 1; } + .text { font-weight: 300; - margin-bottom: 25px; margin: 0 auto 25px; max-width: 580px; position: relative; z-index: 1; + .button { font-weight: 400; padding: 16px 20px; margin-top: 20px; } + h1 { font-weight: 600; text-transform: uppercase; @@ -3701,12 +4178,15 @@ body:not(.is-cordova) { margin-bottom: 20px; display: none; } + h2 { &:extend(.rocket-h2); } + h3 { &:extend(.rocket-h3); } + p { margin: 18px 0; font-size: 16px; @@ -3714,20 +4194,24 @@ body:not(.is-cordova) { font-weight: 400; } } + footer { padding: 20px 0 0; position: relative; z-index: 1; + h4 { text-transform: uppercase; margin-bottom: 8px; font-size: 12px; font-weight: 300; } + div.switch-language { margin-top: 20px; } } + a.meteor { position: fixed; right: 30px; @@ -3738,13 +4222,16 @@ body:not(.is-cordova) { background-size: 100% auto; text-indent: -9999em; } + .share { border-radius: 50%; min-height: 40px; line-height: 20px; - &:before { + + &::before { border-radius: 50%; } + span { display: none; } @@ -3758,8 +4245,12 @@ body:not(.is-cordova) { .mention-link { border-radius: 4px; - padding: 0px 4px 2px 4px; - font-weight: bold; + + &.mention-link-me, + &.mention-link-all { + font-weight: bold; + padding: 0 4px 2px; + } } .highlight-text { @@ -3773,17 +4264,20 @@ body:not(.is-cordova) { } .avatar-suggestion-item { - margin: 5px 0px; + margin: 5px 0; text-align: left; .flex-center; flex-flow: row nowrap; width: 100%; padding: 12px; border-width: 1px; - .transition(background-color .15s ease-out, border-color .15s ease-out); + border-radius: 4px; + transition: background-color 0.15s ease-out, border-color 0.15s ease-out; + &:first-child { margin-top: 10px; } + .avatar { height: 55px; max-height: 55px; @@ -3796,6 +4290,7 @@ body:not(.is-cordova) { text-align: center; position: relative; } + .question-mark { &::before { position: absolute; @@ -3807,15 +4302,27 @@ body:not(.is-cordova) { line-height: 55px; } } + .action { text-align: right; padding-left: 20px; } + .button { min-width: 120px; cursor: pointer; text-align: center; } + + .input-line { + display: flex; + align-items: center; + } + + #avatarurl { + margin-right: 20px; + } + input[type=file] { position: absolute !important; width: 100%; @@ -3825,42 +4332,32 @@ body:not(.is-cordova) { opacity: 0; z-index: 10000; cursor: pointer; + * { cursor: pointer; } } + .avatar-file-input::-webkit-file-upload-button { visibility: hidden; } } -.page-container table { - margin-bottom: 30px; - width: 100%; - th, - td { - vertical-align: middle; - padding: .6rem .7rem; - text-align: left; - border-width: 0 0 1px 0; - } - th { - white-space: nowrap; - } -} - .statistics-table { margin-bottom: 30px; width: 100%; + th, td { text-align: left; padding: 6px 8px; } + th { text-align: right; width: 30%; } + td { width: 70%; } @@ -3868,9 +4365,11 @@ body:not(.is-cordova) { .rocket-team { display: block; + li { display: inline-block; } + a { display: inline-block; width: 50px; @@ -3894,6 +4393,20 @@ body:not(.is-cordova) { #rocket-chat { .flex-opened { right: 40px; + + .flex-tab { + .transform(translateX(-100%)); + } + } + + &.embedded-view { + .flex-opened { + right: 0; + + .flex-tab { + transform: none; + } + } } } } @@ -3904,66 +4417,37 @@ body:not(.is-cordova) { visibility: visible; display: inline-block; } - .side-nav { - top: 0; - // .transform(translateX(-100%)); - .transition(transform .3s ease-out); - } + .main-content { left: 0; z-index: 120; - &.notransition { - .transition(transform .0s); - } } + .main-content, .flex-tab-bar { - .transition(transform .2s linear); + transition: transform 0.2s linear; + + &.notransition { + transition: transform 0; + } } + .fixed-title h2 { margin-left: 45px; } - .flex-tab { - top: 0; - } + .messages-box { padding: 0 10px; } - &.menu-opened { - .side-nav { - .transform(translateX(0)); - } - .burger { - i { - &:nth-child(1) { - opacity: 1; - width: 10px; - .transform-origin(50%, 50%, 0); - .transform(translateY(3px) rotate(-45deg)); - } - &:nth-child(2) { - //.transform(rotate(180deg)); - } - &:nth-child(3) { - opacity: 1; - width: 10px; - .transform-origin(50%, 50%, 0); - .transform(translateY(-3px) rotate(45deg)); - } - } - } - .main-content, - .flex-tab-bar { - .transform(translateX(@rooms-box-width)); - } - } } + .sweet-alert { h2 { font-size: 20px; line-height: 30px; margin: 10px 0; } + button { margin-top: 6px; padding: 10px 22px; @@ -3991,6 +4475,7 @@ body:not(.is-cordova) { .dropzone-overlay { display: none; } + &.over .dropzone-overlay { position: fixed; top: 0; @@ -3998,12 +4483,11 @@ body:not(.is-cordova) { bottom: 0; right: 0; z-index: 1000000; - display: block; font-size: 42px; display: flex; align-items: center; justify-content: center; - margin: 0 51px 0 12px; + > div { padding: 40px; border-radius: 10px; @@ -4014,24 +4498,20 @@ body:not(.is-cordova) { } } -@media (min-width: 780px) { - .dropzone.over .dropzone-overlay { - margin: 0 51px 0 270px; - } -} - .is-cordova { .flex-tab { .control { padding-left: 50px; } + button.more { width: 60px; .transform(translateX(-57px)); } } + .connection-status > .alert { - border-width: 0 0 1px 0; + border-width: 0 0 1px; } } @@ -4040,28 +4520,26 @@ body:not(.is-cordova) { padding-left: 10px; padding-right: 10px; } - .mobile-message-menu { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } } @media all and(max-height: 480px) { #login-card { padding: 10px; margin: 10px auto; - .input-text { + + .input-line { margin-bottom: 6px; } + .submit { - margin: 0px; + margin: 0; } } + .social-login { margin-bottom: 10px; } + .message-form textarea { max-height: 100px !important; } @@ -4074,6 +4552,13 @@ body:not(.is-cordova) { } .webrtc-video { + &.webrtc-video-overlay, + .main-video, + .state-overlay::before, + .videos .video-item { + color: #ffffff; + } + &.webrtc-video-overlay { position: fixed; left: 0; @@ -4084,20 +4569,24 @@ body:not(.is-cordova) { display: flex; flex-direction: column; justify-content: center; + .main-video { - video { + .webrtc-video-element { max-width: 100%; width: auto; } } } + .main-video { text-align: center; - video { + + .webrtc-video-element { width: 100%; min-height: 299px; } - >div { + + > div { margin-top: -28px; position: relative; line-height: 25px; @@ -4106,10 +4595,12 @@ body:not(.is-cordova) { font-weight: bold; } } + .video-flip { transform: scaleX(-1); filter: FlipH; } + .videos { display: -webkit-flex; display: -moz-flex; @@ -4120,6 +4611,7 @@ body:not(.is-cordova) { justify-content: center; -webkit-justify-content: center; -moz-justify-content: center; + .video-item { position: relative; margin-right: 3px; @@ -4127,6 +4619,7 @@ body:not(.is-cordova) { width: 93px; overflow: hidden; text-align: center; + &.state-overlay::before { content: attr(data-state-text); position: absolute; @@ -4140,21 +4633,24 @@ body:not(.is-cordova) { font-size: 12px; font-weight: bold; } - video { + + .webrtc-video-element { height: 70px; max-width: 100px; } - >div { + + > div { line-height: 16px; font-size: 12px; text-align: center; text-overflow: ellipsis; overflow: hidden; - padding: 0px 2px; + padding: 0 2px; margin-top: -16px; position: relative; font-weight: bold; } + .video-muted-overlay { position: absolute; bottom: 16px; @@ -4177,25 +4673,30 @@ body:not(.is-cordova) { } } +.webrtc-video-element { + background-color: #000000; +} + .alert-icon { font-size: 80px; display: block; margin-bottom: 20px; } -.minicolors-theme-rocketchat { - .minicolors-swatch { - height: 33px; - width: 33px; - top: 1px; - left: 1px; - border-radius: 5px 0 0 5px; - overflow: hidden; - border-width: 0 1px 0 0; - } - input { - text-indent: 34px; - } +.colorpicker-input { + text-indent: 34px; +} + +.colorpicker-swatch { + height: 33px; + width: 33px; + top: 1px; + left: 1px; + border-radius: 5px 0 0 5px; + overflow: hidden; + border-width: 0 1px 0 0; + display: block; + position: absolute; } .inline-video { @@ -4208,51 +4709,43 @@ body:not(.is-cordova) { .attention-message { padding-top: 50px; font-size: 24px; + i { display: block; margin-bottom: 20px; font-size: 40px; } + span { display: block; } } .load-more { - text-transform: lowercase; - text-align: center; - line-height: 40px; - font-style: italic; + position: relative; + padding: 1rem 0; } .search-messages-list { .message-cog-container { .message-action { display: none !important; + &.jump-to-star-message { display: block !important; } } } + .no-results { text-align: center; } } -.messages-box { - .message-cog-container { - .message-action { - &.jump-to-search-message { - display: none !important; - } - } - } -} - .terminal { position: absolute; - top: 0px; - bottom: 0px; + top: 0; + bottom: 0; margin: 0; right: 0; left: 0; @@ -4261,20 +4754,23 @@ body:not(.is-cordova) { padding: 8px 10px !important; font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-weight: 500; - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; - margin-bottom: 0px !important; - * { - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; - } - > div { - word-break: break-all; - } + margin-bottom: 0 !important; + background-color: #444444 !important; + color: #ffffff; +} + +.terminal-line { + word-break: break-all; +} + +.terminal-time { + color: #7f7f7f; +} + +.code-colors { + background-color: #f8f8f8; + border-color: #cccccc; + color: #333333; } .new-logs { @@ -4289,8 +4785,9 @@ body:not(.is-cordova) { cursor: pointer; bottom: 8px; left: 50%; - .transition(transform 0.3s ease-out); + transition: transform 0.3s ease-out; .transform(translateY(0)); + &.not { .transform(translateY(150%)); } @@ -4307,7 +4804,8 @@ body:not(.is-cordova) { font-weight: bold; margin-top: 5px; } - pre { + + .script-error { font-size: 12px; font-weight: bold; padding: 6px; @@ -4319,16 +4817,21 @@ body:not(.is-cordova) { .title { display: none; } + .button-fullscreen { display: initial; } + .button-restore { display: none; } + .buttons { text-align: right; - border-width: 0px 1px 1px 1px; + border-width: 0 1px 1px; + background-color: #f7f7f7; } + &.code-mirror-box-fullscreen { height: auto; position: fixed; @@ -4341,25 +4844,31 @@ body:not(.is-cordova) { display: flex; flex-direction: column; align-items: stretch; + .buttons { border-width: 0; } + .title { display: initial; padding-left: 10px; font-size: 16px; line-height: 30px; } + .button-fullscreen { display: none; } + .button-restore { display: initial; } + .CodeMirror { flex-grow: 1; display: flex; flex-direction: column; + .CodeMirror-scroll { flex-grow: 1; } @@ -4377,18 +4886,22 @@ body:not(.is-cordova) { left: 0; } } + .container-bars { .unread-bar { .unread-count { display: inline-block; } + .unread-count-since { display: none; } + > button.jump-to { .jump-to-small { display: inline-block; } + .jump-to-large { display: none; } @@ -4408,7 +4921,7 @@ body:not(.is-cordova) { position: absolute; right: -8px; top: -5px; - opacity: .6; + opacity: 0.6; } .collapse-switch { @@ -4416,6 +4929,7 @@ body:not(.is-cordova) { } // kinda hacky, needed in oembedFrageWidget.html + br.only-after-a { display: none; } @@ -4435,9 +4949,11 @@ a + br.only-after-a { .hide-avatars { .message { padding-left: 20px; + .thumb.user-card-message:not(.thumb-small) { display: none; } + .user.user-card-message { margin-left: -5px; } @@ -4452,15 +4968,19 @@ a + br.only-after-a { position: static; width: auto; float: right; + .message-cog-container { float: left; } + .message-dropdown { left: auto; right: -2px; + ul { flex-direction: row-reverse; - li:first-child i:before { + + li:first-child i::before { content: "\d7"; } } @@ -4478,10 +4998,12 @@ a + br.only-after-a { width: auto; vertical-align: middle; } + label { display: inline-block; max-width: 100%; } + .form-group { display: inline-block; } @@ -4491,20 +5013,30 @@ a + br.only-after-a { .main-content.main-modal { right: 0; } + .messages-container { border-width: 0; + .messages-box { margin-top: 0; } + .footer { min-height: 36px; padding: 0; + + .message-form { + margin-bottom: 0; + } + .message-input { border-width: 0; } + .stream-info { display: none; } + .formatting-tips { display: none; } @@ -4512,5 +5044,12 @@ a + br.only-after-a { } } -#swipebox-slider .slide .swipebox-inline-container, #swipebox-slider .slide .swipebox-video-container, #swipebox-slider .slide img {padding: 40px;} -#swipebox-overlay {background: rgba(13, 13, 13, 0.5);} +#swipebox-slider .slide .swipebox-inline-container, +#swipebox-slider .slide .swipebox-video-container, +#swipebox-slider .slide img { + padding: 40px; +} + +#swipebox-overlay { + background: rgba(13, 13, 13, 0.5); +} diff --git a/packages/rocketchat-theme/client/imports/forms.less b/packages/rocketchat-theme/client/imports/forms.less new file mode 100644 index 0000000000000000000000000000000000000000..92f187a4dd78496bc97af02ae3c5c93aa5927cb8 --- /dev/null +++ b/packages/rocketchat-theme/client/imports/forms.less @@ -0,0 +1,104 @@ +.input { + &.radio { + min-height: 13px; + position: relative; + + input { + position: absolute; + top: 0; + left: 0; + opacity: 0; + outline: 0; + z-index: -1; + width: 0; + height: 0; + + &:checked + label::after { + opacity: 1; + } + } + + label { + cursor: pointer; + user-select: none; + padding-left: 20px; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 15px; + height: 15px; + border-width: 1px; + border-radius: 50%; + } + + &::after { + content: ''; + position: absolute; + top: 4px; + left: 4px; + width: 7px; + height: 7px; + border-radius: 50%; + opacity: 0; + transition: opacity 0.2s ease-out; + } + } + } + + &.checkbox.toggle { + min-height: 20px; + position: relative; + + input { + position: absolute; + top: 0; + left: 0; + opacity: 0; + outline: 0; + z-index: -1; + width: 0; + height: 0; + + &:checked + label::after { + left: 25px; + } + } + + label { + cursor: pointer; + user-select: none; + display: block; + min-height: 20px; + vertical-align: top; + + &::before { + display: block; + position: absolute; + content: ''; + z-index: 0; + top: 0; + left: 0; + box-shadow: none; + width: 40px; + height: 16px; + border-radius: 50px; + transition: background-color 0.2s ease-out; + } + + &::after { + content: ''; + position: absolute; + left: 1px; + top: 1px; + border-radius: 50%; + width: 14px; + height: 14px; + z-index: 1; + transition: left 0.2s ease-out; + } + } + } +} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_keyframes.import.less b/packages/rocketchat-theme/client/imports/keyframes.less similarity index 98% rename from packages/rocketchat-theme/assets/stylesheets/utils/_keyframes.import.less rename to packages/rocketchat-theme/client/imports/keyframes.less index a1ef83ee5b6fda55fdbcbe762c0a6b300b9522ad..8aa3df96f1da34e7f83d2bb7a7cc7c92b7a75e19 100644 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_keyframes.import.less +++ b/packages/rocketchat-theme/client/imports/keyframes.less @@ -1,13 +1,16 @@ // keyframes + @-webkit-keyframes fadeIn { 0% { opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; } + 100% { opacity: 1; visibility: visible; @@ -19,10 +22,12 @@ opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; } + 100% { opacity: 1; visibility: visible; @@ -34,10 +39,12 @@ opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; } + 100% { opacity: 0; visibility: hidden; @@ -49,10 +56,12 @@ opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; } + 100% { opacity: 0; visibility: hidden; @@ -63,6 +72,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -72,6 +82,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -81,6 +92,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -90,6 +102,7 @@ 0% { background: #ffff99; } + 100% { background: none; } @@ -100,11 +113,13 @@ opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; .transform(translateY(-150px)); } + 100% { opacity: 1; visibility: visible; @@ -117,11 +132,13 @@ opacity: 0; visibility: hidden; } + 1% { opacity: 0; visibility: visible; -webkit-transform: translateY(-150px); } + 100% { opacity: 1; visibility: visible; @@ -134,11 +151,13 @@ opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; .transform(translateY(150px)); } + 100% { opacity: 0; visibility: hidden; @@ -150,11 +169,13 @@ opacity: 1; visibility: visible; } + 99% { opacity: 0; visibility: visible; -webkit-transform: translateY(150px); } + 100% { opacity: 0; visibility: hidden; diff --git a/packages/rocketchat-theme/client/imports/lesshat.less b/packages/rocketchat-theme/client/imports/lesshat.less new file mode 100644 index 0000000000000000000000000000000000000000..0979dd5275e4c8ca63c69433f10d0dcaa5aac34f --- /dev/null +++ b/packages/rocketchat-theme/client/imports/lesshat.less @@ -0,0 +1,85 @@ +// lesshat - The best mixin library in the world +// +// version: v4.1.0 (2016-07-19) + +.animation(...) { + @process: ~`(function(e){return e=e||"none",/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-animation: @process; + -moz-animation: @process; + -o-animation: @process; + animation: @process; +} + +.box-sizing(...) { + @process: ~`(function(n){return n=n||"content-box"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-box-sizing: @process; + -moz-box-sizing: @process; + box-sizing: @process; +} + +.calc(...) { + @process: ~`(function(a){function c(c,t){var r=");\n",s=e.split(","),l=s[0]+":"+c+"("+(s[1].trim()||0)+r;"start"==t?a="0;\n"+l:a+=l}a=a||8121991;var t="@{state}",e=a;if(8121991==a)return a;switch(t){case"1":c("-webkit-calc","start"),c("-moz-calc"),c("calc");break;case"2":c("-webkit-calc","start"),c("-moz-calc");break;case"3":c("-webkit-calc","start"),c("calc");break;case"4":c("-webkit-calc","start");break;case"5":c("-moz-calc","start"),c("calc");break;case"6":c("-moz-calc","start");break;case"7":c("calc","start")}return a=a.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + @state: 1; -lh-property: @process; +} + +.transform(...) { + @process: ~`(function(e){e=e||"none";var r={translate:"px",rotate:"deg",rotate3d:"deg",skew:"deg"};/^\w*\(?[a-z0-9.]*\)?/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,""));for(var t in r)e.indexOf(t)>=0&&(e=e.replace(new RegExp(t+"[\\w]?\\([a-z0-9, %]*\\)"),function(e){var n=/(\d+\.?\d*)(?!\w|%)/g;return"rotate3d"==t&&(n=/,\s*\d+$/),e.replace(n,function(e){return e+r[t]})}));return e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: @process; + -moz-transform: @process; + -ms-transform: @process; + -o-transform: @process; + transform: @process; +} + +.transform-origin(...) { + @process: ~`(function(e){e=e||"50% 50% 0";var t=/\d/gi,r=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return/^[^, ]*,/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,"")),t.test(e)&&(e=e.replace(r,function(e){return 0==e&&e||e+"%"})),e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform-origin: @process; + -moz-transform-origin: @process; + -ms-transform-origin: @process; + -o-transform-origin: @process; + transform-origin: @process; +} + +.transform-style(...) { + @process: ~`(function(n){return n=n||"flat"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform-style: @process; + -moz-transform-style: @process; + -ms-transform-style: @process; + -o-transform-style: @process; + transform-style: @process; +} + +.translate(...) { + @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: translate(@process); + -moz-transform: translate(@process); + -ms-transform: translate(@process); + -o-transform: translate(@process); + transform: translate(@process); +} + +.translateX(...) { + @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: translateX(@process); + -moz-transform: translateX(@process); + -ms-transform: translateX(@process); + -o-transform: translateX(@process); + transform: translateX(@process); +} + +.translateY(...) { + @process: ~`(function(n){n=n||"0";var r=/\d/gi,t=/(?:\s|^)(\.?\d+\.?\d*)(?![^(]*\)|\w|%|\.)/gi;return r.test(n)&&(n=n.replace(t,function(n){return 0==n&&n||n+"px"})),n})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: translateY(@process); + -moz-transform: translateY(@process); + -ms-transform: translateY(@process); + -o-transform: translateY(@process); + transform: translateY(@process); +} + +.user-select(...) { + @process: ~`(function(n){return n=n||"auto"})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-user-select: @process; + -moz-user-select: @process; + -ms-user-select: @process; + user-select: @process; +} diff --git a/packages/rocketchat-theme/assets/stylesheets/utils/_reset.import.less b/packages/rocketchat-theme/client/imports/reset.less similarity index 94% rename from packages/rocketchat-theme/assets/stylesheets/utils/_reset.import.less rename to packages/rocketchat-theme/client/imports/reset.less index 1da139742a577a2d2b1051dd1de6867612937776..274ac1571a3ff7801ee7bd375fc978b693b3e265 100644 --- a/packages/rocketchat-theme/assets/stylesheets/utils/_reset.import.less +++ b/packages/rocketchat-theme/client/imports/reset.less @@ -96,7 +96,6 @@ video { } } - /* HTML5 display-role reset for older browsers */ article, @@ -127,10 +126,10 @@ q { quotes: none; } -blockquote:before, -blockquote:after, -q:before, -q:after { +blockquote::before, +blockquote::after, +q::before, +q::after { content: ''; content: none; } diff --git a/packages/rocketchat-theme/assets/stylesheets/rtl.less b/packages/rocketchat-theme/client/imports/rtl.less similarity index 84% rename from packages/rocketchat-theme/assets/stylesheets/rtl.less rename to packages/rocketchat-theme/client/imports/rtl.less index f08412ad0ee937fd9aaccbf660050eb44796bf1a..123b9e68f4f78d2324e9d5e404e5df4e5226a1fd 100644 --- a/packages/rocketchat-theme/assets/stylesheets/rtl.less +++ b/packages/rocketchat-theme/client/imports/rtl.less @@ -1,144 +1,186 @@ .rtl { direction: rtl; + // Mix-ins .right(@right) { right: @right; left: auto; } - .left (@left) { + + .left(@left) { left: @left; right: auto; } + .margin-right(@margin-right) { margin-right: @margin-right; margin-left: auto; } + .margin-left(@margin-left) { margin-left: @margin-left; margin-right: auto; } + .padding-right(@padding-right) { padding-right: @padding-right; - padding-left: 0px; + padding-left: 0; } + .padding-left(@padding-left) { padding-left: @padding-left; - padding-right: 0px; + padding-right: 0; } + button { text-align: right; } + .text-right { text-align: left; } + .side-nav { - .right(0px); + .right(0); + .header { - .right(0px); + .right(0); + .account-box { .info { - padding: 10px 18px 10px 0px; + padding: 10px 18px 10px 0; + .thumb { float: right; } - .thumb:after { + + .thumb::after { .right(-14px); } + .data { float: right; padding: 0 10px 0 25px; + .wrp { text-align: right; } } } + .options { - .right(0px); + .right(0); + .status { .padding-right(38px); } + span.soon { .left(-30px); } - .status:after { + + .status::after { .right(18px); } } + .options._hidden { transform: translateX(100%); } } } + > .arrow { .left(8px); } + .arrow { transform: rotateY(180deg); } + .footer { - .right(0px); + .right(0); } + .rooms-list { direction: ltr; + > .wrapper { direction: rtl; .padding-right(8px); + h3.add-room { i { .left(6px); } } + h3 { .padding-right(10px); } + ul a { padding: 6px 6px 7px 25px; } } } + .more { - padding: 4px 10px 4px 0px; + padding: 4px 10px 4px 0; } + .empty { .padding-right(10px); } + ul .opt { - .left(0px); + .left(0); .padding-left(10px); text-align: left; } + .flex-nav { - .right(0px); + .right(0); + > section { - .right(0px); + .right(0); } + header { - .right(0px); + .right(0); .padding-right(15px); + > div { text-align: right; } } + .content { > .wrapper { direction: rtl; } } + .input-submit { - margin: 35px -4px 0 0px; + margin: 35px -4px 0 0; } - .button:before { - .right(0px); + + .button::before { + .right(0); } + footer { - .right(0px); + .right(0); text-align: right; + > div { text-align: right; } } + &.animated-hidden { .transform(translateX(100%)); + header, footer, .content { @@ -146,47 +188,55 @@ } } } + .search-form { .search { .padding-right(25px); } + .margin-left(20px); } + h3 { .padding-right(10px); + &.add-room { i { .left(6px); } } } + .unread { .left(6px); } } - .side-nav:before { + + .side-nav::before { .right(8px); } + .main-content { - left: 0; + left: 40px; right: @rooms-box-width; - .margin-left(40px); + transition: left 0.25s cubic-bezier(0.5, 0, 0.1, 1); + &.flex-opened { - left: @flex-tab-width; - .flex-tab { - .left(40px); - } + left: @flex-tab-width + 40px; } } + .page-settings { .content { > .info { padding-left: 20px; } } + .section { border-right: none; - border-left: 1px solid #ddd; + border-left: 1px solid #dddddd; + .section-content { .input-line { > label { @@ -196,46 +246,58 @@ } } } + .messages-box { - margin: 60px 0px 0px 20px; + margin: 60px 0 0 20px; + .new-message { .right(50%); } } + .terminal { direction: ltr; } + .container-bars { .upload-progress { .upload-progress-progress { - .right(0px); + .right(0); } + .upload-progress-text { > a { float: left; } } } + .unread-bar { > a.mark-read { float: left; } + > a.jump-to { float: right; } } } + .messages-container { .right(0); + .edit-room-title { .margin-right(4px); } + .wrapper { .right(0); } + .footer { .right(0); } + .message-form { > div { .input-message-container { @@ -244,6 +306,7 @@ } } } + textarea { padding-right: 49px; padding-left: 8px; @@ -251,35 +314,41 @@ border-width: 0 0 0 1px; border-right-width: 0; } + > .stream-info { float: right; } + > .formatting-tips { float: left; position: relative; right: auto; + q { padding: 0 3px 0 0; border-right: 3px solid; - border-left: 0px none; - border-right-color: #CCC; + border-left: 0 none; + // border-right-color: #cccccc; } } } } + .account-box { .options { direction: ltr; + > .wrapper { direction: rtl; } } } + .flex-tab-bar { .left(0); border-right: 1px solid #eaeaea; border-left: none; - z-index: 13; + .tab-button { &.active { .margin-right(-1px); @@ -288,36 +357,43 @@ } } } + .flex-tab { + .right(100%); border-left: unset; border-width: 0 1px 0 0; - border-right-color: @tertiary-background-color; - .left(0); - .transform(translateX(-100%)); + // border-right-color: @tertiary-background-color; + .control { text-align: right; - padding: 12px 30px 12px; + padding: 12px 30px; + > a, > form { float: right; } + .more { .right(0); .transform(translateX(27px)); } + .search-form { padding: 0 0 0 4px; width: 100%; + .icon-plus { .right(4px); } } + .info-tabs { text-align: left; .left(20px); } } } + .flex-opened { .flex-tab { .control { @@ -327,26 +403,31 @@ } } } + .input-line { &.search { .icon-spin { .left(5px); } + .icon-search, .icon-right-open-small { .right(2px); } + input { padding-right: 20px; padding-left: 8px; text-align: right; } } + > div { .right { .left(10px); } } + &.double-col { > label { float: right; @@ -354,13 +435,17 @@ text-align: left; padding: 10px 0 10px 20px; } + > div { float: right; + label { .margin-left(4px); + &:nth-last-child(1) { .margin-left(0); } + input { .margin-left(4px); } @@ -368,31 +453,37 @@ } } } + .user-image { .avatar { - &:after { + &::after { .right(-12px); } } } + .lines .user-image { button { > div { float: right; } } + p { float: right; .padding-right(10px); } } + .user-view { nav { .margin-right(-4px); + .back { float: left; } } + .stats { li { border-right: unset; @@ -400,103 +491,124 @@ } } } + @media all and(max-width: 1100px) { #rocket-chat { .flex-opened { left: 0; right: @rooms-box-width; + + .flex-tab { + .transform(translateX(calc(~'100% + 40px'))); + } } } } + @media all and(max-width: 780px) { #rocket-chat { .main-content { right: 0; } + .fixed-title h2 { margin-right: 45px; } - &.menu-opened { - .burger { - i { - &:nth-child(1) { - .transform(translateY(3px) rotate(45deg)); - } - &:nth-child(3) { - .transform(translateY(-3px) rotate(-45deg)); - } - } - } - .main-content { - .transform(translateX(-@rooms-box-width)); - } - } } } + .burger { .margin-right(7px); - .right(0px); + .right(0); + .unread-burger-alert { .left(4px); } + + &.menu-opened { + i { + &:nth-child(1) { + .transform(translate(25%, 3px) rotate(45deg) scale(0.5, 1)); + } + + &:nth-child(3) { + .transform(translate(25%, -3px) rotate(-45deg) scale(0.5, 1)); + } + } + } } + .arrow { - &:before, - &:after { + &::before, + &::after { .calc(right, ~"50% - 5px"); } - &:before { + + &::before { .transform(rotate(135deg) translateX(4px)); } - &:after { + + &::after { .transform(rotate(-135deg) translateX(4px)); } + &.left { - &:before { + &::before { .transform(rotate(-45deg) translateY(-4px)); } - &:after { + + &::after { .transform(rotate(45deg) translateY(4px)); } } + &.top { - &:before { + &::before { .transform(rotate(45deg) translateX(-2px) translateY(2px)); } - &:after { + + &::after { .transform(rotate(-45deg) translateX(2px) translateY(2px)); } } + &.bottom { - &:before { + &::before { .transform(rotate(-45deg) translateX(-2px) translateY(-2px)); } - &:after { + + &::after { .transform(rotate(45deg) translateX(2px) translateY(-2px)); } } + &.close { - &:before { + &::before { .transform(rotate(-45deg)); } - &:after { + + &::after { .transform(rotate(45deg)); } } } + .message { padding-left: 20px; padding-right: 70px; + .message-dropdown { .right(-2px); + ul { li { &:first-child { padding-right: 6px; padding-left: 8px; - border-left: 1px solid #eee; + border-left: 1px solid #eeeeee; border-right: unset; } + &:last-child { padding-left: 13px; padding-right: 8px; @@ -504,81 +616,102 @@ } } } + .user { margin-left: 5px; margin-right: 0; } + .thumb { .right(20px); } + .info { .edited { - border-right: 1px dotted #BAB8B8; + border-right: 1px dotted #bab8d8; border-left: unset; .padding-right(3px); .margin-right(3px); } } + .private { - .margin-right(10px); // + .margin-right(10px); + // } + &.sequential { padding-top: 4px; + .info { text-align: left; .right(5px); + .edited { border-right: 0; .margin-right(0); .padding-right(0); } + .message-action { - float: right; // - .margin-right(1px); // + float: right; + // + .margin-right(1px); + // } } } } + blockquote { .padding-right(10px); - &:before { - .right(0px); + + &::before { + .right(0); } } + .first-unread { .body { &::before { right: 20px; - left: 0px; + left: 0; } + &::after { - .left(0px); + .left(0); } } } + .ticks-bar { .left(2px); } + .fixed-title { padding: 0 20px 0 10px; .right(0); } + .list-view { > .title { .see-all { float: left; } } + &.uploaded-files-list { a { direction: ltr; } + i { float: right; .margin-left(10px); } } } + .page-list { .list { a { @@ -588,10 +721,12 @@ } } } + .user-image { float: left; .margin-right(12px); } + table { thead { th { @@ -601,12 +736,14 @@ } } } + .statistics-table { th, td { text-align: right; } } + .minicolors-theme-rocketchat { .minicolors-swatch { .right(1px); @@ -614,14 +751,18 @@ border-width: 0 0 0 1px; } } + .code-mirror-box { direction: ltr; + .buttons { text-align: left; } + &.code-mirror-box-fullscreen { left: 40px; right: 260px; + .title { padding-right: 10px; padding-left: unset; @@ -629,6 +770,7 @@ } } } + @media all and (max-width: 780px) { .code-mirror-box { &.code-mirror-box-fullscreen { @@ -636,40 +778,49 @@ } } } + .rocket-form { legend { - &:after { + &::after { right: 0; } } + .logoutOthers { text-align: left; } + .submit { text-align: left; } } + /* Overridding classes from swipebox.min.css */ #swipebox-close { .left(0); } - @media screen and (min-width:800px) { + + @media screen and (min-width: 800px) { #swipebox-close { .left(10px); } } + #swipebox-overlay { direction: ltr; } + /* Override toastr messages to show on hte left side */ .toast-top-right { .left(12px); } + .spotlight { > .spotlight-input { .icon-search { left: 0; } + .message-popup { .popup-item { span { diff --git a/packages/rocketchat-theme/assets/stylesheets/global/_variables.less b/packages/rocketchat-theme/client/imports/variables.less similarity index 100% rename from packages/rocketchat-theme/assets/stylesheets/global/_variables.less rename to packages/rocketchat-theme/client/imports/variables.less diff --git a/packages/rocketchat-theme/client/main.less b/packages/rocketchat-theme/client/main.less new file mode 100644 index 0000000000000000000000000000000000000000..75a37e24dc40e5a209446ee79cf97bcac87932a7 --- /dev/null +++ b/packages/rocketchat-theme/client/main.less @@ -0,0 +1,7 @@ +@import "imports/variables.less"; +@import "imports/reset.less"; +@import "imports/lesshat.less"; +@import "imports/keyframes.less"; +@import "imports/rtl.less"; +@import "imports/forms.less"; +@import "imports/base.less"; diff --git a/packages/rocketchat-theme/client/minicolors/jquery.minicolors.css b/packages/rocketchat-theme/client/minicolors/jquery.minicolors.css deleted file mode 100644 index 47dffa5ecfa603362a4a37a14a8a51155ff7a36c..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/client/minicolors/jquery.minicolors.css +++ /dev/null @@ -1,278 +0,0 @@ -.minicolors { - position: relative; -} - -.minicolors-sprite { - background-image: url(data:image/png;base64,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); -} - -.minicolors-no-data-uris .minicolors-sprite { - background-image: url(jquery.minicolors.png); -} - -.minicolors-swatch { - position: absolute; - vertical-align: middle; - background-position: -80px 0; - border: solid 1px #ccc; - cursor: text; - padding: 0; - margin: 0; - display: inline-block; -} - -.minicolors-swatch-color { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.minicolors input[type=hidden] + .minicolors-swatch { - width: 28px; - position: static; - cursor: pointer; -} - -.minicolors input[type=hidden][disabled] + .minicolors-swatch { - cursor: default; -} - -/* Panel */ -.minicolors-panel { - position: absolute; - width: 173px; - height: 152px; - background: white; - border: solid 1px #CCC; - box-shadow: 0 0 20px rgba(0, 0, 0, .2); - z-index: 99999; - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; - box-sizing: content-box; - display: none; -} - -.minicolors-panel.minicolors-visible { - display: block; -} - -/* Panel positioning */ -.minicolors-position-top .minicolors-panel { - top: -154px; -} - -.minicolors-position-right .minicolors-panel { - right: 0; -} - -.minicolors-position-bottom .minicolors-panel { - top: auto; -} - -.minicolors-position-left .minicolors-panel { - left: 0; -} - -.minicolors-with-opacity .minicolors-panel { - width: 194px; -} - -.minicolors .minicolors-grid { - position: absolute; - top: 1px; - left: 1px; - width: 150px; - height: 150px; - background-position: -120px 0; - cursor: crosshair; -} - -.minicolors .minicolors-grid-inner { - position: absolute; - top: 0; - left: 0; - width: 150px; - height: 150px; -} - -.minicolors-slider-saturation .minicolors-grid { - background-position: -420px 0; -} - -.minicolors-slider-saturation .minicolors-grid-inner { - background-position: -270px 0; - background-image: inherit; -} - -.minicolors-slider-brightness .minicolors-grid { - background-position: -570px 0; -} - -.minicolors-slider-brightness .minicolors-grid-inner { - background-color: black; -} - -.minicolors-slider-wheel .minicolors-grid { - background-position: -720px 0; -} - -.minicolors-slider, -.minicolors-opacity-slider { - position: absolute; - top: 1px; - left: 152px; - width: 20px; - height: 150px; - background-color: white; - background-position: 0 0; - cursor: row-resize; -} - -.minicolors-slider-saturation .minicolors-slider { - background-position: -60px 0; -} - -.minicolors-slider-brightness .minicolors-slider { - background-position: -20px 0; -} - -.minicolors-slider-wheel .minicolors-slider { - background-position: -20px 0; -} - -.minicolors-opacity-slider { - left: 173px; - background-position: -40px 0; - display: none; -} - -.minicolors-with-opacity .minicolors-opacity-slider { - display: block; -} - -/* Pickers */ -.minicolors-grid .minicolors-picker { - position: absolute; - top: 70px; - left: 70px; - width: 12px; - height: 12px; - border: solid 1px black; - border-radius: 10px; - margin-top: -6px; - margin-left: -6px; - background: none; -} - -.minicolors-grid .minicolors-picker > div { - position: absolute; - top: 0; - left: 0; - width: 8px; - height: 8px; - border-radius: 8px; - border: solid 2px white; - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; - box-sizing: content-box; -} - -.minicolors-picker { - position: absolute; - top: 0; - left: 0; - width: 18px; - height: 2px; - background: white; - border: solid 1px black; - margin-top: -2px; - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; - box-sizing: content-box; -} - -/* Inline controls */ -.minicolors-inline { - display: inline-block; -} - -.minicolors-inline .minicolors-input { - display: none !important; -} - -.minicolors-inline .minicolors-panel { - position: relative; - top: auto; - left: auto; - box-shadow: none; - z-index: auto; - display: inline-block; -} - -/* Default theme */ -.minicolors-theme-default .minicolors-swatch { - top: 5px; - left: 5px; - width: 18px; - height: 18px; -} -.minicolors-theme-default.minicolors-position-right .minicolors-swatch { - left: auto; - right: 5px; -} -.minicolors-theme-default.minicolors { - width: auto; - display: inline-block; -} -.minicolors-theme-default .minicolors-input { - height: 20px; - width: auto; - display: inline-block; - padding-left: 26px; -} -.minicolors-theme-default.minicolors-position-right .minicolors-input { - padding-right: 26px; - padding-left: inherit; -} - -/* Bootstrap theme */ -.minicolors-theme-bootstrap .minicolors-swatch { - z-index: 2; - top: 3px; - left: 3px; - width: 28px; - height: 28px; - border-radius: 3px; -} -.minicolors-theme-bootstrap .minicolors-swatch-color { - border-radius: inherit; -} -.minicolors-theme-bootstrap.minicolors-position-right .minicolors-swatch { - left: auto; - right: 3px; -} -.minicolors-theme-bootstrap .minicolors-input { - float: none; - padding-left: 44px; -} -.minicolors-theme-bootstrap.minicolors-position-right .minicolors-input { - padding-right: 44px; - padding-left: 12px; -} -.minicolors-theme-bootstrap .minicolors-input.input-lg + .minicolors-swatch { - top: 4px; - left: 4px; - width: 37px; - height: 37px; - border-radius: 5px; -} -.minicolors-theme-bootstrap .minicolors-input.input-sm + .minicolors-swatch { - width: 24px; - height: 24px; -} -.input-group .minicolors-theme-bootstrap:not(:first-child) .minicolors-input { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} diff --git a/packages/rocketchat-theme/client/minicolors/jquery.minicolors.js b/packages/rocketchat-theme/client/minicolors/jquery.minicolors.js deleted file mode 100644 index 671eb78e2e8f8b695956e5af2cdb1ca1a9bd9d44..0000000000000000000000000000000000000000 --- a/packages/rocketchat-theme/client/minicolors/jquery.minicolors.js +++ /dev/null @@ -1,1019 +0,0 @@ -/* - * jQuery MiniColors: A tiny color picker built on jQuery - * - * Copyright: Cory LaViska for A Beautiful Site, LLC: http://www.abeautifulsite.net/ - * - * Contribute: https://github.com/claviska/jquery-minicolors - * - * @license: http://opensource.org/licenses/MIT - * - */ -(function (factory) { - /* jshint ignore:start */ - // Browser globals - factory(jQuery); - /* jshint ignore:end */ -}(function ($) { - - // Defaults - $.minicolors = { - defaults: { - animationSpeed: 50, - animationEasing: 'swing', - change: null, - changeDelay: 0, - control: 'hue', - dataUris: true, - defaultValue: '', - format: 'hex', - hide: null, - hideSpeed: 100, - inline: false, - keywords: '', - letterCase: 'lowercase', - opacity: false, - position: 'bottom left', - show: null, - showSpeed: 100, - theme: 'default' - } - }; - - // Public methods - $.extend($.fn, { - minicolors: function(method, data) { - - switch(method) { - - // Destroy the control - case 'destroy': - $(this).each( function() { - destroy($(this)); - }); - return $(this); - - // Hide the color picker - case 'hide': - hide(); - return $(this); - - // Get/set opacity - case 'opacity': - // Getter - if( data === undefined ) { - // Getter - return $(this).attr('data-opacity'); - } else { - // Setter - $(this).each( function() { - updateFromInput($(this).attr('data-opacity', data)); - }); - } - return $(this); - - // Get an RGB(A) object based on the current color/opacity - case 'rgbObject': - return rgbObject($(this), method === 'rgbaObject'); - - // Get an RGB(A) string based on the current color/opacity - case 'rgbString': - case 'rgbaString': - return rgbString($(this), method === 'rgbaString'); - - // Get/set settings on the fly - case 'settings': - if( data === undefined ) { - return $(this).data('minicolors-settings'); - } else { - // Setter - $(this).each( function() { - var settings = $(this).data('minicolors-settings') || {}; - destroy($(this)); - $(this).minicolors($.extend(true, settings, data)); - }); - } - return $(this); - - // Show the color picker - case 'show': - show( $(this).eq(0) ); - return $(this); - - // Get/set the hex color value - case 'value': - if( data === undefined ) { - // Getter - return $(this).val(); - } else { - // Setter - $(this).each( function() { - updateFromInput($(this).val(data)); - }); - } - return $(this); - - // Initializes the control - default: - if( method !== 'create' ) data = method; - $(this).each( function() { - init($(this), data); - }); - return $(this); - - } - - } - }); - - // Initialize input elements - function init(input, settings) { - - var minicolors = $('<div class="minicolors" />'), - defaults = $.minicolors.defaults, - format = input.attr('data-format'), - keywords = input.attr('data-keywords'), - opacity = input.attr('data-opacity'); - - // Do nothing if already initialized - if( input.data('minicolors-initialized') ) return; - - // Handle settings - settings = $.extend(true, {}, defaults, settings); - - // The wrapper - minicolors - .addClass('minicolors-theme-' + settings.theme) - .toggleClass('minicolors-with-opacity', settings.opacity) - .toggleClass('minicolors-no-data-uris', settings.dataUris !== true); - - // Custom positioning - if( settings.position !== undefined ) { - $.each(settings.position.split(' '), function() { - minicolors.addClass('minicolors-position-' + this); - }); - } - - // Input size - if( format === 'rgb' ) { - $input_size = opacity ? '25' : '20'; - } else { - $input_size = keywords ? '11' : '7'; - } - - // The input - input - .addClass('minicolors-input') - .data('minicolors-initialized', false) - .data('minicolors-settings', settings) - .prop('size', $input_size) - .wrap(minicolors) - .after( - '<div class="minicolors-panel minicolors-slider-' + settings.control + '">' + - '<div class="minicolors-slider minicolors-sprite">' + - '<div class="minicolors-picker"></div>' + - '</div>' + - '<div class="minicolors-opacity-slider minicolors-sprite">' + - '<div class="minicolors-picker"></div>' + - '</div>' + - '<div class="minicolors-grid minicolors-sprite">' + - '<div class="minicolors-grid-inner"></div>' + - '<div class="minicolors-picker"><div></div></div>' + - '</div>' + - '</div>' - ); - - // The swatch - if( !settings.inline ) { - input.after('<span class="minicolors-swatch minicolors-sprite"><span class="minicolors-swatch-color"></span></span>'); - input.next('.minicolors-swatch').on('click', function(event) { - event.preventDefault(); - input.focus(); - }); - } - - // Prevent text selection in IE - input.parent().find('.minicolors-panel').on('selectstart', function() { return false; }).end(); - - // Inline controls - if( settings.inline ) input.parent().addClass('minicolors-inline'); - - updateFromInput(input, false); - - input.data('minicolors-initialized', true); - - } - - // Returns the input back to its original state - function destroy(input) { - - var minicolors = input.parent(); - - // Revert the input element - input - .removeData('minicolors-initialized') - .removeData('minicolors-settings') - .removeProp('size') - .removeClass('minicolors-input'); - - // Remove the wrap and destroy whatever remains - minicolors.before(input).remove(); - - } - - // Shows the specified dropdown panel - function show(input) { - - var minicolors = input.parent(), - panel = minicolors.find('.minicolors-panel'), - settings = input.data('minicolors-settings'); - - // Do nothing if uninitialized, disabled, inline, or already open - if( !input.data('minicolors-initialized') || - input.prop('disabled') || - minicolors.hasClass('minicolors-inline') || - minicolors.hasClass('minicolors-focus') - ) return; - - hide(); - - minicolors.addClass('minicolors-focus'); - panel - .stop(true, true) - .fadeIn(settings.showSpeed, function() { - if( settings.show ) settings.show.call(input.get(0)); - }); - - } - - // Hides all dropdown panels - function hide() { - - $('.minicolors-focus').each( function() { - - var minicolors = $(this), - input = minicolors.find('.minicolors-input'), - panel = minicolors.find('.minicolors-panel'), - settings = input.data('minicolors-settings'); - - panel.fadeOut(settings.hideSpeed, function() { - if( settings.hide ) settings.hide.call(input.get(0)); - minicolors.removeClass('minicolors-focus'); - }); - - }); - } - - // Moves the selected picker - function move(target, event, animate) { - - var input = target.parents('.minicolors').find('.minicolors-input'), - settings = input.data('minicolors-settings'), - picker = target.find('[class$=-picker]'), - offsetX = target.offset().left, - offsetY = target.offset().top, - x = Math.round(event.pageX - offsetX), - y = Math.round(event.pageY - offsetY), - duration = animate ? settings.animationSpeed : 0, - wx, wy, r, phi; - - // Touch support - if( event.originalEvent.changedTouches ) { - x = event.originalEvent.changedTouches[0].pageX - offsetX; - y = event.originalEvent.changedTouches[0].pageY - offsetY; - } - - // Constrain picker to its container - if( x < 0 ) x = 0; - if( y < 0 ) y = 0; - if( x > target.width() ) x = target.width(); - if( y > target.height() ) y = target.height(); - - // Constrain color wheel values to the wheel - if( target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid') ) { - wx = 75 - x; - wy = 75 - y; - r = Math.sqrt(wx * wx + wy * wy); - phi = Math.atan2(wy, wx); - if( phi < 0 ) phi += Math.PI * 2; - if( r > 75 ) { - r = 75; - x = 75 - (75 * Math.cos(phi)); - y = 75 - (75 * Math.sin(phi)); - } - x = Math.round(x); - y = Math.round(y); - } - - // Move the picker - if( target.is('.minicolors-grid') ) { - picker - .stop(true) - .animate({ - top: y + 'px', - left: x + 'px' - }, duration, settings.animationEasing, function() { - updateFromControl(input, target); - }); - } else { - picker - .stop(true) - .animate({ - top: y + 'px' - }, duration, settings.animationEasing, function() { - updateFromControl(input, target); - }); - } - - } - - // Sets the input based on the color picker values - function updateFromControl(input, target) { - - function getCoords(picker, container) { - - var left, top; - if( !picker.length || !container ) return null; - left = picker.offset().left; - top = picker.offset().top; - - return { - x: left - container.offset().left + (picker.outerWidth() / 2), - y: top - container.offset().top + (picker.outerHeight() / 2) - }; - - } - - var hue, saturation, brightness, x, y, r, phi, - - hex = input.val(), - format = input.attr('data-format'), - keywords = input.attr('data-keywords'), - opacity = input.attr('data-opacity'), - - // Helpful references - minicolors = input.parent(), - settings = input.data('minicolors-settings'), - swatch = minicolors.find('.minicolors-swatch'), - - // Panel objects - grid = minicolors.find('.minicolors-grid'), - slider = minicolors.find('.minicolors-slider'), - opacitySlider = minicolors.find('.minicolors-opacity-slider'), - - // Picker objects - gridPicker = grid.find('[class$=-picker]'), - sliderPicker = slider.find('[class$=-picker]'), - opacityPicker = opacitySlider.find('[class$=-picker]'), - - // Picker positions - gridPos = getCoords(gridPicker, grid), - sliderPos = getCoords(sliderPicker, slider), - opacityPos = getCoords(opacityPicker, opacitySlider); - - // Handle colors - if( target.is('.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider') ) { - - // Determine HSB values - switch(settings.control) { - - case 'wheel': - // Calculate hue, saturation, and brightness - x = (grid.width() / 2) - gridPos.x; - y = (grid.height() / 2) - gridPos.y; - r = Math.sqrt(x * x + y * y); - phi = Math.atan2(y, x); - if( phi < 0 ) phi += Math.PI * 2; - if( r > 75 ) { - r = 75; - gridPos.x = 69 - (75 * Math.cos(phi)); - gridPos.y = 69 - (75 * Math.sin(phi)); - } - saturation = keepWithin(r / 0.75, 0, 100); - hue = keepWithin(phi * 180 / Math.PI, 0, 360); - brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); - hex = hsb2hex({ - h: hue, - s: saturation, - b: brightness - }); - - // Update UI - slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); - break; - - case 'saturation': - // Calculate hue, saturation, and brightness - hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); - saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); - brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); - hex = hsb2hex({ - h: hue, - s: saturation, - b: brightness - }); - - // Update UI - slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness })); - minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100); - break; - - case 'brightness': - // Calculate hue, saturation, and brightness - hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); - saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); - brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); - hex = hsb2hex({ - h: hue, - s: saturation, - b: brightness - }); - - // Update UI - slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); - minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100)); - break; - - default: - // Calculate hue, saturation, and brightness - hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360); - saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100); - brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); - hex = hsb2hex({ - h: hue, - s: saturation, - b: brightness - }); - - // Update UI - grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 })); - break; - - } - - // Handle opacity - if( settings.opacity ) { - opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2); - } else { - opacity = 1; - } - if( settings.opacity ) input.attr('data-opacity', opacity); - - // Set color string - if( format === 'rgb' ) { - // Returns RGB(A) string - var rgb = hex2rgb(hex), - opacity = input.attr('data-opacity') === '' ? 1 : keepWithin( parseFloat( input.attr('data-opacity') ).toFixed(2), 0, 1 ); - if( isNaN( opacity ) ) opacity = 1; - - if( input.minicolors('rgbObject').a < 1 && rgb ) { - // Set RGBA string if alpha - value = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat( opacity ) + ')'; - } else { - // Set RGB string (alpha = 1) - value = 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')'; - } - } else { - // Returns hex color - value = convertCase( hex, settings.letterCase ); - } - - // Update value from picker - input.val( value ); - } - - // Set swatch color - swatch.find('span').css({ - backgroundColor: hex, - opacity: opacity - }); - - // Handle change event - doChange(input, value, opacity); - - } - - // Sets the color picker values from the input - function updateFromInput(input, preserveInputValue) { - - var hex, - hsb, - format = input.attr('data-format'), - keywords = input.attr('data-keywords'), - opacity, - x, y, r, phi, - - // Helpful references - minicolors = input.parent(), - settings = input.data('minicolors-settings'), - swatch = minicolors.find('.minicolors-swatch'), - - // Panel objects - grid = minicolors.find('.minicolors-grid'), - slider = minicolors.find('.minicolors-slider'), - opacitySlider = minicolors.find('.minicolors-opacity-slider'), - - // Picker objects - gridPicker = grid.find('[class$=-picker]'), - sliderPicker = slider.find('[class$=-picker]'), - opacityPicker = opacitySlider.find('[class$=-picker]'); - - // Determine hex/HSB values - if( isRgb(input.val()) ) { - // If input value is a rgb(a) string, convert it to hex color and update opacity - hex = rgbString2hex(input.val()); - alpha = keepWithin(parseFloat(getAlpha(input.val())).toFixed(2), 0, 1); - if( alpha ) { - input.attr('data-opacity', alpha); - } - } else { - hex = convertCase(parseHex(input.val(), true), settings.letterCase); - } - - if( !hex ){ - hex = convertCase(parseInput(settings.defaultValue, true), settings.letterCase); - } - hsb = hex2hsb(hex); - - // Get array of lowercase keywords - keywords = !keywords ? [] : $.map(keywords.split(','), function(a) { - return $.trim(a.toLowerCase()); - }); - - // Set color string - if( input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1 ) { - value = convertCase(input.val()); - } else { - value = isRgb(input.val()) ? parseRgb(input.val()) : hex; - } - - // Update input value - if( !preserveInputValue ) input.val(value); - - // Determine opacity value - if( settings.opacity ) { - // Get from data-opacity attribute and keep within 0-1 range - opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1); - if( isNaN(opacity) ) opacity = 1; - input.attr('data-opacity', opacity); - swatch.find('span').css('opacity', opacity); - - // Set opacity picker position - y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height()); - opacityPicker.css('top', y + 'px'); - } - - // Set opacity to zero if input value is transparent - if( input.val().toLowerCase() === 'transparent' ) { - swatch.find('span').css('opacity', 0); - } - - // Update swatch - swatch.find('span').css('backgroundColor', hex); - - // Determine picker locations - switch(settings.control) { - - case 'wheel': - // Set grid position - r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2); - phi = hsb.h * Math.PI / 180; - x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width()); - y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height()); - gridPicker.css({ - top: y + 'px', - left: x + 'px' - }); - - // Set slider position - y = 150 - (hsb.b / (100 / grid.height())); - if( hex === '' ) y = 0; - sliderPicker.css('top', y + 'px'); - - // Update panel color - slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); - break; - - case 'saturation': - // Set grid position - x = keepWithin((5 * hsb.h) / 12, 0, 150); - y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); - gridPicker.css({ - top: y + 'px', - left: x + 'px' - }); - - // Set slider position - y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height()); - sliderPicker.css('top', y + 'px'); - - // Update UI - slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b })); - minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100); - break; - - case 'brightness': - // Set grid position - x = keepWithin((5 * hsb.h) / 12, 0, 150); - y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height()); - gridPicker.css({ - top: y + 'px', - left: x + 'px' - }); - - // Set slider position - y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height()); - sliderPicker.css('top', y + 'px'); - - // Update UI - slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); - minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100)); - break; - - default: - // Set grid position - x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width()); - y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); - gridPicker.css({ - top: y + 'px', - left: x + 'px' - }); - - // Set slider position - y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height()); - sliderPicker.css('top', y + 'px'); - - // Update panel color - grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 })); - break; - - } - - // Fire change event, but only if minicolors is fully initialized - if( input.data('minicolors-initialized') ) { - doChange(input, value, opacity); - } - - } - - // Runs the change and changeDelay callbacks - function doChange(input, value, opacity) { - - var settings = input.data('minicolors-settings'), - lastChange = input.data('minicolors-lastChange'); - - // Only run if it actually changed - if( !lastChange || lastChange.value !== value || lastChange.opacity !== opacity ) { - - // Remember last-changed value - input.data('minicolors-lastChange', { - value: value, - opacity: opacity - }); - - // Fire change event - if( settings.change ) { - if( settings.changeDelay ) { - // Call after a delay - clearTimeout(input.data('minicolors-changeTimeout')); - input.data('minicolors-changeTimeout', setTimeout( function() { - settings.change.call(input.get(0), value, opacity); - }, settings.changeDelay)); - } else { - // Call immediately - settings.change.call(input.get(0), value, opacity); - } - } - input.trigger('change').trigger('input'); - } - - } - - // Generates an RGB(A) object based on the input's value - function rgbObject(input) { - var hex = parseHex($(input).val(), true), - rgb = hex2rgb(hex), - opacity = $(input).attr('data-opacity'); - if( !rgb ) return null; - if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) }); - return rgb; - } - - // Generates an RGB(A) string based on the input's value - function rgbString(input, alpha) { - var hex = parseHex($(input).val(), true), - rgb = hex2rgb(hex), - opacity = $(input).attr('data-opacity'); - if( !rgb ) return null; - if( opacity === undefined ) opacity = 1; - if( alpha ) { - return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')'; - } else { - return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')'; - } - } - - // Converts to the letter case specified in settings - function convertCase(string, letterCase) { - return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase(); - } - - // Parses a string and returns a valid hex string when possible - function parseHex(string, expand) { - string = string.replace(/^#/g, ''); - if( !string.match(/^[A-F0-9]{3,6}/ig) ) return ''; - if( string.length !== 3 && string.length !== 6 ) return ''; - if( string.length === 3 && expand ) { - string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2]; - } - return '#' + string; - } - - // Parses a string and returns a valid RGB(A) string when possible - function parseRgb(string, obj) { - - var values = string.replace(/[^\d,.]/g, ''), - rgba = values.split(','), - output; - - rgba[0] = keepWithin(parseInt(rgba[0], 10), 0, 255); - rgba[1] = keepWithin(parseInt(rgba[1], 10), 0, 255); - rgba[2] = keepWithin(parseInt(rgba[2], 10), 0, 255); - if( rgba[3] ) { - rgba[3] = keepWithin(parseFloat(rgba[3], 10), 0, 1); - } - - // Return RGBA object - if( obj ) { - return { - r: rgba[0], - g: rgba[1], - b: rgba[2], - a: rgba[3] ? rgba[3] : null - }; - } - - // Return RGBA string - if( rgba[3] ) { - return 'rgba(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ', ' + rgba[3] + ')'; - } else { - return 'rgb(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ')'; - } - - } - - // Parses a string and returns a valid color string when possible - function parseInput(string, expand) { - if( isRgb(string) ) { - // Returns a valid rgb(a) string - return parseRgb(string); - } else { - return parseHex(string, expand); - } - } - - // Keeps value within min and max - function keepWithin(value, min, max) { - if( value < min ) value = min; - if( value > max ) value = max; - return value; - } - - // Checks if a string is a valid RGB(A) string - function isRgb(string) { - rgb = string.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); - return (rgb && rgb.length === 4) ? true : false; - } - - // Function to get alpha from a RGB(A) string - function getAlpha(rgba) { - rgba = rgba.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+(\.\d{1,2})?|\.\d{1,2})[\s+]?/i); - return (rgba && rgba.length === 6) ? rgba[4] : '1'; - } - - // Converts an HSB object to an RGB object - function hsb2rgb(hsb) { - var rgb = {}; - var h = Math.round(hsb.h); - var s = Math.round(hsb.s * 255 / 100); - var v = Math.round(hsb.b * 255 / 100); - if(s === 0) { - rgb.r = rgb.g = rgb.b = v; - } else { - var t1 = v; - var t2 = (255 - s) * v / 255; - var t3 = (t1 - t2) * (h % 60) / 60; - if( h === 360 ) h = 0; - if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; } - else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; } - else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; } - else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; } - else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; } - else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; } - else { rgb.r = 0; rgb.g = 0; rgb.b = 0; } - } - return { - r: Math.round(rgb.r), - g: Math.round(rgb.g), - b: Math.round(rgb.b) - }; - } - - // Converts an RGB string to a hex string - function rgbString2hex(rgb){ - rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); - return (rgb && rgb.length === 4) ? '#' + - ('0' + parseInt(rgb[1],10).toString(16)).slice(-2) + - ('0' + parseInt(rgb[2],10).toString(16)).slice(-2) + - ('0' + parseInt(rgb[3],10).toString(16)).slice(-2) : ''; - } - - // Converts an RGB object to a hex string - function rgb2hex(rgb) { - var hex = [ - rgb.r.toString(16), - rgb.g.toString(16), - rgb.b.toString(16) - ]; - $.each(hex, function(nr, val) { - if (val.length === 1) hex[nr] = '0' + val; - }); - return '#' + hex.join(''); - } - - // Converts an HSB object to a hex string - function hsb2hex(hsb) { - return rgb2hex(hsb2rgb(hsb)); - } - - // Converts a hex string to an HSB object - function hex2hsb(hex) { - var hsb = rgb2hsb(hex2rgb(hex)); - if( hsb.s === 0 ) hsb.h = 360; - return hsb; - } - - // Converts an RGB object to an HSB object - function rgb2hsb(rgb) { - var hsb = { h: 0, s: 0, b: 0 }; - var min = Math.min(rgb.r, rgb.g, rgb.b); - var max = Math.max(rgb.r, rgb.g, rgb.b); - var delta = max - min; - hsb.b = max; - hsb.s = max !== 0 ? 255 * delta / max : 0; - if( hsb.s !== 0 ) { - if( rgb.r === max ) { - hsb.h = (rgb.g - rgb.b) / delta; - } else if( rgb.g === max ) { - hsb.h = 2 + (rgb.b - rgb.r) / delta; - } else { - hsb.h = 4 + (rgb.r - rgb.g) / delta; - } - } else { - hsb.h = -1; - } - hsb.h *= 60; - if( hsb.h < 0 ) { - hsb.h += 360; - } - hsb.s *= 100/255; - hsb.b *= 100/255; - return hsb; - } - - // Converts a hex string to an RGB object - function hex2rgb(hex) { - hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); - return { - /* jshint ignore:start */ - r: hex >> 16, - g: (hex & 0x00FF00) >> 8, - b: (hex & 0x0000FF) - /* jshint ignore:end */ - }; - } - - // Handle events - $(document) - // Hide on clicks outside of the control - .on('mousedown.minicolors touchstart.minicolors', function(event) { - if( !$(event.target).parents().add(event.target).hasClass('minicolors') ) { - hide(); - } - }) - // Start moving - .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) { - var target = $(this); - event.preventDefault(); - $(document).data('minicolors-target', target); - move(target, event, true); - }) - // Move pickers - .on('mousemove.minicolors touchmove.minicolors', function(event) { - var target = $(document).data('minicolors-target'); - if( target ) move(target, event); - }) - // Stop moving - .on('mouseup.minicolors touchend.minicolors', function() { - $(this).removeData('minicolors-target'); - }) - // Show panel when swatch is clicked - .on('mousedown.minicolors touchstart.minicolors', '.minicolors-swatch', function(event) { - var input = $(this).parent().find('.minicolors-input'); - event.preventDefault(); - show(input); - }) - // Show on focus - .on('focus.minicolors', '.minicolors-input', function() { - var input = $(this); - if( !input.data('minicolors-initialized') ) return; - show(input); - }) - // Update value on blur - .on('blur.minicolors', '.minicolors-input', function() { - var input = $(this), - keywords = input.attr('data-keywords'), - settings = input.data('minicolors-settings'), - hex, - rgba, - swatchOpacity; - - if( !input.data('minicolors-initialized') ) return; - - // Get array of lowercase keywords - keywords = !keywords ? [] : $.map(keywords.split(','), function(a) { - return $.trim(a.toLowerCase()); - }); - - // Set color string - if( input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1 ) { - value = input.val(); - } else { - // Get RGBA values for easy conversion - if( isRgb(input.val()) ) { - rgba = parseRgb(input.val(), true); - } else { - hex = parseHex(input.val(), true); - rgba = hex ? hex2rgb(hex) : null; - } - - // Convert to format - if( rgba === null ) { - value = settings.defaultValue; - } else if( settings.format === 'rgb' ) { - value = settings.opacity ? - parseRgb('rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + input.attr('data-opacity') + ')') : - parseRgb('rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')'); - } else { - value = rgb2hex(rgba); - } - } - - // Update swatch opacity - swatchOpacity = settings.opacity ? input.attr('data-opacity') : 1; - if( value.toLowerCase() === 'transparent' ) swatchOpacity = 0; - input - .closest('.minicolors') - .find('.minicolors-swatch > span') - .css('opacity', swatchOpacity); - - // Set input value - input.val(value); - - // Is it blank? - if( input.val() === '' ) input.val(parseInput(settings.defaultValue, true)); - - // Adjust case - input.val( convertCase(input.val(), settings.letterCase) ); - - }) - // Handle keypresses - .on('keydown.minicolors', '.minicolors-input', function(event) { - var input = $(this); - if( !input.data('minicolors-initialized') ) return; - switch(event.keyCode) { - case 9: // tab - hide(); - break; - case 13: // enter - case 27: // esc - hide(); - input.blur(); - break; - } - }) - // Update on keyup - .on('keyup.minicolors', '.minicolors-input', function() { - var input = $(this); - if( !input.data('minicolors-initialized') ) return; - updateFromInput(input, true); - }) - // Update on paste - .on('paste.minicolors', '.minicolors-input', function() { - var input = $(this); - if( !input.data('minicolors-initialized') ) return; - setTimeout( function() { - updateFromInput(input, true); - }, 1); - }); - -})); diff --git a/packages/rocketchat-theme/assets/stylesheets/fontello.css b/packages/rocketchat-theme/client/vendor/fontello/css/fontello.css old mode 100644 new mode 100755 similarity index 98% rename from packages/rocketchat-theme/assets/stylesheets/fontello.css rename to packages/rocketchat-theme/client/vendor/fontello/css/fontello.css index a9ca191f95793fb2994bbb7fa7aa8c2d98d5879a..3da0e897da43fcca68192399ef4963f9f2690015 --- a/packages/rocketchat-theme/assets/stylesheets/fontello.css +++ b/packages/rocketchat-theme/client/vendor/fontello/css/fontello.css @@ -1,10 +1,11 @@ @font-face { font-family: 'fontello'; - src: url('fonts/fontello.eot?80104145'); - src: url('fonts/fontello.eot?80104145#iefix') format('embedded-opentype'), - url('fonts/fontello.woff2?80104145') format('woff2'), - url('fonts/fontello.woff?80104145') format('woff'), - url('fonts/fontello.ttf?80104145') format('truetype'); + src: url('../font/fontello.eot?90843248'); + src: url('../font/fontello.eot?90843248#iefix') format('embedded-opentype'), + url('../font/fontello.woff2?90843248') format('woff2'), + url('../font/fontello.woff?90843248') format('woff'), + url('../font/fontello.ttf?90843248') format('truetype'), + url('../font/fontello.svg?90843248#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -14,7 +15,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('/fonts/fontello.svg?80104145#fontello') format('svg'); + src: url('../font/fontello.svg?90843248#fontello') format('svg'); } } */ diff --git a/public/fonts/fontello.html b/packages/rocketchat-theme/client/vendor/fontello/demo.html similarity index 99% rename from public/fonts/fontello.html rename to packages/rocketchat-theme/client/vendor/fontello/demo.html index 1d698b3978c2ebfb09d1ddf8c0803fe9224c73a0..1247bdf23e335b47fe7f355a6628331bb1e43ff1 100644 --- a/public/fonts/fontello.html +++ b/packages/rocketchat-theme/client/vendor/fontello/demo.html @@ -229,10 +229,10 @@ body { } @font-face { font-family: 'fontello'; - src: url('./fontello.eot?87518418'); - src: url('./fontello.eot?87518418#iefix') format('embedded-opentype'), - url('./fontello.woff?87518418') format('woff'), - url('./fontello.ttf?87518418') format('truetype'); + src: url('./font/fontello.eot?87518418'); + src: url('./font/fontello.eot?87518418#iefix') format('embedded-opentype'), + url('./font/fontello.woff?87518418') format('woff'), + url('./font/fontello.ttf?87518418') format('truetype'); font-weight: normal; font-style: normal; } diff --git a/public/fonts/fontello.eot b/packages/rocketchat-theme/client/vendor/fontello/font/fontello.eot similarity index 100% rename from public/fonts/fontello.eot rename to packages/rocketchat-theme/client/vendor/fontello/font/fontello.eot diff --git a/packages/rocketchat-theme/client/vendor/fontello/font/fontello.svg b/packages/rocketchat-theme/client/vendor/fontello/font/fontello.svg new file mode 100755 index 0000000000000000000000000000000000000000..ba07cafa1d7b87cb2b56ad55d3dd46ccbee55b8b --- /dev/null +++ b/packages/rocketchat-theme/client/vendor/fontello/font/fontello.svg @@ -0,0 +1,980 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata> +<defs> +<font id="fontello" horiz-adv-x="1000" > +<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" /> +<missing-glyph horiz-adv-x="1000" /> +<glyph glyph-name="parking" unicode="!" d="M1096 504l-99 255c-19 49-59 91-135 91h-140-238-141c-76 0-117-42-136-91l-98-255c-40-5-109-51-109-138v-325h96v-104c0-127 181-126 181 0v104h325 325v-104c1-126 181-127 181 0v104h97v325c0 87-70 133-109 138z m-906-267c-47 0-84 39-84 86 0 48 37 86 84 86 46 0 83-38 83-86 0-47-37-86-83-86z m412 270h0-391l75 201c9 28 23 48 56 49h260 0 261c33-1 47-21 56-49l75-201h-392z m413-270c-46 0-84 39-84 86 0 48 38 86 84 86 46 0 83-38 84-86 0-47-38-86-84-86z" horiz-adv-x="1205" /> + +<glyph glyph-name="bedroom" unicode=""" d="M292 361c0 75 61 135 135 135 75 0 135-60 135-135 0-74-60-135-135-135-74 0-135 61-135 135z m1123-135h-791v174c0 53 40 96 89 96h612c49 0 90-43 90-96v-174z m220 263v-559c0-32-26-58-58-58-16 0-31 6-41 17-10 10-17 25-17 41v96h-1300v-101c0-29-23-53-53-53-14 0-28 6-37 15-10 10-16 23-16 38v805c0 30 24 53 53 53 15 0 28-6 38-15 9-10 15-23 15-38v-607h1300v366c0 32 26 58 58 58 16 0 30-6 41-17s17-25 17-41z" horiz-adv-x="1644" /> + +<glyph glyph-name="elevator" unicode="#" d="M974 850h-948c-14 0-26-12-26-26v-948c0-14 12-26 26-26h948c14 0 26 12 26 26v948c0 14-12 26-26 26z m-53-573c0 0 0 0 0 0h-92c-7 0-13 4-15 10-3 6-1 13 3 18l46 46c7 7 17 7 24 0l45-45c3-3 6-7 6-13 0-9-8-16-17-16z m12-79l-46-46c-4-4-8-5-12-5-4 0-8 1-12 5l-46 46c-4 4-6 11-3 18 2 6 8 10 15 10h92c7 0 13-4 15-10 3-7 1-14-3-18z m-683 402h224v-697h-224v697z m276-697v697h224v-697h-224z m-188 947c0 90 72 163 162 163 90 0 163-73 163-163" horiz-adv-x="1000" /> + +<glyph glyph-name="kettle" unicode="$" d="M732 550c0 0 0 0 0 0-5 114-98 205-214 205-118 0-213-96-213-214v-61l-146 85h-99l245-245v-470h427v279h0c116 0 211 95 211 210 0 116-95 211-211 211z m0-354l0 0 0 287h0c79 0 144-65 144-144 0-79-65-143-144-143z m-214 654c27 0 48-21 48-47 0-27-21-48-48-48-26 0-47 21-47 48 0 26 21 47 47 47z" horiz-adv-x="1000" /> + +<glyph glyph-name="pan" unicode="%" d="M106 587h792c18 0 33 15 33 33 0 19-15 33-33 33h-17c-47 44-182 76-345 80 6 8 10 17 10 28 0 24-20 44-44 44-25 0-45-20-45-44 0-11 4-20 10-28-163-4-298-36-344-80h-17c-18 0-33-14-33-33 0-18 15-33 33-33z m845-43h-898c-25 0-44-19-44-44 0-24 19-44 44-44h53v-443c0-61 49-110 110-110h572c60 0 110 49 110 110v443h53c24 0 44 20 44 44 0 25-20 44-44 44z" horiz-adv-x="1000" /> + +<glyph glyph-name="tap" unicode="&" d="M870 224v-59h-270v59h32v138c0 22-18 39-39 39h-150v-38h-165v38h-148v207h148v37h53v121h-89c-20 0-37 16-37 36s16 36 37 36h237c20 0 36-16 36-36s-16-36-36-36h-89v-121h53v-37h189c114 0 206-93 206-207v-177h32z m-54-281c0-45-36-81-81-81-45 0-82 36-82 81 0 13 3 25 9 36 5 10 6 10 13 21 9 13 20 30 30 47 18 30 30 59 30 59s12-29 30-60c9-16 21-33 30-46 7-11 8-11 13-21 6-11 8-23 8-36z" horiz-adv-x="1000" /> + +<glyph glyph-name="foxter" unicode="'" d="M500-140c-270 0-490 220-490 490 0 270 220 490 490 490 270 0 490-220 490-490 0-270-220-490-490-490l0 0z m0 917l0 0c-235 0-427-192-427-427 0-235 192-427 427-427 235 0 427 192 427 427 0 235-192 427-427 427z m-225-411c1 68-30 87-70 82-36-3-64-8-79-14-3-16-6-32-7-48 0-12-1-24-1-36 0-4 0-9 0-13 7-1 22-2 45-2 0-47 2-93 6-137-9 2-17 5-23 7 32-78 89-143 161-185-23 77-33 213-32 346z m498-273c5 1 10 2 14 4 15 16 28 34 40 54 4 37-13 43-42 38-56-8-129-14-206-16 2 49 2 104 3 159 142 0 269 2 297 4 1 1 2 1 3 1 0 4 0 9 0 13 0 12 0 24-1 36-1 16-4 32-7 48-45 17-200 26-359 26-57 0-94-22-94-93-1-177 6-358 21-396 7-1 15-2 23-2 11-1 23-1 35-1 12 0 24 0 35 1 9 0 17 1 25 3 6 15 11 51 14 100 73 3 142 10 199 21z m-263 482c125-1 249-12 321-32-25 42-58 80-96 110-51 7-108 12-167 14-3 30-7 52-11 62-7 1-14 2-21 2-12 1-24 1-36 1-12 0-24 0-36-1-9 0-18-2-26-3-6-13-10-43-13-83-4-50 30-71 85-70z m-218 59c3 16 6 31 10 44-55-33-101-80-133-135 13 4 27 7 42 10 39 6 72 31 81 81z" horiz-adv-x="1000" /> + +<glyph glyph-name="left-circled2" unicode="(" d="M643 404v-108q0-7-5-12t-13-5h-196v-108q0-7-5-12t-13-5q-7 0-14 5l-178 178q-5 5-5 13t5 13l179 178q5 5 13 5 7 0 12-5t6-12v-108h196q7 0 13-5t5-12z m89-54q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="right-circled2" unicode=")" d="M643 350q0-8-5-13l-179-178q-5-5-13-5-7 0-12 5t-5 12v108h-197q-7 0-12 5t-6 12v108q0 7 6 12t12 5h197v108q0 7 5 12t12 5q7 0 14-5l178-178q5-5 5-13z m89 0q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="football" unicode="*" d="M505 850c-278 0-505-225-505-501 0-276 227-501 505-501s506 225 506 501c0 276-227 501-506 501z m156-72c73-27 137-72 186-131 35-61 41-91 39-107l-139-51-150 95c-10 54-35 142-42 170 37 23 99 24 106 24z m-139-24l0 0c0-1 30-107 43-170l-135-111-217 32c0 0-13 58-13 104l0 0c1 1 77 122 203 161 0 0 74-3 119-16z m-216 5c-67-45-114-106-128-126-18-8-37-23-52-38 43 70 106 127 180 164z m-248-400c0 8 1 15 1 23 12-21 30-37 43-47 11-41 42-147 93-231-2-33 2-56 5-71-87 82-142 197-142 326z m165-240c-52 86-84 200-92 232 33 69 75 122 75 122l212-31c24-69 58-141 74-175-24-35-90-133-129-181-38-1-103 20-140 33z m282-207c-39 0-76 5-112 15 37 1 59 10 72 18 11-2 44-8 88-8 44 0 100 6 155 30 3-1 6-2 9-2-63-34-135-53-212-53z m200 88c-113-53-235-23-237-23l-9 5c-33 30-62 77-67 87 0 0 0 1-1 1 41 51 103 143 126 178 127 6 177 16 177 16 1-1 65-85 96-160-16-37-60-80-85-104z m205 184c-51-40-96-56-96-56-20 40-91 154-91 154 23 59 33 139 37 178l134 49c33-32 51-87 56-109 2-13 3-27 3-41 0-66-15-128-41-185 0 7-2 10-2 10z" horiz-adv-x="1008" /> + +<glyph glyph-name="tennis" unicode="+" d="M275 480c-41-71-106-119-178-140-2 72 15 145 53 212 39 67 94 118 157 152 18-73 9-153-32-224z m363-209c-57-99-68-211-39-312-98-25-206-13-301 41-94 55-159 142-186 240 102 26 194 91 250 189 57 99 68 211 39 312 98 25 206 13 301-41 95-55 159-142 186-240-102-26-194-91-250-189z m212-123c-39-66-94-118-157-152-18 73-9 153 32 224 41 71 106 119 179 140 1-72-16-145-54-212z" horiz-adv-x="1000" /> + +<glyph glyph-name="chrome" unicode="," d="M498 850c-147-1-291-67-387-186l154-237c39 111 150 182 267 170l414-22c-42 84-108 157-196 208-79 46-166 67-252 67z m-416-226c-52-79-82-173-82-274 0-250 183-457 423-494l128 252c-116-22-233 39-281 146l-188 370z m885-94l-282-15c76-89 82-221 13-317l-226-347c94-6 190 15 278 66 216 125 304 387 217 613z m-467-11c-93 0-169-76-169-169s76-169 169-169 169 76 169 169-76 169-169 169z" horiz-adv-x="1000" /> + +<glyph glyph-name="opera" unicode="-" d="M426-150c-568 0-568 1000 0 1000s567-1000 0-1000z m0 92c237 0 237 816 0 816s-238-816 0-816z" horiz-adv-x="851" /> + +<glyph glyph-name="crown" unicode="." d="M419 822c-39 0-71-32-71-72 0-40 32-72 71-72 39 0 74 32 74 72 0 40-35 72-74 72z m-48-222c-40-119-101-151-101-151s-52 145-109 231c-23-52-47-86-96-100-1-44 51-208 55-290l601 0c2 79 55 256 55 288-38 16-85 51-96 102-63-86-109-231-109-231s-61 32-101 151c-27-13-77-13-99 0z m-297 222c-40 0-74-32-74-72 0-40 34-72 74-72 39 0 70 32 70 72 0 40-31 72-70 72z m47-603l0-119 600 0c0 43 0 81 0 119z m644 603c-40 0-72-32-72-72 0-40 32-72 72-72 40 0 72 32 72 72 0 40-32 72-72 72z" horiz-adv-x="837" /> + +<glyph glyph-name="ie" unicode="/" d="M844 850c-81-1-181-46-281-106-239 35-498-103-509-386 60 93 224 257 367 301l0-13c-179-90-571-612-360-773 90-69 310 37 344 76 29-5 59-8 89-8 201 0 371 124 424 292l-301 0c-13-56-56-92-121-92-102 0-126 68-126 151l561 0c29 204-114 385-306 439 174 115 363 161 299-104l20 0c35 109 21 191-38 213-18 7-37 10-58 10-1 0-3 0-4 0z m-352-303c79 0 128-48 128-129l-252 0c0 77 46 129 124 129z m-367-422c58-83 149-145 256-171-77-58-226-84-266-52-48 40-36 124 10 223z" horiz-adv-x="944" /> + +<glyph glyph-name="glass" unicode="0" d="M948 746q0-19-24-43l-353-353v-429h179q15 0 25-10t11-25-11-25-25-11h-500q-14 0-25 11t-11 25 11 25 25 10h179v429l-353 353q-24 24-24 43 0 13 10 21t21 9 24 3h786q13 0 24-3t21-9 10-21z" horiz-adv-x="1000" /> + +<glyph glyph-name="music" unicode="1" d="M857 725v-625q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 33 58 18 54 6q58 0 107-22v300l-429-132v-396q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 34 58 17 54 6q58 0 107-21v539q0 17 10 32t28 20l464 142q7 3 16 3 22 0 38-16t15-38z" horiz-adv-x="857.1" /> + +<glyph glyph-name="search" unicode="2" d="M643 386q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" /> + +<glyph glyph-name="360" unicode="3" d="M1068 458v-19c85-25 134-57 134-92 0-59-137-109-337-130v-88c255 26 436 98 436 182 0 59-88 112-233 147z m-703-76c-35 0-62 28-62 28l-36-58c0 0 36-40 104-40 76 0 112 52 112 104 0 42-28 78-73 88l63 71v45h-193v-62h72c20 0 31 2 31 2v-1c0 0-13-11-27-29l-41-48 16-37h21c33 0 57-9 57-33 0-17-16-30-44-30z m282-70c66 0 104 51 104 103 0 52-37 104-100 104-14 0-34-6-42-11h0c9 27 29 50 65 50 22 0 42-9 42-9l20 64c0 0-25 13-68 13-94 0-145-86-145-168 0-100 63-146 124-146z m-16 147c33 0 46-23 46-45 0-18-12-34-30-34-21 0-47 24-47 61 0 14 14 18 31 18z m387 11c0 87-28 156-115 156s-114-69-114-156c0-87 28-158 114-158s115 71 115 158z m-153 0c0 49 9 88 38 88s38-39 38-88c0-50-8-90-38-90s-38 40-38 90z m-235-264c-294 2-532 64-532 141 0 39 65 74 165 100v18c-158-35-264-91-264-154 0-105 282-189 631-192v-45l155 89-155 89v-46z" horiz-adv-x="1300" /> + +<glyph glyph-name="mail" unicode="4" d="M929 11v428q-18-20-39-36-149-115-238-189-28-24-46-37t-48-28-57-13h-2q-26 0-57 13t-48 28-46 37q-88 74-238 189-21 16-39 36v-428q0-7 6-13t12-5h822q7 0 12 5t6 13z m0 586v14t-1 7-1 7-3 5-5 4-8 2h-822q-7 0-12-6t-6-12q0-94 83-159 107-84 223-176 4-3 20-17t25-21 25-17 28-16 24-5h2q11 0 24 5t28 16 25 17 25 21 20 17q116 92 224 176 30 24 56 65t26 73z m71 21v-607q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v607q0 37 26 63t63 26h822q37 0 63-26t26-63z" horiz-adv-x="1000" /> + +<glyph glyph-name="mail-alt" unicode="5" d="M1000 454v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" /> + +<glyph glyph-name="heart" unicode="6" d="M500-79q-14 0-25 10l-348 336q-5 5-15 15t-31 37-38 54-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192q0-123-128-251l-347-335q-10-10-25-10z" horiz-adv-x="1000" /> + +<glyph glyph-name="heart-empty" unicode="7" d="M929 517q0 46-12 80t-31 55-46 33-52 18-55 4-62-14-62-36-48-40-34-34q-10-13-27-13t-27 13q-14 15-34 34t-48 40-62 36-62 14-55-4-52-18-46-33-31-55-12-80q0-93 105-198l324-312 324 312q105 105 105 198z m71 0q0-123-128-251l-347-335q-10-10-25-10t-25 10l-348 336q-5 5-15 15t-31 37-38 54-30 67-13 77q0 123 71 192t196 70q34 0 70-12t67-33 54-38 42-38q20 20 42 38t54 38 67 33 70 12q125 0 196-70t71-192z" horiz-adv-x="1000" /> + +<glyph glyph-name="camera-tour" unicode="8" d="M583 571c20-20 30-45 30-74 0-29-10-54-30-74-21-21-46-31-75-31-29 0-53 10-74 31-21 20-31 45-31 74 0 29 10 54 31 74 21 21 45 31 74 31 29 0 54-10 75-31z m61-555c0 8-3 14-8 20l-139 139c-9 9-19 10-31 6-12-5-18-14-17-26v-80c-209 17-368 99-368 197 0 30 16 60 45 86v38c-78-48-126-109-126-176 0-135 195-248 449-269v-74c-1-13 5-22 17-27 11-4 22-2 31 7l139 139c3 4 5 8 7 13l1 2-1 1c0 1 1 3 1 4z m215 318c0-93-88-94-155-94-78 0-157 0-235 0-68 0-135 0-202 0-31 0-59 5-82 28-33 33-27 78-27 120 0 82 0 163 0 244 0 62 23 122 96 122 26 0 51 0 76 0 8 0 17 38 20 46 21 56 79 47 126 47 44 0 92 7 135 0 37-7 51-37 63-68 9-25 7-25 33-25 25 0 54 3 79-2 42-9 73-49 73-91m-235-279c63 63 63 167 0 231-64 63-168 63-231 0-64-64-64-168 0-231 63-64 167-64 231 0 32 32-32-32 0 0z m383-162c0 65-43 124-117 171v-41c23-24 35-51 35-78 0-66-70-124-181-161-3 0-5-1-8-2-29-10-44-43-33-75 10-31 43-49 72-39 3 1 5 2 8 3 136 50 224 131 224 222z" horiz-adv-x="1005" /> + +<glyph glyph-name="hubot" unicode="9" d="M188 475c-35 0-63-28-63-62v-125c0-35 28-63 63-63h500c34 0 62 28 62 63v125c0 34-28 62-62 62h-500z m500-109l-79-78h-93l-78 78-79-78h-93l-78 78v47h46l79-79 78 79h93l79-79 78 79h47v-47z m-375-203h250v-63h-250v63z m125 562c-242 0-438-182-438-406v-281c0-35 28-63 63-63h750c34 0 62 28 62 63v281c0 224-196 406-437 406z m375-687h-750v281c0 193 165 349 375 349s375-156 375-349v-281z" horiz-adv-x="875" /> + +<glyph glyph-name="squirrel" unicode=":" d="M750 788c-138 0-250-82-250-183 0-121 31-190 0-380 0 281-173 396-250 396 3 32-30 42-30 42s-14-7-19-22c-17 20-35 18-35 18l-9-37c0 0-114-40-116-201 14-21 97-38 156-27 56-3 42-50 30-62-53-53-102 18-164 18s-63-62 0-62 62-63 187-63c-193-75 0-250 0-250h-62c-63 0-63-63-63-63s250 0 375 0c188 0 313 63 313 218 0 53-27 111-63 158-69 91 14 167 63 125s187-63 187 125c0 138-112 250-250 250z m-594-313c-17 0-31 14-31 31 0 18 14 32 31 32 18 0 32-14 32-32 0-17-14-31-32-31z" horiz-adv-x="1000" /> + +<glyph glyph-name="clippy" unicode=";" d="M125 100h250v-62h-250v62z m313 375h-313v-62h313v62z m125-187v125l-188-188 188-187v125h312v125h-312z m-282 62h-156v-62h156v62z m-156-187h156v62h-156v-62z m563-63h62v-125c-1-18-7-32-19-44s-26-18-43-19h-625c-35 0-63 29-63 63v688c0 34 28 62 63 62h187c0 69 56 125 125 125s125-56 125-125h188c34 0 62-28 62-62v-313h-62v188h-625v-563h625v125z m-563 500h500c0 34-28 63-62 63h-63c-34 0-62 28-62 62s-29 63-63 63-62-29-62-63-29-62-63-62h-62c-35 0-63-29-63-63z" horiz-adv-x="875" /> + +<glyph glyph-name="left-dir" unicode="<" d="M357 600v-500q0-14-10-25t-26-11-25 11l-250 250q-10 11-10 25t10 25l250 250q11 11 25 11t26-11 10-25z" horiz-adv-x="357.1" /> + +<glyph glyph-name="right-dir" unicode=">" d="M321 350q0-14-10-25l-250-250q-11-11-25-11t-25 11-11 25v500q0 15 11 25t25 11 25-11l250-250q10-10 10-25z" horiz-adv-x="357.1" /> + +<glyph glyph-name="star-half" unicode="?" d="M464 832v-747l-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23z" horiz-adv-x="500" /> + +<glyph glyph-name="star-half-alt" unicode="@" d="M662 316l143 140-198 29-37 5-17 34-89 179v-537l33-17 178-94-34 198-6 37z m252 146l-202-197 48-279q2-19-4-29t-19-11q-9 0-22 7l-251 132-250-132q-13-7-23-7-12 0-19 11t-3 29l48 279-203 197q-18 18-13 33t30 20l280 40 126 254q11 23 27 23 16 0 28-23l125-254 280-40q25-4 31-20t-14-33z" horiz-adv-x="928.6" /> + +<glyph glyph-name="marquee" unicode="A" d="M0 850l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-285l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="user" unicode="B" d="M714 69q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" /> + +<glyph glyph-name="users" unicode="C" d="M331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="shower" unicode="D" d="M834 393h-668c0 113 92 205 205 205h57v254h144v-254h57c113 0 205-92 205-205z m-300-82v1c0 18-15 33-34 33-19 0-34-15-34-34 0-19 15-34 34-34s34 15 34 34z m0-283v0c0 19-15 34-34 34-19 0-34-15-34-34 0-19 15-35 34-35s34 16 34 35z m0 141v1c0 19-15 34-34 34-19 0-34-16-34-35 0-18 15-34 34-34s34 16 34 34z m0-283v1c0 18-15 33-34 33-19 0-34-15-34-34 0-19 15-34 34-34 19 0 34 15 34 34z m367 7c17 7 26 27 19 44l-1 1c-6 17-26 26-44 18-17-7-26-27-19-44 6-14 19-22 32-22 4 0 9 1 13 3z m-47 129c17 7 26 27 19 44l-1 1c-7 17-26 26-44 18-17-7-26-27-19-44 6-14 18-22 32-22 4 0 9 1 13 3z m-50 127c18 6 28 25 23 43l-1 1c-5 18-24 27-42 22-18-5-28-25-23-43 5-14 18-24 33-24 3 0 6 0 10 1z m-45 130c18 7 26 27 19 45l0 0c-7 18-27 26-44 19-17-7-26-27-19-45 6-13 18-21 32-21 4 0 8 1 12 2z m-59-408c18 4 30 22 27 40l-1 1c-3 18-21 30-40 26-18-3-30-22-26-40 3-17 17-28 33-28 2 0 5 0 7 1z m-25 135c19 2 33 19 31 37l0 1c-2 19-19 32-37 30-19-2-33-19-31-38 2-17 17-30 34-30 1 0 2 0 3 0z m-17 136c18 4 30 22 26 40l0 1c-3 18-21 30-40 26-18-4-30-22-27-40 4-17 18-28 34-28 2 0 4 0 7 1z m-21 135c18 4 30 22 26 41l0 0c-4 19-22 30-40 27-18-4-30-22-27-41 4-16 18-27 34-27 2 0 4 0 7 0z m-493-365l0 0c7 18-2 37-19 44-17 7-38-1-44-19-7-17 1-37 19-44 4-2 8-3 12-3 14 0 26 8 32 22z m47 129l0 0c7 18-1 37-19 44-17 7-37-1-44-19-7-18 1-37 19-44 4-2 8-3 12-3 14 0 27 8 32 22z m48 131l0 1c6 18-5 37-23 42-18 6-37-5-42-23-6-18 4-37 23-43 3-1 6-1 9-1 15 0 29 10 33 24z m46 126l0 1c7 17-1 37-19 44-17 7-37-2-44-20-7-17 1-37 19-44 4-1 8-2 12-2 14 0 27 8 32 21z m56-400l0 0c3 19-9 36-27 40-19 4-37-8-40-27-4-18 8-36 26-40 3-1 5-1 7-1 16 0 30 11 34 28z m21 138l0 1c2 19-12 35-30 37-19 2-36-12-38-31-2-18 12-35 31-37 1 0 2 0 3 0 18 0 32 13 34 30z m21 133l0 0c4 19-8 36-27 40-18 4-36-8-40-27-4-18 8-36 27-40 2-1 4-1 6-1 16 0 30 11 34 28z m21 135l0 1c4 18-8 36-27 40-18 3-36-9-40-27-4-19 8-37 27-41 2 0 4 0 6 0 16 0 31 11 34 27z" horiz-adv-x="1000" /> + +<glyph glyph-name="male" unicode="E" d="M571 457v-232q0-22-15-38t-38-16-38 16-16 38v196h-35v-509q0-25-19-44t-44-18-44 18-18 44v259h-36v-259q0-25-19-44t-44-18-44 18-18 44v509h-36v-196q0-22-15-38t-38-16-38 16-16 38v232q0 45 31 76t76 31h357q45 0 76-31t31-76z m-160 250q0-52-37-88t-88-37-89 37-36 88 36 89 89 36 88-36 37-89z" horiz-adv-x="571.4" /> + +<glyph glyph-name="female" unicode="F" d="M714 261q0-23-15-38t-38-16q-29 0-45 24l-127 190h-25v-73l138-230q5-8 5-18 0-14-10-25t-26-11h-107v-152q0-25-18-44t-44-18h-89q-26 0-45 18t-18 44v152h-107q-15 0-25 11t-11 25q0 10 5 18l138 230v73h-25l-127-190q-16-24-44-24-23 0-38 16t-16 38q0 16 9 29l143 215q41 59 98 59h214q58 0 99-59l142-215q9-13 9-29z m-232 446q0-52-36-88t-89-37-88 37-37 88 37 89 88 36 89-36 36-89z" horiz-adv-x="714.3" /> + +<glyph glyph-name="video" unicode="G" d="M214-43v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="videocam" unicode="H" d="M1000 654v-608q0-23-22-32-7-3-14-3-15 0-25 10l-225 225v-92q0-67-47-114t-113-47h-393q-67 0-114 47t-47 114v392q0 67 47 114t114 47h393q66 0 113-47t47-114v-92l225 225q10 10 25 10 7 0 14-2 22-10 22-33z" horiz-adv-x="1000" /> + +<glyph glyph-name="picture" unicode="I" d="M357 529q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="camera" unicode="J" d="M536 475q66 0 113-47t47-114-47-113-113-47-114 47-47 113 47 114 114 47z m393 232q59 0 101-42t41-101v-500q0-59-41-101t-101-42h-786q-59 0-101 42t-42 101v500q0 59 42 101t101 42h125l28 76q11 27 39 47t58 20h286q29 0 57-20t39-47l29-76h125z m-393-643q103 0 176 74t74 176-74 177-176 73-177-73-73-177 73-176 177-74z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="th-large" unicode="K" d="M429 279v-215q0-29-22-50t-50-21h-286q-29 0-50 21t-21 50v215q0 29 21 50t50 21h286q29 0 50-21t22-50z m0 428v-214q0-29-22-50t-50-22h-286q-29 0-50 22t-21 50v214q0 29 21 50t50 22h286q29 0 50-22t22-50z m500-428v-215q0-29-22-50t-50-21h-286q-29 0-50 21t-21 50v215q0 29 21 50t50 21h286q29 0 50-21t22-50z m0 428v-214q0-29-22-50t-50-22h-286q-29 0-50 22t-21 50v214q0 29 21 50t50 22h286q29 0 50-22t22-50z" horiz-adv-x="928.6" /> + +<glyph glyph-name="th" unicode="L" d="M286 154v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m0 285v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m357-285v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m-357 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m357-286v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m357-285v-108q0-22-16-37t-38-16h-178q-22 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m-357 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m357-286v-107q0-22-16-38t-38-15h-178q-22 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m0 286v-107q0-22-16-38t-38-16h-178q-22 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="th-list" unicode="M" d="M286 154v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m0 285v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m714-285v-108q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v108q0 22 16 38t38 15h535q23 0 38-15t16-38z m-714 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m714-286v-107q0-22-16-38t-38-15h-535q-23 0-38 15t-16 38v107q0 23 16 38t38 16h535q23 0 38-16t16-38z m0 286v-107q0-22-16-38t-38-16h-535q-23 0-38 16t-16 38v107q0 22 16 38t38 16h535q23 0 38-16t16-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="child" unicode="N" d="M663 544l-163-163v-460q0-25-18-44t-44-18-45 18-18 44v215h-36v-215q0-25-18-44t-44-18-44 18-19 44v460l-163 163q-15 16-15 38t15 38q17 16 39 16t37-16l128-127h205l127 127q16 16 38 16t38-16q16-16 16-38t-16-38z m-181 92q0-52-36-89t-89-36-88 36-37 89 37 88 88 37 89-37 36-88z" horiz-adv-x="714.3" /> + +<glyph glyph-name="ok" unicode="O" d="M933 534q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="ok-circled" unicode="P" d="M717 440q0 16-10 26l-51 50q-11 11-25 11t-25-11l-228-227-126 126q-11 11-25 11t-25-11l-51-50q-10-10-10-26 0-15 10-25l202-202q10-10 25-10 15 0 26 10l303 303q10 10 10 25z m140-90q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="ok-squared" unicode="Q" d="M382 125l343 343q11 10 11 25t-11 25l-57 57q-11 11-25 11t-25-11l-261-261-118 118q-10 11-25 11t-25-11l-57-57q-10-10-10-25t10-25l200-200q11-10 25-10t25 10z m475 493v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="cancel" unicode="R" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" /> + +<glyph glyph-name="plus" unicode="S" d="M786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" /> + +<glyph glyph-name="plus-circled" unicode="T" d="M679 314v72q0 14-11 25t-25 10h-143v143q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-143h-143q-14 0-25-10t-10-25v-72q0-14 10-25t25-10h143v-143q0-15 11-25t25-11h71q15 0 25 11t11 25v143h143q14 0 25 10t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="plus-squared" unicode="U" d="M714 314v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="plus-squared-alt" unicode="V" d="M643 404v-36q0-8-5-13t-13-5h-196v-196q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v196h-196q-8 0-13 5t-5 13v36q0 7 5 12t13 5h196v197q0 8 5 13t13 5h36q8 0 13-5t5-13v-197h196q8 0 13-5t5-12z m71-250v464q0 37-26 63t-63 26h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63z m72 464v-464q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q66 0 114-48t47-113z" horiz-adv-x="785.7" /> + +<glyph glyph-name="minus" unicode="W" d="M786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q23 0 38-16t16-38z" horiz-adv-x="785.7" /> + +<glyph glyph-name="minus-circled" unicode="X" d="M679 314v72q0 14-11 25t-25 10h-429q-14 0-25-10t-10-25v-72q0-14 10-25t25-10h429q14 0 25 10t11 25z m178 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="minus-squared" unicode="Y" d="M714 314v72q0 14-10 25t-25 10h-500q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h500q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="minus-squared-alt" unicode="Z" d="M643 404v-36q0-8-5-13t-13-5h-464q-8 0-13 5t-5 13v36q0 7 5 12t13 5h464q8 0 13-5t5-12z m71-250v464q0 37-26 63t-63 26h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63z m72 464v-464q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q66 0 114-48t47-113z" horiz-adv-x="785.7" /> + +<glyph glyph-name="left-open" unicode="[" d="M654 682l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" /> + +<glyph glyph-name="help-circled" unicode="\" d="M500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65-95 23q-136 0-207-119-9-13 4-24l74-55q4-4 10-4 9 0 14 7 30 38 48 51 19 14 48 14 27 0 48-15t21-33q0-21-11-34t-38-25q-35-15-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19 25 27 16 34 7 45z m214-107q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="right-open" unicode="]" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" /> + +<glyph glyph-name="info" unicode="^" d="M357 100v-71q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v71q0 15 11 25t25 11h35v214h-35q-15 0-25 11t-11 25v71q0 15 11 25t25 11h214q15 0 25-11t11-25v-321h35q15 0 26-11t10-25z m-71 643v-107q0-15-11-25t-25-11h-143q-14 0-25 11t-11 25v107q0 14 11 25t25 11h143q15 0 25-11t11-25z" horiz-adv-x="357.1" /> + +<glyph glyph-name="home" unicode="_" d="M786 296v-267q0-15-11-25t-25-11h-214v214h-143v-214h-214q-15 0-25 11t-11 25v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-3-7 1-12 6l-35 41q-4 6-3 13t6 12l401 334q18 15 42 15t43-15l136-113v108q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q6-4 6-12t-4-13z" horiz-adv-x="928.6" /> + +<glyph glyph-name="link" unicode="`" d="M813 171q0 23-16 38l-116 116q-16 16-38 16-24 0-40-18 1-1 10-10t12-12 9-11 7-14 2-15q0-23-16-38t-38-16q-8 0-15 2t-14 7-11 9-12 12-10 10q-19-17-19-40 0-23 16-38l115-116q15-15 38-15 22 0 38 15l82 81q16 16 16 37z m-393 394q0 22-15 38l-115 115q-16 16-38 16-22 0-38-15l-82-82q-16-15-16-37 0-22 16-38l116-116q15-15 38-15 23 0 40 17-2 2-11 11t-12 12-8 10-7 14-2 16q0 22 15 38t38 15q9 0 16-2t14-7 11-8 12-12 10-11q18 17 18 41z m500-394q0-66-48-113l-82-81q-46-47-113-47-68 0-114 48l-115 115q-46 47-46 114 0 68 49 116l-49 49q-48-49-116-49-67 0-114 47l-116 116q-47 47-47 114t47 113l82 82q47 46 114 46 67 0 114-47l115-116q46-46 46-113 0-69-49-117l49-49q48 49 116 49 67 0 114-47l116-116q47-47 47-114z" horiz-adv-x="928.6" /> + +<glyph glyph-name="unlink" unicode="a" d="M245 141l-143-143q-6-5-13-5t-12 5q-6 6-6 13t6 13l142 142q6 5 13 5t13-5q5-5 5-12t-5-13z m94-23v-179q0-8-5-13t-13-5-12 5-5 13v179q0 8 5 13t12 5 13-5 5-13z m-125 125q0-8-5-13t-13-5h-178q-8 0-13 5t-5 13 5 13 13 5h178q8 0 13-5t5-13z m706-72q0-66-48-113l-82-81q-46-47-113-47-68 0-114 48l-186 187q-12 12-24 31l134 10 152-153q15-15 38-15t38 15l82 81q16 16 16 37 0 23-16 38l-153 154 10 133q20-11 31-23l188-188q47-48 47-114z m-345 404l-133-10-152 153q-16 16-38 16-22 0-38-15l-82-82q-16-15-16-37 0-22 16-38l153-153-10-134q-20 12-32 24l-187 187q-47 48-47 114 0 67 47 113l82 82q47 46 114 46 67 0 114-47l186-187q12-12 23-32z m354-46q0-8-5-13t-13-5h-179q-8 0-13 5t-5 13 5 12 13 5h179q8 0 13-5t5-12z m-304 303v-178q0-8-5-13t-13-5-13 5-5 13v178q0 8 5 13t13 5 13-5 5-13z m227-84l-143-143q-6-5-13-5t-12 5q-5 6-5 13t5 13l143 143q5 5 12 5t13-5q5-6 5-13t-5-13z" horiz-adv-x="928.6" /> + +<glyph glyph-name="link-ext" unicode="b" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="link-ext-alt" unicode="c" d="M714 332v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="lock" unicode="e" d="M179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" /> + +<glyph glyph-name="lock-open" unicode="f" d="M929 529v-143q0-15-11-25t-25-11h-36q-14 0-25 11t-11 25v143q0 59-41 101t-101 41-101-41-42-101v-108h53q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h375v108q0 103 73 176t177 74 176-74 74-176z" horiz-adv-x="928.6" /> + +<glyph glyph-name="cancel-circled" unicode="g" d="M641 224q0 14-10 25l-101 101 101 101q10 11 10 25 0 15-10 26l-51 50q-10 11-25 11-15 0-25-11l-101-101-101 101q-11 11-25 11-16 0-26-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 26-11 14 0 25 11l101 101 101-101q10-11 25-11 15 0 25 11l51 50q10 11 10 26z m216 126q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="lock-open-alt" unicode="h" d="M589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" /> + +<glyph glyph-name="meteor" unicode="i" d="M2 848l872-923c0 0 29-21 52 3 23 25 5 49 5 49l-929 871 0 0z m276-87l664-716c0 0 30-21 52 4 23 24 6 49 6 49l-722 663 0 0z m-194-187l664-716c0 0 30-21 52 4 23 24 6 49 6 49l-722 663 0 0z m427 112l464-500c0 0 21-15 37 2 16 17 3 34 3 34l-504 464 0 0z m-363-328l464-501c0 0 20-14 36 3 16 17 4 34 4 34l-504 464 0 0z m602 220l210-228c0 0 10-7 18 1 8 8 2 16 2 16l-230 211 0 0z m-486-451l210-227c0 0 10-7 18 1 8 8 2 16 2 16l-230 210 0 0z" horiz-adv-x="1020" /> + +<glyph glyph-name="pin" unicode="j" d="M268 368v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" /> + +<glyph glyph-name="eye" unicode="k" d="M929 314q-85 132-213 197 34-58 34-125 0-103-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 243 68 186 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" /> + +<glyph glyph-name="eye-off" unicode="l" d="M310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" /> + +<glyph glyph-name="gitlab" unicode="m" d="M962 288c-42 131-85 262-127 393-9 28-18 55-27 83-6 16-29 18-34 0-3-9-6-18-9-26-27-84-55-168-82-252-4-11-5-38-19-38-34 0-68 0-102 0-78 0-155 0-232 0-6 0-21 59-23 65-14 45-29 90-43 134-8 23-15 46-22 68-5 15-8 37-17 50-6 21-30 11-35-5-44-137-89-274-133-411-10-28-38-79-7-101 11-11 26-19 39-28 71-52 143-104 215-157 61-44 123-89 184-134 16-11 15-6 31 7 36 25 71 51 106 76 108 79 216 157 324 236 12 9 18 24 13 40z" horiz-adv-x="1000" /> + +<glyph glyph-name="rocketchat" unicode="n" d="M969 351c0 47-14 93-41 135-25 37-60 71-104 99-85 54-197 84-315 84-39 0-78-4-115-10-24 22-51 41-80 57-154 75-282 2-282 2s119-98 100-184c-54-53-83-116-83-183 0 0 0 0 0 0 0-1 0-1 0-1 0-66 29-130 83-183 19-85-100-183-100-183s128-73 282 1c29 16 56 36 80 58 37-7 76-10 115-10 118 0 230 30 315 84 44 28 79 61 104 99 27 42 41 87 41 134 0 0 0 0 0 1 0 0 0 0 0 0z m-459-258c-50 0-97 5-141 16-45-54-143-128-238-104 31 33 77 89 67 182-57 44-91 101-91 163 0 142 180 257 403 257 222 0 402-115 402-257 0-142-180-257-402-257z m53 257c0-29-24-53-53-53-30 0-54 24-54 53s24 54 54 54c29 0 53-24 53-54z m133 54c-30 0-54-24-54-54s24-53 54-53c29 0 53 24 53 53s-24 54-53 54z m-373 0c-29 0-53-24-53-54s24-53 53-53c30 0 54 24 54 53s-24 54-54 54z" horiz-adv-x="1000" /> + +<glyph glyph-name="repo" unicode="o" d="M250 288h-62v62h62v-62z m0 187h-62v-62h62v62z m0 125h-62v-62h62v62z m0 125h-62v-62h62v62z m500 63v-750c0-35-28-63-62-63h-313v-125l-94 94-93-94v125h-125c-35 0-63 28-63 63v750c0 34 28 62 63 62h625c34 0 62-28 62-62z m-62-625h-625v-125h125v62h187v-62h313v125z m0 625h-563v-563h563v563z" horiz-adv-x="750" /> + +<glyph glyph-name="tag" unicode="p" d="M250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" /> + +<glyph glyph-name="spin" unicode="q" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" /> + +<glyph glyph-name="firefox" unicode="r" d="M504-137c-216 0-387 126-466 306-87 200-17 521 139 663l-6-156c8 9 68 12 78-1 32 62 136 109 220 110-32-26-106-124-99-174 40-13 103-13 136-15 10-6 8-40-12-68-27-36-97-49-97-49l8-106-77 38c-26-64 35-120 97-110 69 12 94 57 142 54 49-2 68-29 61-54-7-30-59-25-59-25-44-70-103-100-198-92 144-119 338-11 387 86 50 97 7 242-43 283 59-25 99-51 120-107 11 124-46 266-149 349 193-56 311-205 314-444 3-239-211-488-496-488z" horiz-adv-x="1000" /> + +<glyph glyph-name="tags" unicode="s" d="M250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z m215 0q0-30-21-51l-274-274q-22-21-51-21-20 0-33 8t-29 25l262 262q21 21 21 51 0 29-21 50l-399 399q-21 21-57 36t-65 15h125q29 0 65-15t57-36l399-399q21-21 21-50z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="bookmark" unicode="t" d="M650 779q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" /> + +<glyph glyph-name="bookmark-empty" unicode="u" d="M643 707h-572v-693l237 227 49 47 50-47 236-227v693z m7 72q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" /> + +<glyph glyph-name="flag" unicode="v" d="M179 707q0-40-36-61v-707q0-7-5-12t-13-6h-36q-7 0-12 6t-6 12v707q-35 21-35 61 0 30 21 51t50 21 51-21 21-51z m821-36v-425q0-14-7-22t-22-15q-120-65-206-65-34 0-69 12t-60 27-65 27-79 12q-107 0-259-81-10-5-19-5-14 0-25 10t-10 25v415q0 17 17 30 12 8 44 24 132 67 235 67 60 0 112-16t122-49q21-11 49-11 30 0 65 12t62 26 49 26 30 12q15 0 25-10t11-26z" horiz-adv-x="1000" /> + +<glyph glyph-name="flag-empty" unicode="w" d="M929 267v344q-95-51-171-51-46 0-81 18-56 27-103 42t-99 16q-97 0-225-71v-334q137 63 242 63 30 0 57-4t55-15 43-17 46-22l16-8q24-12 56-12 67 0 164 51z m-750 440q0-19-10-36t-26-25v-707q0-8-5-13t-13-5h-36q-7 0-12 5t-6 13v707q-16 9-25 25t-10 36q0 30 21 51t50 21 51-21 21-51z m821-36v-425q0-22-19-32-6-3-10-5-122-65-206-65-49 0-88 20l-16 7q-35 19-55 27t-51 16-63 8q-57 0-132-24t-127-57q-9-5-19-5-9 0-18 4-17 11-17 31v415q0 19 17 30 19 12 44 24t63 29 85 28 87 10q62 0 117-17t116-48q21-11 50-11 68 0 173 63 12 6 17 9 17 9 35-1 17-11 17-31z" horiz-adv-x="1000" /> + +<glyph glyph-name="flag-checkered" unicode="x" d="M464 292v107q-101-9-214-65v-103q114 53 214 61z m0 233v110q-96-4-214-70v-106q120 62 214 66z m465-258v103q-132-65-215-40v125q-11 3-21 8-3 2-19 10t-19 9-18 9-19 8-18 8-20 7-20 4-22 4-22 3-24 1q-13 0-28-2v-124h11q57 0 107-16t111-46q10-5 21-8v-105q24-9 51-9 67 0 164 51z m0 238v106q-95-51-171-51-25 0-44 4v-109q83-23 215 50z m-750 202q0-19-10-36t-26-25v-707q0-8-5-13t-13-5h-36q-7 0-12 5t-6 13v707q-16 9-25 25t-10 36q0 30 21 51t50 21 51-21 21-51z m821-36v-425q0-22-19-32-6-3-10-5-122-65-206-65-49 0-88 20l-16 7q-35 19-55 27t-51 16-63 8q-57 0-132-24t-127-57q-9-5-19-5-9 0-18 4-17 11-17 31v415q0 19 17 30 19 12 44 24t63 29 85 28 87 10q62 0 117-17t116-48q21-11 50-11 68 0 173 63 12 6 17 9 17 9 35-1 17-11 17-31z" horiz-adv-x="1000" /> + +<glyph glyph-name="thumbs-up" unicode="y" d="M143 100q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m643 321q0 29-22 50t-50 22h-196q0 32 27 89t26 89q0 55-17 81t-72 27q-14-15-21-48t-17-70-33-61q-13-13-43-51-2-3-13-16t-18-23-19-24-22-25-22-19-22-15-20-6h-18v-357h18q7 0 18-1t18-4 21-6 20-7 20-6 16-6q118-41 191-41h67q107 0 107 93 0 15-2 31 16 9 26 30t10 41-10 38q29 28 29 67 0 14-5 31t-14 26q18 1 30 26t12 45z m71 1q0-50-27-91 5-18 5-38 0-43-21-81 1-12 1-24 0-56-33-99 0-78-48-123t-126-45h-72q-54 0-106 13t-121 36q-65 23-77 23h-161q-29 0-50 21t-21 50v357q0 30 21 51t50 21h153q20 13 77 86 32 42 60 72 13 14 19 48t17 70 35 60q22 21 50 21 47 0 84-18t57-57 20-104q0-51-27-107h98q58 0 101-42t42-100z" horiz-adv-x="857.1" /> + +<glyph glyph-name="thumbs-down" unicode="z" d="M143 600q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m643-321q0 19-12 45t-30 26q8 10 14 27t5 31q0 38-29 66 10 17 10 38 0 21-10 41t-26 30q2 16 2 31 0 47-27 70t-76 23h-71q-73 0-191-41-3-1-16-5t-20-7-20-7-21-6-18-4-18-1h-18v-357h18q9 0 20-5t22-15 22-20 22-25 19-24 18-22 13-17q30-38 43-51 23-24 33-61t17-70 21-48q54 0 72 27t17 81q0 33-26 89t-27 89h196q28 0 50 22t22 50z m71-1q0-57-42-100t-101-42h-98q27-55 27-107 0-66-20-104-19-39-57-57t-84-18q-28 0-50 21-19 18-30 45t-14 51-10 47-17 36q-27 28-60 71-57 73-77 86h-153q-29 0-50 21t-21 51v357q0 29 21 50t50 21h161q12 0 77 23 72 24 125 36t111 13h63q78 0 126-44t48-121v-3q33-43 33-99 0-12-1-24 21-38 21-80 0-21-5-39 27-41 27-91z" horiz-adv-x="857.1" /> + +<glyph glyph-name="angle-left" unicode="{" d="M350 546q0-7-6-12l-219-220 219-219q6-6 6-13t-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="357.1" /> + +<glyph glyph-name="checklist" unicode="|" d="M1000 319l-375-375-187 187 93 94 94-94 281 282 94-94z m-644-232l50-49h-281c-34 0-62 28-62 62v563c0 34 28 62 62 62h438c34 0 62-28 62-62v-407l-50 50c-24 25-64 25-89 0l-130-131c-24-24-24-63 0-88z m-106 513h313v63h-313v-63z m0-125h313v63h-313v-63z m0-125h188v63h-188v-63z m-62-62h-63v-63h63v63z m0 125h-63v-63h63v63z m0 125h-63v-63h63v63z m0 125h-63v-63h63v63z" horiz-adv-x="1000" /> + +<glyph glyph-name="angle-right" unicode="}" d="M332 314q0-7-5-12l-261-261q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l261-260q5-5 5-13z" horiz-adv-x="357.1" /> + +<glyph glyph-name="upload" unicode="~" d="M714 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" /> + +<glyph glyph-name="angle-circled-left" unicode="«" d="M507 72l57 57q11 10 11 25t-11 25l-171 171 171 171q11 11 11 25t-11 26l-57 57q-10 10-25 10t-25-10l-253-254q-11-10-11-25t11-25l253-253q11-11 25-11t25 11z m350 278q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="angle-circled-right" unicode="»" d="M400 72l254 253q10 11 10 25t-10 25l-254 254q-10 10-25 10t-25-10l-57-57q-11-11-11-26t11-25l171-171-171-171q-11-11-11-25t11-25l57-57q11-11 25-11t25 11z m457 278q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="angle-double-left" unicode="‹" d="M350 82q0-7-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13z m214 0q0-7-5-13l-28-28q-6-5-13-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q6 6 13 6t13-6l28-28q5-5 5-13t-5-12l-220-220 220-219q5-6 5-13z" horiz-adv-x="571.4" /> + +<glyph glyph-name="angle-double-right" unicode="›" d="M332 314q0-7-5-12l-261-261q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l261-260q5-5 5-13z m214 0q0-7-5-12l-260-261q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13z" horiz-adv-x="571.4" /> + +<glyph glyph-name="right" unicode="≤" d="M1000 404v-108q0-7-5-12t-13-5h-696v-125q0-12-11-17t-19 3l-215 196q-5 5-5 12 0 8 5 14l215 197q9 8 19 4 11-5 11-17v-125h696q8 0 13-5t5-12z" horiz-adv-x="1000" /> + +<glyph glyph-name="left" unicode="≥" d="M964 352q0-8-5-14l-215-197q-8-8-19-4-11 5-11 17v125h-696q-8 0-13 5t-5 12v108q0 7 5 12t13 5h696v125q0 12 11 17t19-3l215-195q5-6 5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="left-big" unicode="≦" d="M857 350v-71q0-30-18-51t-47-21h-393l164-164q21-20 21-50t-21-50l-42-43q-21-20-51-20-29 0-50 20l-364 364q-20 21-20 50 0 29 20 51l364 363q21 21 50 21 29 0 51-21l42-41q21-22 21-51t-21-51l-164-164h393q29 0 47-20t18-51z" horiz-adv-x="857.1" /> + +<glyph glyph-name="right-big" unicode="≧" d="M821 314q0-30-20-50l-363-364q-22-20-51-20-29 0-50 20l-42 42q-22 21-22 51t22 51l163 163h-393q-29 0-47 21t-18 51v71q0 30 18 51t47 20h393l-163 165q-22 20-22 50t22 50l42 42q21 21 50 21 29 0 51-21l363-363q20-20 20-51z" horiz-adv-x="857.1" /> + +<glyph glyph-name="right-hand" unicode="≨" d="M143 100q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m786 321q0 29-22 50t-50 22h-321q0 11 8 27t19 31 18 38 8 47q0 37-24 54t-65 17q-13 0-50-77-14-25-21-37-22-35-62-81-40-45-57-59-38-32-78-32h-18v-357h18q40 0 93-18t108-35 101-18q105 0 105 93 0 15-3 31 17 9 27 30t9 41-10 38q30 28 30 67 0 14-6 31t-14 26h185q29 0 50 21t22 50z m71 1q0-59-42-101t-101-42h-94q-2-35-21-67 2-12 2-24 0-56-34-99 1-78-47-123t-127-45q-74 0-179 39-92 33-125 33h-161q-29 0-50 21t-21 50v357q0 30 21 51t50 21h161q6 0 12 2t13 8 13 10 13 13 12 12 10 12 8 9q36 42 56 72 7 12 18 35t21 40 23 35 30 28 39 10q70 0 115-38t46-105q0-38-13-72h209q58 0 101-42t42-100z" horiz-adv-x="1000" /> + +<glyph glyph-name="left-hand" unicode="≩" d="M768 64h18v357h-18q-20 0-38 7t-35 21-28 25-27 31q-4 5-7 7-40 46-62 81-8 13-21 38-1 2-6 13t-10 20-12 20-12 17-10 6q-40 0-64-17t-25-54q0-24 8-47t19-38 18-31 8-27h-321q-28 0-50-22t-22-50q0-29 22-50t50-21h185q-9-9-14-26t-6-31q0-39 30-67-10-18-10-38t9-41 27-30q-2-13-2-31 0-47 27-70t75-23q47 0 102 18t109 35 93 18z m161 36q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m71 321v-357q0-29-21-50t-50-21h-161q-33 0-125-33-106-39-176-39-80 0-129 44t-48 121l0 3q-34 42-34 99 0 12 2 24-19 32-21 67h-94q-59 0-101 42t-42 101q0 58 42 100t101 42h209q-13 34-13 72 0 68 46 105t115 38q21 0 39-10t31-28 22-35 21-40 18-35q20-30 56-72 1-1 8-9t10-12 12-12 13-13 13-10 13-8 12-2h161q29 0 50-21t21-51z" horiz-adv-x="1000" /> + +<glyph glyph-name="left-circled" unicode="≪" d="M714 314v72q0 14-10 25t-25 10h-281l106 106q11 11 11 25t-11 25l-51 51q-10 10-25 10t-25-10l-202-202-51-51q-10-10-10-25t10-25l51-51 202-202q10-10 25-10t25 10l51 51q10 10 10 25t-10 25l-106 106h281q14 0 25 10t10 25z m143 36q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="right-circled" unicode="≫" d="M717 350q0 15-10 25l-51 51-202 202q-10 10-25 10t-25-10l-51-51q-10-10-10-25t10-25l106-106h-280q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h280l-106-106q-10-10-10-25t10-25l51-51q10-10 25-10t25 10l202 202 51 51q10 10 10 25z m140 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="expand-right" unicode="≶" d="M607 350q0-18-15-29l-250-179q-17-12-37-2-19 9-19 31v358q0 22 19 31 20 10 37-2l250-179q15-11 15-29z m107-268v536q0 8-5 13t-13 5h-535q-8 0-13-5t-5-13v-536q0-8 5-13t13-5h535q8 0 13 5t5 13z m143 536v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="collapse-left" unicode="≷" d="M571 529v-358q0-14-10-25t-25-10q-11 0-21 6l-250 179q-15 11-15 29t15 29l250 179q10 6 21 6 14 0 25-10t10-25z m143-447v536q0 7-5 12t-13 6h-535q-7 0-13-6t-5-12v-536q0-7 5-12t13-6h535q8 0 13 6t5 12z m143 536v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="right-open-small" unicode="⊢" d="M98 626l226-236q16-16 16-40 0-22-16-38l-226-236q-16-16-40-16t-40 16q-36 36 0 80l186 194-186 196q-36 44 0 80 16 16 41 16t39-16z" horiz-adv-x="340" /> + +<glyph glyph-name="left-open-small" unicode="⊣" d="M242 626q14 16 39 16t41-16q38-36 0-80l-186-196 186-194q38-44 0-80-16-16-40-16t-40 16l-226 236q-16 16-16 38 0 24 16 40 206 214 226 236z" horiz-adv-x="341" /> + +<glyph glyph-name="download-cloud" unicode="" d="M714 332q0 8-5 13t-13 5h-125v196q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-196h-125q-8 0-13-5t-5-13q0-8 5-13l196-196q5-5 13-5t13 5l196 196q5 6 5 13z m357-125q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="upload-cloud" unicode="" d="M714 368q0 8-5 13l-196 196q-5 5-13 5t-13-5l-196-196q-5-6-5-13 0-8 5-13t13-5h125v-196q0-8 5-13t12-5h108q7 0 12 5t5 13v196h125q8 0 13 5t5 13z m357-161q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24 0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35 59 0 101-42t42-101q0-43-23-77 72-17 119-76t46-133z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="reply" unicode="" d="M1000 225q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" /> + +<glyph glyph-name="reply-all" unicode="" d="M357 246v-39q0-23-22-33-7-3-14-3-15 0-25 11l-285 286q-11 10-11 25t11 25l285 286q17 17 39 8 22-10 22-33v-39l-221-222q-11-11-11-25t11-25z m643-21q0-32-9-74t-22-77-27-70-22-51l-11-22q-5-10-16-10-3 0-5 1-14 4-13 19 24 223-59 315-36 40-95 62t-150 29v-140q0-23-21-33-8-3-14-3-15 0-25 11l-286 286q-11 10-11 25t11 25l286 286q16 17 39 8 21-10 21-33v-147q230-15 335-123 94-96 94-284z" horiz-adv-x="1000" /> + +<glyph glyph-name="forward" unicode="" d="M1000 493q0-15-11-25l-285-286q-11-11-25-11t-25 11-11 25v143h-125q-55 0-98-3t-86-12-74-24-59-39-45-56-27-77-10-101q0-31 3-69 0-4 2-13t1-15q0-8-5-14t-13-6q-9 0-15 10-4 5-8 12t-7 17-6 13q-71 159-71 252 0 111 30 186 90 225 488 225h125v143q0 14 11 25t25 10 25-10l285-286q11-11 11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="quote-left" unicode="" d="M429 314v-214q0-45-32-76t-76-31h-214q-44 0-76 31t-31 76v393q0 58 23 111t61 91 91 61 111 23h35q15 0 26-11t10-25v-72q0-14-10-25t-26-10h-35q-59 0-101-42t-42-101v-18q0-22 16-38t37-16h125q45 0 76-31t32-76z m500 0v-214q0-45-32-76t-76-31h-214q-44 0-76 31t-31 76v393q0 58 23 111t61 91 91 61 111 23h35q15 0 26-11t10-25v-72q0-14-10-25t-26-10h-35q-59 0-101-42t-42-101v-18q0-22 16-38t37-16h125q45 0 76-31t32-76z" horiz-adv-x="928.6" /> + +<glyph glyph-name="quote-right" unicode="" d="M429 671v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z m500 0v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z" horiz-adv-x="928.6" /> + +<glyph glyph-name="code" unicode="" d="M344 69l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13t-6-13z m330 596l-208-721q-2-7-9-11t-13-1l-34 9q-8 3-11 9t-2 14l209 720q2 8 8 11t13 2l35-10q7-2 11-9t1-13z m367-363l-260-261q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13t-5-12z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="export" unicode="" d="M786 298v-144q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h142q7 0 13-6t5-12q0-15-15-18-43-15-74-34-5-2-9-2h-62q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v119q0 11 10 16 16 7 31 21 8 9 19 4 12-5 12-16z m132 277l-214-214q-10-11-25-11-7 0-14 3-22 9-22 33v107h-89q-181 0-245-73-66-77-41-264 2-13-11-19-5-1-7-1-9 0-14 7-6 8-12 17t-22 39-28 55-21 64-10 68q0 27 2 51t8 50 15 49 27 45 38 42 52 34 70 27 89 17 110 6h89v107q0 24 22 33 7 3 14 3 14 0 25-11l214-214q11-10 11-25t-11-25z" horiz-adv-x="928.6" /> + +<glyph glyph-name="export-alt" unicode="" d="M561 236l196 196q11 11 11 25t-11 25l-196 197q-17 17-39 8-22-10-22-33v-90q-66 0-120-11t-91-28-64-44-42-53-25-61-12-62-3-62q0-101 93-226 6-6 14-6 4 0 7 1 13 5 11 19-25 197 35 264 25 29 72 42t125 13v-89q0-24 22-33 7-3 14-3 14 0 25 11z m296 382v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="share" unicode="" d="M679 279q74 0 126-53t52-126-52-126-126-53-127 53-52 126q0 7 1 19l-201 100q-51-48-121-48-75 0-127 53t-52 126 52 126 127 53q70 0 121-48l201 100q-1 12-1 19 0 74 52 126t127 53 126-53 52-126-52-126-126-53q-71 0-122 48l-201-100q1-12 1-19t-1-19l201-100q51 48 122 48z" horiz-adv-x="857.1" /> + +<glyph glyph-name="share-squared" unicode="" d="M714 183q0 49-35 84t-84 36q-46 0-80-33l-135 67q1 9 1 13t-1 13l135 67q34-33 80-33 50 0 84 36t35 84-35 84-84 35-84-35-35-84q0-4 1-13l-134-67q-35 32-81 32-49 0-84-35t-35-84 35-84 84-35q46 0 81 32l134-67q-1-9-1-13 0-49 35-84t84-35 84 35 35 84z m143 435v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="pencil" unicode="" d="M203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" /> + +<glyph glyph-name="pencil-squared" unicode="" d="M225 232l85-85-29-29h-31v53h-54v32z m232 217q7-7-2-16l-163-163q-9-9-16-1-8 7 1 16l163 163q9 9 17 1z m-153-385l303 304-161 161-303-304v-161h161z m339 340l51 51q16 16 16 38t-16 38l-85 85q-15 15-38 15t-37-15l-52-52z m214 214v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="edit" unicode="" d="M496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="print" unicode="" d="M214-7h500v143h-500v-143z m0 357h500v214h-89q-22 0-38 16t-16 38v89h-357v-357z m643-36q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m72 0v-232q0-7-6-12t-12-6h-125v-89q0-22-16-38t-38-16h-536q-22 0-37 16t-16 38v89h-125q-7 0-13 6t-5 12v232q0 44 32 76t75 31h36v304q0 22 16 38t37 16h375q23 0 50-12t42-26l85-85q15-16 27-43t11-49v-143h35q45 0 76-31t32-76z" horiz-adv-x="928.6" /> + +<glyph glyph-name="retweet" unicode="" d="M714 11q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="keyboard" unicode="" d="M214 198v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m72 143v-53q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h125q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m572-286v-53q0-9-9-9h-482q-9 0-9 9v53q0 9 9 9h482q9 0 9-9z m-357 143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-8-9h-54q-9 0-9 9v53q0 9 9 9h54q8 0 8-9z m-71 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m215-143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-286 286v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-196q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h62v134q0 9 9 9h54q9 0 9-9z m71-420v500h-929v-500h929z m71 500v-500q0-29-20-50t-51-21h-929q-29 0-50 21t-21 50v500q0 30 21 51t50 21h929q30 0 51-21t20-51z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="gamepad" unicode="" d="M464 243v71q0 8-5 13t-13 5h-107v107q0 8-5 13t-13 5h-71q-8 0-13-5t-5-13v-107h-107q-8 0-13-5t-5-13v-71q0-8 5-13t13-5h107v-107q0-8 5-13t13-5h71q8 0 13 5t5 13v107h107q8 0 13 5t5 13z m322-36q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m143 143q0 30-21 51t-51 20-50-20-21-51 21-50 50-21 51 21 21 50z m142-71q0-119-83-202t-202-84q-107 0-189 71h-123q-81-71-188-71-119 0-202 84t-84 202 84 202 202 83h500q118 0 202-83t83-202z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="comment" unicode="" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" /> + +<glyph glyph-name="chat" unicode="" d="M786 421q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" /> + +<glyph glyph-name="comment-empty" unicode="" d="M500 636q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" /> + +<glyph glyph-name="chat-empty" unicode="" d="M393 636q-85 0-160-29t-118-79-44-107q0-45 30-88t83-73l54-32-19-46q19 11 34 21l25 18 30-6q43-8 85-8 85 0 160 29t118 79 43 106-43 107-118 79-160 29z m0 71q106 0 197-38t143-104 53-144-53-143-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38z m459-652q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128q0-67-40-126t-108-98z" horiz-adv-x="1000" /> + +<glyph glyph-name="bell" unicode="" d="M509-96q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-372 160h726q-149 168-149 465 0 28-13 58t-39 58-67 45-95 17-95-17-67-45-39-58-13-58q0-297-149-465z m827 0q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" /> + +<glyph glyph-name="bell-alt" unicode="" d="M509-96q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" /> + +<glyph glyph-name="bell-off" unicode="" d="M869 375q35-199 167-311 0-29-21-50t-51-21h-250q0-59-42-101t-101-42-100 42-42 100z m-298-480q9 0 9 9t-9 8q-32 0-56 24t-24 57q0 9-9 9t-9-9q0-41 29-70t69-28z m560 893q4-6 4-14t-6-12l-1045-905q-5-5-13-4t-12 6l-47 53q-4 6-4 14t6 12l104 90q-11 17-11 36 28 24 51 49t47 67 42 89 28 115 11 145q0 84 65 157t171 89q-4 10-4 21 0 23 16 38t37 16 38-16 16-38q0-11-4-21 69-10 122-46t82-88l234 202q5 5 13 4t12-6z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="bell-off-empty" unicode="" d="M580-96q0 8-9 8-32 0-56 24t-24 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-299 265l489 424q-23 49-74 82t-125 32q-51 0-94-17t-68-45-38-58-14-58q0-215-76-360z m755-105q0-29-21-50t-51-21h-250q0-59-42-101t-101-42-100 42-42 100l83 72h422q-92 105-126 256l61 55q35-199 167-311z m48 777l47-53q4-6 4-14t-6-12l-1045-905q-5-5-13-4t-12 6l-47 53q-4 6-4 14t6 12l104 90q-11 17-11 36 28 24 51 49t47 67 42 89 28 115 11 145q0 84 65 157t171 89q-4 10-4 21 0 23 16 38t37 16 38-16 16-38q0-11-4-21 69-10 122-46t82-88l234 202q5 5 13 4t12-6z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="attention-alt" unicode="" d="M286 154v-125q0-15-11-25t-25-11h-143q-14 0-25 11t-11 25v125q0 14 11 25t25 10h143q15 0 25-10t11-25z m17 589l-16-429q-1-14-12-25t-25-10h-143q-14 0-25 10t-12 25l-15 429q-1 14 10 25t24 11h179q14 0 25-11t10-25z" horiz-adv-x="357.1" /> + +<glyph glyph-name="attention" unicode="" d="M571 83v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" /> + +<glyph glyph-name="attention-circled" unicode="" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m71-696v106q0 8-5 13t-12 5h-107q-8 0-13-5t-6-13v-106q0-8 6-13t13-6h107q7 0 12 6t5 13z m-1 192l10 346q0 7-6 10-5 5-13 5h-123q-8 0-13-5-6-3-6-10l10-346q0-6 5-10t14-4h103q8 0 13 4t6 10z" horiz-adv-x="857.1" /> + +<glyph glyph-name="location" unicode="" d="M429 493q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m142 0q0-61-18-100l-203-432q-9-18-27-29t-37-11-38 11-26 29l-204 432q-18 39-18 100 0 118 84 202t202 84 202-84 83-202z" horiz-adv-x="571.4" /> + +<glyph glyph-name="direction" unicode="" d="M782 655l-357-714q-10-20-32-20-3 0-8 1-13 3-20 13t-8 22v322h-321q-13 0-22 7t-13 20 2 23 17 17l714 357q7 4 16 4 15 0 25-10 9-8 10-20t-3-22z" horiz-adv-x="785.7" /> + +<glyph glyph-name="compass" unicode="" d="M357 243l143 71-143 72v-143z m214 330v-303l-285-143v303z m161-223q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="trash-alt" unicode="" d="M286 82v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m143 0v393q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q8 0 13 5t5 13z m142 0v393q0 8-5 13t-12 5h-36q-8 0-13-5t-5-13v-393q0-8 5-13t13-5h36q7 0 12 5t5 13z m-303 554h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" /> + +<glyph glyph-name="trash" unicode="" d="M286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" /> + +<glyph glyph-name="doc" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z" horiz-adv-x="857.1" /> + +<glyph glyph-name="docs" unicode="" d="M946 636q23 0 38-16t16-38v-678q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v160h-303q-23 0-38 16t-16 38v375q0 22 11 49t27 42l228 228q15 16 42 27t49 11h232q23 0 38-16t16-38v-183q38 23 71 23h232z m-303-119l-167-167h167v167z m-357 214l-167-167h167v167z m109-361l176 176v233h-214v-233q0-22-15-37t-38-16h-233v-357h286v143q0 22 11 49t27 42z m534-449v643h-215v-232q0-22-15-38t-38-15h-232v-358h500z" horiz-adv-x="1000" /> + +<glyph glyph-name="doc-text" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" /> + +<glyph glyph-name="doc-inv" unicode="" d="M571 564v264q13-8 21-16l227-228q8-7 16-20h-264z m-71-18q0-22 16-37t38-16h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304z" horiz-adv-x="857.1" /> + +<glyph glyph-name="doc-text-inv" unicode="" d="M819 584q8-7 16-20h-264v264q13-8 21-16z m-265-91h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304q0-22 16-37t38-16z m89-411v36q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v36q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-393q-8 0-13-5t-5-12v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="folder" unicode="" d="M929 511v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" /> + +<glyph glyph-name="folder-open" unicode="" d="M1049 319q0-17-18-37l-187-221q-24-28-67-48t-81-20h-607q-19 0-33 7t-15 24q0 17 17 37l188 221q24 28 67 48t80 20h607q19 0 34-7t15-24z m-192 192v-90h-464q-53 0-110-26t-92-67l-188-221-2-3q0 2-1 7t0 7v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q52 0 88-37t37-88z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="folder-empty" unicode="" d="M857 118v393q0 22-15 38t-38 15h-393q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-536q0-22 16-38t38-16h679q22 0 38 16t15 38z m72 393v-393q0-51-37-88t-88-37h-679q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h375q51 0 88-37t37-88z" horiz-adv-x="928.6" /> + +<glyph glyph-name="folder-open-empty" unicode="" d="M994 331q0 19-30 19h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q23 0 48 13t40 29l164 203q10 12 10 22z m-637 90h429v90q0 22-16 38t-38 15h-321q-23 0-38 16t-16 38v36q0 22-15 38t-38 15h-179q-22 0-38-15t-16-38v-476l143 175q25 30 65 49t78 19z m708-90q0-35-25-67l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q52 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-37z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="box" unicode="" d="M607 386q0 14-10 25t-26 10h-142q-15 0-25-10t-11-25 11-25 25-11h142q15 0 26 11t10 25z m322 107v-536q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v536q0 14 11 25t25 11h786q14 0 25-11t11-25z m35 250v-143q0-14-10-25t-25-11h-858q-14 0-25 11t-10 25v143q0 14 10 25t25 11h858q14 0 25-11t10-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="rss" unicode="" d="M214 100q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m286-69q1-15-9-26-10-12-27-12h-75q-14 0-24 9t-11 23q-12 128-103 219t-219 103q-14 1-23 11t-9 24v75q0 16 12 26 9 10 24 10h3q89-7 170-45t145-101q63-63 101-145t45-171z m286-1q1-15-10-26-10-11-26-11h-80q-14 0-25 10t-10 23q-7 120-57 228t-129 188-188 129-227 57q-14 1-24 11t-10 24v80q0 16 11 26 10 10 25 10h1q147-8 280-67t238-164q104-104 164-238t67-280z" horiz-adv-x="785.7" /> + +<glyph glyph-name="rss-squared" unicode="" d="M286 136q0 29-21 50t-51 21-50-21-21-50 21-51 50-21 51 21 21 51z m196-53q-8 130-99 222t-221 98q-8 1-14-5t-5-13v-71q0-7 5-12t12-6q86-6 147-68t67-147q1-7 6-12t12-5h72q7 0 13 6t5 13z m214 0q-3 86-31 166t-78 145-115 114-145 78-166 31q-7 1-13-5-5-5-5-13v-71q0-7 5-12t12-6q114-4 211-62t156-155 62-211q0-8 5-13t13-5h71q7 0 13 6 6 5 5 13z m161 535v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="phone" unicode="" d="M786 158q0-15-6-39t-12-38q-11-28-68-60-52-28-103-28-15 0-30 2t-32 7-26 8-31 11-28 10q-54 20-97 47-71 44-148 120t-120 148q-27 43-46 97-2 5-10 28t-12 31-8 26-7 32-2 29q0 52 29 104 31 57 59 68 14 6 38 12t39 6q8 0 12-2 10-3 30-42 6-11 16-31t20-35 17-30q2-2 10-14t12-20 4-16q0-11-16-27t-35-31-34-30-16-25q0-5 3-13t4-11 8-14 7-10q42-77 97-132t131-97q1 0 10-6t14-8 11-5 13-2q10 0 25 16t30 34 31 35 28 16q7 0 15-4t20-12 14-10q14-8 30-17t36-20 30-17q39-19 42-29 2-4 2-12z" horiz-adv-x="785.7" /> + +<glyph glyph-name="phone-squared" unicode="" d="M714 184q0 6-1 9t-10 9-22 14-27 15-25 14-16 9q-3 1-11 7t-14 8-11 3q-9 0-21-12t-22-25-21-25-19-11q-4 0-9 2t-9 3-9 6-8 4q-55 31-95 71t-71 95q-1 2-5 8t-5 9-4 9-2 9q0 8 12 19t25 22 25 22 11 20q0 6-2 12t-9 14-7 10q-2 4-8 16t-14 25-15 27-14 23-9 10-9 1q-27 0-56-13-26-11-45-52t-19-73q0-9 1-19t3-17 5-18 6-17 7-18 6-17q33-92 121-179t178-121q4-1 17-6t19-7 16-5 19-5 17-3 19-2q31 0 72 19t53 45q12 30 12 56z m143 434v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="fax" unicode="" d="M161 636q37 0 63-26t26-64v-607q0-37-26-63t-63-26h-72q-36 0-63 26t-26 63v607q0 37 26 64t63 26h72z m768-91q32-19 52-52t19-72v-428q0-59-42-101t-101-42h-482q-37 0-63 26t-26 63v857q0 23 15 38t38 16h375q23 0 49-11t43-27l85-85q15-15 26-42t12-49v-91z m-411-552v71q0 8-5 13t-13 5h-71q-8 0-13-5t-5-13v-71q0-8 5-13t13-5h71q8 0 13 5t5 13z m0 143v71q0 8-5 13t-13 5h-71q-8 0-13-5t-5-13v-71q0-8 5-13t13-5h71q8 0 13 5t5 13z m0 143v71q0 8-5 13t-13 5h-71q-8 0-13-5t-5-13v-71q0-8 5-13t13-5h71q8 0 13 5t5 13z m143-286v71q0 8-5 13t-13 5h-72q-7 0-12-5t-5-13v-71q0-8 5-13t12-5h72q8 0 13 5t5 13z m0 143v71q0 8-5 13t-13 5h-72q-7 0-12-5t-5-13v-71q0-8 5-13t12-5h72q8 0 13 5t5 13z m0 143v71q0 8-5 13t-13 5h-72q-7 0-12-5t-5-13v-71q0-8 5-13t12-5h72q8 0 13 5t5 13z m143-286v71q0 8-5 13t-13 5h-72q-7 0-12-5t-6-13v-71q0-8 6-13t12-5h72q8 0 13 5t5 13z m0 143v71q0 8-5 13t-13 5h-72q-7 0-12-5t-6-13v-71q0-8 6-13t12-5h72q8 0 13 5t5 13z m0 143v71q0 8-5 13t-13 5h-72q-7 0-12-5t-6-13v-71q0-8 6-13t12-5h72q8 0 13 5t5 13z m53 214v143h-89q-22 0-38 15t-16 38v90h-357v-286h500z" horiz-adv-x="1000" /> + +<glyph glyph-name="menu" unicode="" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" /> + +<glyph glyph-name="cog" unicode="" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="cog-alt" unicode="" d="M500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="wrench" unicode="" d="M214 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" /> + +<glyph glyph-name="sliders" unicode="" d="M196 64v-71h-196v71h196z m197 72q14 0 25-11t11-25v-143q0-14-11-25t-25-11h-143q-14 0-25 11t-11 25v143q0 15 11 25t25 11h143z m89 214v-71h-482v71h482z m-357 286v-72h-125v72h125z m732-572v-71h-411v71h411z m-536 643q15 0 26-10t10-26v-142q0-15-10-25t-26-11h-142q-15 0-25 11t-11 25v142q0 15 11 26t25 10h142z m358-286q14 0 25-10t10-25v-143q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v143q0 14 11 25t25 10h143z m178-71v-71h-125v71h125z m0 286v-72h-482v72h482z" horiz-adv-x="857.1" /> + +<glyph glyph-name="basket" unicode="" d="M357-7q0-29-21-50t-50-22-50 22-22 50 22 50 50 21 50-21 21-50z m500 0q0-29-21-50t-50-22-50 22-22 50 22 50 50 21 50-21 21-50z m72 607v-286q0-13-10-23t-22-12l-583-68q7-34 7-40 0-8-13-35h513q15 0 26-11t10-25-10-25-26-11h-571q-14 0-25 11t-11 25q0 6 5 18t9 20 12 22 8 17l-98 459h-114q-15 0-25 10t-11 25 11 26 25 10h143q9 0 16-3t10-9 8-14 4-14 3-17 3-14h670q14 0 25-11t11-25z" horiz-adv-x="928.6" /> + +<glyph glyph-name="calendar" unicode="" d="M71-79h161v161h-161v-161z m197 0h178v161h-178v-161z m-197 197h161v178h-161v-178z m197 0h178v178h-178v-178z m-197 214h161v161h-161v-161z m411-411h179v161h-179v-161z m-214 411h178v161h-178v-161z m428-411h161v161h-161v-161z m-214 197h179v178h-179v-178z m-196 482v161q0 7-6 12t-12 6h-36q-7 0-12-6t-6-12v-161q0-7 6-13t12-5h36q7 0 12 5t6 13z m410-482h161v178h-161v-178z m-214 214h179v161h-179v-161z m214 0h161v161h-161v-161z m18 268v161q0 7-5 12t-13 6h-35q-7 0-13-6t-5-12v-161q0-7 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" /> + +<glyph glyph-name="calendar-empty" unicode="" d="M71-79h786v572h-786v-572z m215 679v161q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h36q8 0 13 5t5 13z m428 0v161q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" /> + +<glyph glyph-name="login" unicode="" d="M661 350q0-14-11-25l-303-304q-11-10-26-10t-25 10-10 25v161h-250q-15 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 10 25t25 10 26-10l303-304q11-10 11-25z m196 196v-392q0-67-47-114t-114-47h-178q-7 0-13 5t-5 13q0 2-1 11t0 15 2 13 5 11 12 3h178q37 0 64 27t26 63v392q0 37-26 64t-64 26h-174t-6 0-6 2-5 3-4 5-1 8q0 2-1 11t0 15 2 13 5 11 12 3h178q67 0 114-47t47-114z" horiz-adv-x="857.1" /> + +<glyph glyph-name="logout" unicode="" d="M357 46q0-2 1-11t0-14-2-14-5-11-12-3h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" /> + +<glyph glyph-name="mic" unicode="" d="M643 457v-71q0-124-82-215t-204-104v-74h143q15 0 25-11t11-25-11-25-25-11h-357q-15 0-25 11t-11 25 11 25 25 11h143v74q-121 13-204 104t-82 215v71q0 15 11 25t25 11 25-11 10-25v-71q0-103 74-177t176-73 177 73 73 177v71q0 15 11 25t25 11 25-11 11-25z m-143 214v-285q0-74-52-126t-127-53-126 53-52 126v285q0 74 52 127t126 52 127-52 52-127z" horiz-adv-x="642.9" /> + +<glyph glyph-name="mute" unicode="" d="M151 323l-56-57q-24 58-24 120v71q0 15 11 25t25 11 25-11 11-25v-71q0-30 8-63z m622 336l-202-202v-71q0-74-52-126t-126-53q-31 0-61 11l-53-54q54-28 114-28 103 0 177 73t73 177v71q0 15 11 25t25 11 25-11 10-25v-71q0-124-82-215t-203-104v-74h142q15 0 26-11t10-25-10-25-26-11h-357q-14 0-25 11t-10 25 10 25 25 11h143v74q-70 7-131 45l-142-142q-5-6-13-6t-12 6l-46 46q-6 5-6 13t6 12l689 689q5 6 12 6t13-6l46-46q6-5 6-13t-6-12z m-212 73l-347-346v285q0 74 53 127t126 52q57 0 103-33t65-85z" horiz-adv-x="785.7" /> + +<glyph glyph-name="volume-off" unicode="" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z" horiz-adv-x="428.6" /> + +<glyph glyph-name="volume-down" unicode="" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z m214-304q0-42-24-79t-63-52q-5-3-14-3-14 0-25 10t-10 26q0 12 6 20t17 14 19 12 16 21 6 31-6 32-16 20-19 13-17 13-6 20q0 15 10 26t25 10q9 0 14-3 39-15 63-52t24-79z" horiz-adv-x="642.9" /> + +<glyph glyph-name="volume-up" unicode="" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z m214-304q0-42-24-79t-63-52q-5-3-14-3-14 0-25 10t-10 26q0 12 6 20t17 14 19 12 16 21 6 31-6 32-16 20-19 13-17 13-6 20q0 15 10 26t25 10q9 0 14-3 39-15 63-52t24-79z m143 0q0-85-48-158t-125-105q-7-3-14-3-15 0-26 11t-10 25q0 22 21 33 32 16 43 25 41 30 64 75t23 97-23 97-64 75q-11 9-43 25-21 11-21 33 0 14 10 25t25 11q8 0 15-3 78-33 125-105t48-158z m143 0q0-128-71-236t-189-158q-7-3-14-3-15 0-25 11t-11 25q0 20 22 33 4 2 12 6t13 6q25 14 46 28 68 51 107 127t38 161-38 161-107 127q-21 15-46 28-4 3-13 6t-12 6q-22 13-22 33 0 15 11 25t25 11q7 0 14-3 118-51 189-158t71-236z" horiz-adv-x="928.6" /> + +<glyph glyph-name="headphones" unicode="" d="M929 356q0-93-34-176l-11-27-103-18q-13-47-51-77t-87-29v-18q0-8-5-13t-13-5h-36q-7 0-12 5t-6 13v321q0 8 6 13t12 5h36q8 0 13-5t5-13v-18q40 0 72-19t52-54l38 7q16 53 16 108 0 82-49 155t-132 117-176 43-176-43-132-117-49-155q0-55 16-108l38-7q19 34 52 54t73 19v18q0 8 5 13t13 5h35q8 0 13-5t5-13v-321q0-8-5-13t-13-5h-35q-8 0-13 5t-5 13v18q-49 0-88 29t-50 77l-103 18-11 27q-34 83-34 176 0 84 37 162t100 135 149 92 178 34 179-34 148-92 100-135 38-162z" horiz-adv-x="928.6" /> + +<glyph glyph-name="clock" unicode="" d="M500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="lightbulb" unicode="" d="M411 529q0-8-6-13t-12-5-13 5-5 13q0 25-30 39t-59 14q-7 0-13 5t-5 13 5 13 13 5q28 0 55-9t49-30 21-50z m89 0q0 40-19 74t-50 57-69 35-76 12-76-12-69-35-50-57-20-74q0-57 38-101 6-6 17-18t17-19q72-85 79-166h127q8 81 79 166 6 6 17 19t17 18q38 44 38 101z m71 0q0-87-57-150-25-27-42-48t-33-54-19-60q26-15 26-46 0-20-13-35 13-15 13-36 0-29-25-45 8-13 8-26 0-26-18-40t-43-14q-11-25-34-39t-48-15-49 15-33 39q-26 0-44 14t-17 40q0 13 7 26-25 16-25 45 0 21 14 36-14 15-14 35 0 31 26 46-2 28-19 60t-33 54-41 48q-58 63-58 150 0 55 25 103t65 79 92 49 104 19 104-19 91-49 66-79 24-103z" horiz-adv-x="571.4" /> + +<glyph glyph-name="block" unicode="" d="M732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-111-41-153q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" /> + +<glyph glyph-name="resize-full" unicode="" d="M421 261q0-7-5-13l-185-185 80-81q10-10 10-25t-10-25-25-11h-250q-15 0-25 11t-11 25v250q0 15 11 25t25 11 25-11l80-80 186 185q5 6 12 6t13-6l64-63q5-6 5-13z m436 482v-250q0-15-10-25t-26-11-25 11l-80 80-185-185q-6-6-13-6t-13 6l-64 64q-5 5-5 12t5 13l186 185-81 81q-10 10-10 25t10 25 25 11h250q15 0 26-11t10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="resize-full-alt" unicode="" d="M716 548l-198-198 198-198 80 80q17 18 39 8 22-9 22-33v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 21 7 38l81 81-198 198-198-198 80-81q17-17 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l80-80 198 198-198 198-80-80q-11-11-25-11-7 0-14 3-22 9-22 33v250q0 14 11 25t25 11h250q23 0 33-23 9-21-8-38l-80-81 198-198 198 198-81 81q-17 17-7 38 9 23 32 23h250q15 0 26-11t10-25v-250q0-24-22-33-7-3-14-3-14 0-25 11z" horiz-adv-x="857.1" /> + +<glyph glyph-name="resize-small" unicode="" d="M429 314v-250q0-14-11-25t-25-10-25 10l-81 81-185-186q-5-5-13-5t-12 5l-64 64q-6 6-6 13t6 13l185 185-80 80q-11 11-11 25t11 25 25 11h250q14 0 25-11t11-25z m421 375q0-7-6-12l-185-186 80-80q11-11 11-25t-11-25-25-11h-250q-14 0-25 11t-10 25v250q0 14 10 25t25 10 25-10l81-80 185 185q6 5 13 5t13-5l63-64q6-5 6-13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="resize-vertical" unicode="" d="M393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-10 10-10 25t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" /> + +<glyph glyph-name="resize-horizontal" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-572v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h572v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="move" unicode="" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="zoom-in" unicode="" d="M571 404v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" /> + +<glyph glyph-name="zoom-out" unicode="" d="M571 404v-36q0-7-5-13t-12-5h-322q-7 0-12 5t-6 13v36q0 7 6 12t12 5h322q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" /> + +<glyph glyph-name="down-circled2" unicode="" d="M625 332q0-7-6-13l-178-178q-6-5-12-5t-13 5l-179 178q-8 9-4 20 5 11 17 11h107v196q0 8 5 13t13 5h107q8 0 13-5t5-13v-196h107q8 0 13-5t5-13z m-196 322q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="up-circled2" unicode="" d="M624 361q-5-11-17-11h-107v-196q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v196h-107q-8 0-13 5t-5 13q0 7 6 13l178 178q6 5 13 5t12-5l179-178q8-9 4-20z m-195 293q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="help" unicode="" d="M393 149v-134q0-9-7-15t-15-7h-134q-9 0-16 7t-7 15v134q0 9 7 16t16 6h134q9 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 11t-6 20v26q0 46 37 87t79 60q33 16 47 32t14 42q0 24-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" /> + +<glyph glyph-name="info-circled" unicode="" d="M571 82v89q0 8-5 13t-12 5h-54v286q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h53v-179h-53q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h250q7 0 12 5t5 13z m-71 500v89q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h107q8 0 13 5t5 13z m357-232q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="down-dir" unicode="" d="M571 457q0-14-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 11-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" /> + +<glyph glyph-name="up-dir" unicode="" d="M571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" /> + +<glyph glyph-name="star" unicode="" d="M929 489q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> + +<glyph glyph-name="star-empty" unicode="" d="M635 290l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> + +<glyph glyph-name="down-open" unicode="" d="M939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" /> + +<glyph glyph-name="gym" unicode="" d="M790 172v356c0 11 7 20 16 22 1 0 2 0 3 0h31c10 0 19-10 19-22v-97h24c10 0 19-10 19-22v-116c0-13-9-23-19-23h-24v-98c0-12-9-22-19-22h-31c-1 0-2 0-3 0-9 2-16 11-16 22z m-674 98h15v-98c0-12 7-22 17-22h28c1 0 2 0 3 0 8 2 14 11 14 22v356c0 11-6 20-14 22-1 0-2 0-3 0h-28c-10 0-17-10-17-22v-97h-15c-10 0-18-10-18-22v-116c0-13 8-23 18-23z m99-197h68c13 0 23 10 23 23v202h21 20 289 21 20v-202c0-13 11-23 23-23h68c12 0 22 10 22 23v508c0 13-10 23-22 23h-68c-12 0-23-10-23-23v-200h-20-21-289-20-21v200c0 13-10 23-23 23h-68c-12 0-22-10-22-23v-508c0-13 10-23 22-23z" horiz-adv-x="1000" /> + +<glyph glyph-name="sports" unicode="" d="M624 655c46 0 83 37 83 82 0 46-37 83-83 83-45 0-82-37-82-83 0-45 37-82 82-82z m189-171l-38 35-105 104c-9 9-23 15-37 15l-157 1-289 4c-12 2-23-1-33-9-17-15-19-41-4-59 8-10 20-15 32-14l196-1 105 0-156-186 0 0-85-99c-3-4-6-7-8-11-14-26-9-59 17-73 23-12 56-5 71 14l107 127 7 9 12-9-5-5-294-356c-3-3-6-7-8-11-15-26-6-58 20-73 22-13 50-7 66 11l338 397 0 0 140 173 23-19-95-119c-6-8-9-18-9-28 2-24 22-42 45-41 11 0 20 4 27 11l114 142c37 40 3 70 3 70z" horiz-adv-x="1000" /> + +<glyph glyph-name="up-open" unicode="" d="M939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" /> + +<glyph glyph-name="browser" unicode="" d="M313 663h62v-63h-62v63z m-125 0h62v-63h-62v63z m-125 0h62v-63h-62v63z m750-625h-750v500h750v-500z m0 562h-375v63h375v-63z m62 63c0 34-28 62-62 62h-750c-35 0-63-28-63-62v-625c0-35 28-63 63-63h750c34 0 62 28 62 63v625z" horiz-adv-x="875" /> + +<glyph glyph-name="download" unicode="" d="M714 100q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-39l-250-250q-10-11-25-11t-25 11l-250 250q-17 16-8 39 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" /> + +<glyph glyph-name="angle-up" unicode="" d="M600 189q0-7-6-12l-28-28q-5-6-12-6t-13 6l-220 219-219-219q-5-6-13-6t-12 6l-28 28q-6 5-6 12t6 13l260 260q5 6 12 6t13-6l260-260q6-5 6-13z" horiz-adv-x="642.9" /> + +<glyph glyph-name="angle-down" unicode="" d="M600 439q0-7-6-12l-260-261q-5-5-13-5t-12 5l-260 261q-6 5-6 12t6 13l28 28q5 6 12 6t13-6l219-219 220 219q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="642.9" /> + +<glyph glyph-name="angle-circled-up" unicode="" d="M650 214l57 57q11 11 11 25t-11 26l-253 253q-11 11-25 11t-25-11l-254-253q-10-11-10-26t10-25l57-57q11-10 25-10t25 10l172 172 171-172q11-10 25-10t25 10z m207 136q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="angle-circled-down" unicode="" d="M454 125l253 254q11 10 11 25t-11 25l-57 57q-10 10-25 10t-25-10l-171-172-172 172q-10 10-25 10t-25-10l-57-57q-10-11-10-25t10-25l254-254q10-10 25-10t25 10z m403 225q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="angle-double-up" unicode="" d="M600 118q0-7-6-13l-28-28q-5-5-12-5t-13 5l-220 219-219-219q-5-5-13-5t-12 5l-28 28q-6 6-6 13t6 13l260 260q5 5 12 5t13-5l260-260q6-6 6-13z m0 214q0-7-6-13l-28-28q-5-5-12-5t-13 5l-220 220-219-220q-5-5-13-5t-12 5l-28 28q-6 6-6 13t6 13l260 260q5 6 12 6t13-6l260-260q6-6 6-13z" horiz-adv-x="642.9" /> + +<glyph glyph-name="angle-double-down" unicode="" d="M600 368q0-7-6-13l-260-260q-5-6-13-6t-12 6l-260 260q-6 6-6 13t6 13l28 28q5 5 12 5t13-5l219-220 220 220q5 5 13 5t12-5l28-28q6-6 6-13z m0 214q0-7-6-13l-260-260q-5-5-13-5t-12 5l-260 260q-6 6-6 13t6 13l28 28q5 6 12 6t13-6l219-219 220 219q5 6 13 6t12-6l28-28q6-6 6-13z" horiz-adv-x="642.9" /> + +<glyph glyph-name="down" unicode="" d="M427 125q4-10-3-19l-195-215q-6-5-13-5-8 0-13 5l-198 215q-8 9-3 19 5 11 16 11h125v696q0 8 5 13t13 5h107q8 0 13-5t5-13v-696h125q11 0 16-11z" horiz-adv-x="428.6" /> + +<glyph glyph-name="up" unicode="" d="M427 575q-5-11-16-11h-125v-696q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v696h-125q-12 0-16 11t3 19l195 215q5 5 13 5 7 0 13-5l198-215q7-8 3-19z" horiz-adv-x="428.6" /> + +<glyph glyph-name="down-big" unicode="" d="M899 386q0-30-21-50l-363-364q-22-21-51-21-29 0-50 21l-363 364q-21 20-21 50 0 29 21 51l41 41q22 21 51 21 29 0 50-21l164-164v393q0 29 21 50t51 22h71q29 0 50-22t21-50v-393l165 164q20 21 50 21 29 0 51-21l41-41q21-22 21-51z" horiz-adv-x="928.6" /> + +<glyph glyph-name="up-big" unicode="" d="M899 308q0-28-21-50l-41-42q-22-21-51-21-30 0-50 21l-165 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50 0 30 21 51l363 363q20 21 50 21 30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" /> + +<glyph glyph-name="up-hand" unicode="" d="M714-43q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m72 427q0 105-93 105-15 0-32-3-9 17-29 27t-41 9-38-10q-28 30-67 30-14 0-31-6t-26-14v185q0 29-22 50t-50 22q-28 0-50-22t-21-50v-321q-11 0-27 8t-31 19-38 18-47 8q-37 0-55-24t-17-65q0-13 78-50 25-14 36-21 36-22 81-62 45-40 59-57 32-38 32-78v-18h357v18q0 40 18 93t36 108 18 101z m71 2q0-74-38-179-33-92-33-125v-161q0-29-21-50t-51-21h-357q-29 0-50 21t-21 50v161q0 6-3 12t-8 13-10 13-12 13-12 12-12 10-10 8q-41 36-72 56-11 7-34 18t-40 21-36 23-27 30-10 39q0 70 37 115t106 46q38 0 71-13v209q0 58 43 101t100 42q58 0 101-42t42-101v-94q35-2 66-21 12 2 24 2 57 0 100-34 77 1 122-47t45-127z" horiz-adv-x="857.1" /> + +<glyph glyph-name="down-hand" unicode="" d="M786 314q0 47-18 102t-36 109-18 93v18h-357v-18q0-20-7-38t-20-35-26-28-30-27q-5-4-8-7-45-40-81-62-12-8-38-21-1-1-12-6t-20-10-20-12-17-12-7-10q0-40 17-64t55-25q24 0 47 8t38 19 31 18 27 8v-321q0-28 21-50t50-22q29 0 50 22t22 50v185q25-20 57-20 39 0 67 30 17-10 38-10t41 9 29 27q14-2 32-2 47 0 70 27t23 75z m-72 429q0 14-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143-426q0-80-43-129t-121-48l-3 0q-43-34-100-34-12 0-24 2-30-17-66-21v-94q0-59-42-101t-101-42q-58 0-100 42t-43 101v209q-30-13-71-13-68 0-105 46t-38 115q0 21 10 39t27 31 36 22 40 21 34 18q31 20 72 56 2 1 10 8t12 10 12 12 12 13 10 13 8 13 3 12v161q0 29 21 50t50 21h357q30 0 51-21t21-50v-161q0-33 33-125 38-106 38-176z" horiz-adv-x="857.1" /> + +<glyph glyph-name="up-circled" unicode="" d="M717 351q0 15-10 25l-202 202-51 51q-10 10-25 10t-25-10l-51-51-202-202q-10-10-10-25t10-26l51-50q10-10 25-10t25 10l105 105v-280q0-14 11-25t25-11h71q15 0 25 11t11 25v280l106-105q10-11 25-11t25 11l51 50q10 11 10 26z m140-1q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="down-circled" unicode="" d="M717 349q0 16-10 26l-51 50q-10 10-25 10t-25-10l-106-105v280q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-280l-105 105q-11 11-25 11t-25-11l-51-50q-10-10-10-26t10-25l202-202 51-50q10-10 25-10t25 10l51 50 202 202q10 10 10 25z m140 1q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="cw" unicode="" d="M857 707v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 22 7 38l77 77q-82 77-194 77-58 0-111-23t-91-61-61-91-23-111 23-111 61-91 91-61 111-23q66 0 125 29t100 82q4 6 13 7 8 0 14-5l76-77q5-4 6-11t-5-13q-60-74-147-114t-182-41q-87 0-167 34t-136 92-92 137-34 166 34 166 92 137 136 92 167 34q82 0 158-31t137-88l72 72q17 18 39 8 22-9 22-33z" horiz-adv-x="857.1" /> + +<glyph glyph-name="ccw" unicode="" d="M857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z" horiz-adv-x="857.1" /> + +<glyph glyph-name="arrows-cw" unicode="" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="level-up" unicode="" d="M568 514q-10-21-32-21h-107v-482q0-8-5-13t-13-5h-393q-12 0-16 10-5 11 2 19l89 108q5 6 14 6h179v357h-107q-23 0-33 21-9 20 5 38l179 214q10 12 27 12t28-12l178-214q15-18 5-38z" horiz-adv-x="571.4" /> + +<glyph glyph-name="level-down" unicode="" d="M18 707h393q7 0 12-5t6-13v-482h107q22 0 32-20t-5-39l-178-214q-11-13-28-13t-27 13l-179 214q-14 17-5 39 10 20 33 20h107v357h-179q-8 0-14 6l-89 108q-7 7-2 19 5 10 16 10z" horiz-adv-x="571.4" /> + +<glyph glyph-name="shuffle" unicode="" d="M372 582q-34-52-77-153-12 25-20 41t-23 35-28 32-36 19-45 8h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125q139 0 229-125z m628-446q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107q-18 0-48 0t-45-1-41 1-39 3-36 6-35 10-32 16-33 22-31 30-31 39q33 52 76 152 12-25 20-40t23-36 28-31 35-20 46-8h143v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z m0 500q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-143q-27 0-49-8t-38-25-29-34-25-44q-18-34-43-95-16-37-28-62t-30-59-36-55-41-47-50-38-60-23-71-10h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125q27 0 48 9t39 25 28 34 26 43q17 35 43 96 16 36 28 62t30 58 36 56 41 46 50 39 59 23 72 9h143v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="exchange" unicode="" d="M1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 6-5 13 0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-178q-5-6-12-6-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="history" unicode="" d="M857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z m-357 161v-250q0-8-5-13t-13-5h-178q-8 0-13 5t-5 13v35q0 8 5 13t13 5h125v197q0 8 5 13t12 5h36q8 0 13-5t5-13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="expand" unicode="" d="M639 473q10-19-3-36l-178-250q-11-16-29-16t-29 16l-179 250q-13 17-3 36 10 20 32 20h357q23 0 32-20z m75-391v536q0 7-5 12t-13 6h-535q-7 0-13-6t-5-12v-536q0-7 5-12t13-6h535q8 0 13 6t5 12z m143 536v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="collapse" unicode="" d="M639 227q-9-20-32-20h-357q-22 0-32 20-10 19 3 37l179 250q10 15 29 15t29-15l178-250q13-18 3-37z m75-145v536q0 7-5 12t-13 6h-535q-7 0-13-6t-5-12v-536q0-7 5-12t13-6h535q8 0 13 6t5 12z m143 536v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="play" unicode="" d="M772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" /> + +<glyph glyph-name="play-circled" unicode="" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" /> + +<glyph glyph-name="play-circled2" unicode="" d="M661 350q0-21-18-31l-304-178q-8-5-18-5-8 0-17 4-18 11-18 31v358q0 20 18 31 18 10 35-1l304-178q18-10 18-31z m71 0q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="stop" unicode="" d="M857 743v-786q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v786q0 14 11 25t25 11h785q15 0 26-11t10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="pause" unicode="" d="M857 743v-786q0-14-10-25t-26-11h-285q-15 0-25 11t-11 25v786q0 14 11 25t25 11h285q15 0 26-11t10-25z m-500 0v-786q0-14-10-25t-26-11h-285q-15 0-25 11t-11 25v786q0 14 11 25t25 11h285q15 0 26-11t10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="to-end" unicode="" d="M25-71q-10-11-18-8t-7 18v822q0 14 7 18t18-8l396-396q5-5 8-10v378q0 14 10 25t25 11h72q14 0 25-11t10-25v-786q0-14-10-25t-25-11h-72q-14 0-25 11t-10 25v379q-3-6-8-11z" horiz-adv-x="571.4" /> + +<glyph glyph-name="to-end-alt" unicode="" d="M25-71q-10-11-18-8t-7 18v822q0 14 7 18t18-8l396-396q5-5 8-10v396q0 14 7 18t18-8l396-396q5-5 7-10v378q0 14 11 25t25 11h71q15 0 25-11t11-25v-786q0-14-11-25t-25-11h-71q-15 0-25 11t-11 25v379q-2-6-7-11l-396-396q-11-11-18-8t-7 18v397q-3-6-8-11z" horiz-adv-x="1000" /> + +<glyph glyph-name="to-start" unicode="" d="M546 771q11 11 18 8t7-18v-822q0-14-7-18t-18 8l-396 396q-5 5-7 11v-379q0-14-11-25t-25-11h-71q-15 0-25 11t-11 25v786q0 14 11 25t25 11h71q15 0 25-11t11-25v-378q2 5 7 10z" horiz-adv-x="571.4" /> + +<glyph glyph-name="to-start-alt" unicode="" d="M975 771q11 11 18 8t7-18v-822q0-14-7-18t-18 8l-396 396q-5 5-8 11v-397q0-14-7-18t-18 8l-396 396q-5 5-7 11v-379q0-14-11-25t-25-11h-71q-15 0-25 11t-11 25v786q0 14 11 25t25 11h71q15 0 25-11t11-25v-378q2 5 7 10l396 396q11 11 18 8t7-18v-396q3 5 8 10z" horiz-adv-x="1000" /> + +<glyph glyph-name="fast-fw" unicode="" d="M25-71q-10-11-18-8t-7 18v822q0 14 7 18t18-8l396-396q5-5 8-10v396q0 14 7 18t18-8l396-396q11-10 11-25t-11-25l-396-396q-11-11-18-8t-7 18v397q-3-6-8-11z" horiz-adv-x="928.6" /> + +<glyph glyph-name="fast-bw" unicode="" d="M904 771q10 11 17 8t8-18v-822q0-14-8-18t-17 8l-397 396q-5 5-7 11v-397q0-14-7-18t-18 8l-396 396q-11 11-11 25t11 25l396 396q11 11 18 8t7-18v-396q2 5 7 10z" horiz-adv-x="928.6" /> + +<glyph glyph-name="eject" unicode="" d="M8 304l396 396q11 11 25 11t25-11l396-396q11-11 8-18t-18-7h-822q-14 0-17 7t7 18z m814-311h-786q-14 0-25 11t-10 25v142q0 15 10 26t25 10h786q15 0 25-10t11-26v-142q0-15-11-25t-25-11z" horiz-adv-x="858.3" /> + +<glyph glyph-name="target" unicode="" d="M668 279h-61q-14 0-25 10t-11 25v72q0 14 11 25t25 10h61q-18 61-63 106t-105 62v-60q0-15-11-25t-25-11h-71q-15 0-25 11t-11 25v60q-60-17-105-62t-63-106h61q15 0 25-10t11-25v-72q0-14-11-25t-25-10h-61q18-61 63-106t105-62v60q0 15 11 26t25 10h71q15 0 25-10t11-26v-60q60 18 105 62t63 106z m189 107v-72q0-14-10-25t-26-10h-79q-21-90-87-156t-155-86v-80q0-14-11-25t-25-11h-71q-15 0-25 11t-11 25v80q-90 21-155 86t-86 156h-80q-15 0-25 10t-11 25v72q0 14 11 25t25 10h80q20 90 86 156t155 86v80q0 14 11 25t25 11h71q15 0 25-11t11-25v-80q90-21 155-86t87-156h79q15 0 26-10t10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="signal" unicode="" d="M143 46v-107q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v107q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 72v-179q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v179q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 143v-322q0-8-5-13t-12-5h-108q-7 0-12 5t-5 13v322q0 8 5 13t12 5h108q7 0 12-5t5-13z m215 214v-536q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v536q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 286v-822q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v822q0 8 5 13t13 5h107q8 0 13-5t5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="award" unicode="" d="M256 357q-42 91-42 207h-143v-53q0-44 53-91t132-63z m601 154v53h-143q0-116-41-207 79 16 131 63t53 91z m72 71v-71q0-40-24-80t-62-73-97-54-120-25q-23-30-53-53-21-19-29-40t-8-50q0-30 17-51t54-21q42 0 75-25t32-64v-36q0-8-5-13t-13-5h-464q-8 0-13 5t-5 13v36q0 39 33 64t74 25q38 0 55 21t17 51q0 28-8 50t-29 40q-30 23-53 53-64 3-121 25t-96 54-63 73-23 80v71q0 23 16 38t38 16h160v53q0 37 27 63t63 27h321q37 0 63-27t26-63v-53h161q22 0 38-16t16-38z" horiz-adv-x="928.6" /> + +<glyph glyph-name="desktop" unicode="" d="M1000 296v465q0 7-5 12t-13 6h-893q-7 0-12-6t-6-12v-465q0-7 6-12t12-5h893q7 0 13 5t5 12z m71 465v-607q0-37-26-63t-63-27h-303q0-20 9-43t17-40 9-24q0-14-10-25t-25-11h-286q-15 0-25 11t-11 25q0 8 9 25t18 39 9 43h-304q-36 0-63 27t-26 63v607q0 37 26 63t63 26h893q37 0 63-26t26-63z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="laptop" unicode="" d="M232 136q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="tablet" unicode="" d="M357 64q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m214 90v535q0 8-5 13t-12 5h-465q-7 0-12-5t-6-13v-535q0-8 6-13t12-5h465q7 0 12 5t5 13z m72 535v-607q0-37-26-63t-63-26h-465q-36 0-63 26t-26 63v607q0 37 26 63t63 27h465q36 0 63-27t26-63z" horiz-adv-x="642.9" /> + +<glyph glyph-name="mobile" unicode="" d="M259 64q0 19-13 32t-32 13-31-13-13-32 13-31 31-13 32 13 13 31z m116 90v392q0 8-5 13t-13 5h-286q-7 0-12-5t-5-13v-392q0-8 5-13t12-5h286q7 0 13 5t5 13z m-107 473q0 9-9 9h-89q-9 0-9-9t9-9h89q9 0 9 9z m161 9v-572q0-29-22-50t-50-21h-286q-29 0-50 21t-21 50v572q0 29 21 50t50 21h286q29 0 50-21t22-50z" horiz-adv-x="428.6" /> + +<glyph glyph-name="inbox" unicode="" d="M571 314h176q0 2-1 5t-2 4l-118 277h-395l-118-277q-1-1-2-4t-1-5h176l53-107h179z m286-16v-269q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v269q0 34 14 68l133 308q5 14 20 24t29 9h465q14 0 29-9t20-24l133-308q14-34 14-68z" horiz-adv-x="857.1" /> + +<glyph glyph-name="globe" unicode="" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" /> + +<glyph glyph-name="sun" unicode="" d="M821 350q0 65-25 125t-69 102-102 69-125 25-125-25-102-69-69-102-25-125 25-125 69-102 102-69 125-25 125 25 102 69 69 102 25 125z m154-155q-2-8-11-11l-163-53v-171q0-9-7-15-8-5-16-2l-163 53-100-139q-6-7-15-7t-14 7l-101 139-163-53q-8-3-16 2-7 6-7 15v171l-163 53q-9 3-11 11-3 10 2 17l100 138-100 138q-5 8-2 17 2 8 11 11l163 53v171q0 9 7 15 8 5 16 2l163-53 101 139q5 6 14 6t15-6l100-139 163 53q8 3 16-2 7-6 7-15v-171l163-53q9-3 11-11 3-9-2-17l-100-138 100-138q5-7 2-17z" horiz-adv-x="1000" /> + +<glyph glyph-name="cloud" unicode="" d="M1071 207q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 74 40 135t104 91q-1 16-1 24 0 118 84 202t202 84q88 0 159-49t105-129q39 35 93 35 59 0 101-42t42-101q0-42-23-77 72-17 119-75t46-134z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="flash" unicode="" d="M494 534q10-11 4-24l-302-646q-7-14-23-14-2 0-8 1-9 3-14 11t-3 16l110 451-226-56q-2-1-7-1-10 0-17 7-10 8-7 21l112 461q2 8 9 13t15 5h183q11 0 18-7t7-17q0-4-2-10l-96-258 221 54q5 2 7 2 11 0 19-9z" horiz-adv-x="500" /> + +<glyph glyph-name="moon" unicode="" d="M704 123q-30-5-61-5-102 0-188 50t-137 137-50 188q0 107 58 199-112-33-183-128t-72-214q0-72 29-139t76-113 114-77 139-28q80 0 152 34t123 96z m114 47q-53-113-159-181t-230-68q-87 0-167 34t-136 92-92 137-34 166q0 85 32 163t87 135 132 92 161 38q25 1 34-22 11-23-8-40-48-43-73-101t-26-122q0-83 41-152t111-111 152-41q66 0 127 29 23 10 40-7 8-8 10-19t-2-22z" horiz-adv-x="857.1" /> + +<glyph glyph-name="umbrella" unicode="" d="M500 388v-324q0-58-42-100t-101-43-100 43-43 100q0 15 11 25t25 11 25-11 11-25q0-28 22-49t49-22 50 22 22 49v324q18 6 35 6t36-6z m429-15q0-7-6-13t-12-5q-6 0-13 6-27 25-52 38t-57 13q-38 0-71-21t-58-54q-4-5-10-15t-8-14q-6-9-15-9-10 0-16 9-3 4-9 14t-9 15q-24 34-58 54t-71 21-71-21-57-54q-4-5-10-15t-8-14q-6-9-16-9-10 0-16 9-2 4-8 14t-10 15q-24 34-57 54t-71 21q-33 0-57-13t-52-38q-7-6-13-6-7 0-13 5t-5 13q0 3 1 4 25 102 96 178t166 114 201 38q78 0 154-22t137-63 109-105 64-140q1-1 1-4z m-429 406v-55q-23 1-36 1t-35-1v55q0 14 10 25t25 10 25-10 11-25z" horiz-adv-x="928.6" /> + +<glyph glyph-name="flight" unicode="" d="M768 761q24-29 7-83t-61-96l-90-90 90-388q3-11-7-18l-71-54q-4-3-11-3-2 0-4 0-8 2-12 9l-155 284-145-145 30-108q3-10-5-17l-53-54q-5-5-13-5h-1q-9 1-14 7l-105 141-141 105q-6 4-7 13-1 7 5 14l54 54q5 5 12 5 4 0 5 0l108-30 145 145-284 155q-8 5-9 14-1 9 5 15l71 71q8 7 17 5l371-89 89 89q43 43 96 60t83-6z" horiz-adv-x="785.7" /> + +<glyph glyph-name="tasklist" unicode="" d="M963 288h-488c-37 0-37 25-37 62s0 63 37 63h488c37 0 37-26 37-63s0-62-37-62z m-363 312c-37 0-37 25-37 63s0 62 37 62h363c37 0 37-25 37-62s0-63-37-63h-363z m-600 6l88 81 100-100 255 263 88-88-343-344-188 188z m475-506h488c37 0 37-25 37-62s0-63-37-63h-488c-37 0-37 25-37 63s0 62 37 62z" horiz-adv-x="1000" /> + +<glyph glyph-name="paper-plane" unicode="" d="M984 844q19-13 15-36l-142-857q-3-16-18-25-8-5-18-5-6 0-13 3l-253 104-135-165q-10-13-27-13-7 0-12 2-11 4-17 13t-7 21v195l482 590-596-516-221 91q-20 8-22 30-1 23 18 33l928 536q9 5 18 5 11 0 20-6z" horiz-adv-x="1000" /> + +<glyph glyph-name="leaf" unicode="" d="M714 457q0 15-10 25t-25 11q-96 0-178-28t-145-74-131-123q-11-12-11-25 0-15 11-25t25-11q13 0 25 11 15 13 41 39t38 37q76 69 150 98t175 29q14 0 25 11t10 25z m286 111q0-53-11-108-26-125-103-214t-200-149q-119-61-244-61-83 0-160 27-8 2-49 23t-53 21q-9 0-22-18t-25-39-30-39-33-18q-24 0-36 10t-25 33q-1 2-3 6t-3 6-2 5-1 7q0 20 17 41t38 37 38 31 18 27q0 2-8 21t-9 25q-5 28-5 58 0 64 24 123t66 103 96 77 113 53q31 10 81 15t101 5 99 3 91 13 64 32l16 16t17 16 15 11 20 9 24 3q22 0 40-26t26-63 14-69 4-53z" horiz-adv-x="1000" /> + +<glyph glyph-name="font" unicode="" d="M405 538l-95-251q18 0 76-1t89-1q11 0 32 1-48 141-102 252z m-405-617l1 44q13 4 31 7t32 6 28 8 25 17 17 28l132 344 156 404h72q4-8 6-12l114-268q19-43 60-144t63-153q9-19 33-80t40-94q11-26 19-32 11-9 49-17t47-11q4-22 4-32 0-3-1-8t0-7q-35 0-106 5t-107 4q-42 0-120-4t-99-4q0 24 2 43l73 16q1 0 7 1t9 2 8 3 9 4 6 4 5 6 1 8q0 9-17 54t-40 99-24 56l-251 1q-14-32-43-109t-28-91q0-12 8-21t24-14 27-7 32-5 23-2q1-11 1-32 0-5-1-16-33 0-98 6t-97 6q-5 0-15-3t-12-2q-45-8-105-8z" horiz-adv-x="928.6" /> + +<glyph glyph-name="bold" unicode="" d="M310 1q41-18 78-18 210 0 210 187 0 64-23 101-15 24-34 41t-38 26-45 14-47 6-53 1q-40 0-56-6 0-29 0-88t-1-88q0-5 0-38t0-54 2-47 7-37z m-8 417q23-4 61-4 46 0 80 7t61 25 42 50 14 79q0 39-16 68t-45 46-60 24-69 8q-28 0-73-7 0-28 3-84t2-85q0-15 0-45t-1-44q0-26 1-38z m-302-497l1 53q9 2 48 9t59 15q4 7 7 15t4 19 4 18 1 21 0 19v36q0 548-12 572-2 5-12 8t-25 6-28 4-27 3-17 2l-2 46q55 1 190 6t208 6q13 0 38-1t38 0q39 0 76-7t72-24 60-39 41-59 16-76q0-29-9-54t-22-40-36-32-41-25-47-22q86-20 144-75t57-138q0-56-20-101t-52-72-77-48-91-27-98-8q-25 0-74 2t-74 1q-59 0-171-6t-129-7z" horiz-adv-x="785.7" /> + +<glyph glyph-name="italic" unicode="" d="M0-78l10 48q12 4 34 9t40 11 33 13q16 19 23 56 1 4 35 162t63 303 29 165v14q-13 8-30 11t-39 4-32 3l10 58q19-1 67-4t84-4 67-1q27 0 55 1t68 4 54 4q-2-22-10-50-17-6-57-16t-60-19q-5-10-8-23t-5-23-4-25-4-24q-15-82-49-234t-43-198q-1-5-7-32t-11-51-9-46-4-32l1-10q9-3 103-18-2-24-9-55-6 0-18-1t-18-1q-16 0-49 6t-48 6q-77 1-115 1-28 0-79-5t-68-7z" horiz-adv-x="571.4" /> + +<glyph glyph-name="paragraph" unicode="" d="M713 745v-41q0-16-10-34t-24-18q-28 0-30-1-14-3-18-17-1-6-1-36v-643q0-14-11-24t-24-10h-60q-14 0-24 10t-10 24v680h-80v-680q0-14-9-24t-25-10h-60q-14 0-24 10t-10 24v277q-82 7-137 33-70 33-107 100-36 65-36 145 0 92 50 159 49 66 116 89 62 21 233 21h267q14 0 24-10t10-24z" horiz-adv-x="714.3" /> + +<glyph glyph-name="text-height" unicode="" d="M973 64q19 0 24-10t-6-25l-71-90q-11-15-27-15t-27 15l-71 90q-11 15-6 25t24 10h44v572h-44q-19 0-24 10t6 25l71 90q11 15 27 15t27-15l71-90q11-15 6-25t-24-10h-44v-572h44z m-928 714l30-15q7-3 118-3 25 0 74 1t73 1q21 0 60 0t60 0h164q3 0 12 0t11 0 9 1 10 5 8 10l24 1q2 0 7-1t8 0q1-62 1-187 0-45-2-61-22-8-38-10-14 24-31 71-1 5-6 27t-8 41-4 20q-3 4-7 7t-8 3-8 1-10 1-9-1q-9 0-37 1t-41 0-36-1-40-3q-5-46-4-76 0-53 1-217t1-254q0-9-1-40t0-51 7-38q22-12 69-24t67-21q2-22 2-28 0-8-1-16l-19-1q-43-1-122 5t-115 5q-28 0-85-5t-84-5q-2 29-2 29v5q9 15 34 24t55 17 44 15q10 23 10 213 0 57-1 170t-2 169v65q0 1 0 9t1 14-1 14-2 13-2 8q-7 7-91 7-18 0-52-7t-44-15q-11-7-19-40t-18-62-24-30q-23 15-31 25v214z" horiz-adv-x="1000" /> + +<glyph glyph-name="text-width" unicode="" d="M45 778l30-15q7-3 118-3 25 0 74 1t73 1q40 0 138 1t170 0 138-2q18-1 31 17l23 1q3 0 8-1t8 0q1-62 1-187 0-45-3-61-21-8-38-10-13 24-30 71-1 5-6 27t-8 41-4 20q-6 7-15 10-3 1-37 1-17 0-52 1t-57 1-53-2-53-3q-5-46-5-76l1-85v29q0-31 0-86t1-101 0-85q0-9-1-40t0-51 7-38q22-12 69-24t67-21q3-22 3-28 0-8-2-16l-19-1q-42-1-121 5t-116 5q-28 0-84-5t-85-5q-2 29-2 29v5q10 15 35 24t55 17 43 15q4 9 7 41t3 81 1 87-1 85 0 50q0 4-1 12t-2 12q0 4 1 25t0 41 0 42-1 38-4 18q-6 7-90 7-23 0-91-8t-77-14q-11-6-19-39t-18-63-24-30q-23 15-31 25v214z m686-715q7 0 24-11t32-23 33-28 20-17q14-11 14-27t-14-27q-2-2-20-17t-33-27-32-23-24-11q-7 0-11 5t-6 16-1 19 0 18 1 11h-571q0-1 1-11t1-18-2-19-5-16-12-5q-7 0-23 11t-32 23-34 27-20 17q-14 11-14 27t14 27q3 2 20 17t34 28 32 23 23 11q7 0 12-6t5-16 2-19-1-18-1-11h571q0 1-1 11t0 18 1 19 6 16 11 6z" horiz-adv-x="857.1" /> + +<glyph glyph-name="align-left" unicode="" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-857q-15 0-25 11t-11 25v72q0 14 11 25t25 10h857q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-643q-15 0-25 10t-11 25v72q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="align-center" unicode="" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-500q-14 0-25 11t-11 25v71q0 15 11 25t25 11h500q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v72q0 14 11 25t25 10h786q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-358q-14 0-25 10t-10 25v72q0 14 10 25t25 11h358q14 0 25-11t10-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="align-right" unicode="" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 214v-71q0-15-11-25t-25-11h-714q-14 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m0 215v-72q0-14-11-25t-25-11h-857q-14 0-25 11t-11 25v72q0 14 11 25t25 10h857q15 0 25-10t11-25z m0 214v-72q0-14-11-25t-25-10h-643q-14 0-25 10t-10 25v72q0 14 10 25t25 11h643q15 0 25-11t11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="align-justify" unicode="" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 214v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 215v-72q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v72q0 14 11 25t25 10h928q15 0 25-10t11-25z m0 214v-72q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v72q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="list" unicode="" d="M143 118v-107q0-7-5-13t-13-5h-107q-7 0-13 5t-5 13v107q0 7 5 12t13 6h107q7 0 13-6t5-12z m0 214v-107q0-7-5-13t-13-5h-107q-7 0-13 5t-5 13v107q0 7 5 13t13 5h107q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-107q-7 0-13 6t-5 12v107q0 8 5 13t13 5h107q7 0 13-5t5-13z m857-428v-107q0-7-5-13t-13-5h-750q-7 0-12 5t-6 13v107q0 7 6 12t12 6h750q7 0 13-6t5-12z m-857 643v-107q0-8-5-13t-13-5h-107q-7 0-13 5t-5 13v107q0 7 5 12t13 6h107q7 0 13-6t5-12z m857-429v-107q0-7-5-13t-13-5h-750q-7 0-12 5t-6 13v107q0 7 6 13t12 5h750q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-750q-7 0-12 6t-6 12v107q0 8 6 13t12 5h750q7 0 13-5t5-13z m0 215v-107q0-8-5-13t-13-5h-750q-7 0-12 5t-6 13v107q0 7 6 12t12 6h750q7 0 13-6t5-12z" horiz-adv-x="1000" /> + +<glyph glyph-name="indent-left" unicode="" d="M214 546v-321q0-7-5-13t-13-5q-7 0-12 5l-161 161q-5 5-5 13t5 13l161 160q5 5 12 5 8 0 13-5t5-13z m786-428v-107q0-7-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z m0 214v-107q0-7-5-13t-13-5h-607q-7 0-13 5t-5 13v107q0 7 5 13t13 5h607q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-607q-7 0-13 6t-5 12v107q0 8 5 13t13 5h607q7 0 13-5t5-13z m0 215v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z" horiz-adv-x="1000" /> + +<glyph glyph-name="indent-right" unicode="" d="M196 386q0-8-5-13l-160-161q-5-5-13-5-7 0-13 5t-5 13v321q0 8 5 13t13 5q8 0 13-5l160-160q5-5 5-13z m804-268v-107q0-7-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z m0 214v-107q0-7-5-13t-13-5h-607q-7 0-13 5t-5 13v107q0 7 5 13t13 5h607q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-607q-7 0-13 6t-5 12v107q0 8 5 13t13 5h607q7 0 13-5t5-13z m0 215v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z" horiz-adv-x="1000" /> + +<glyph glyph-name="list-bullet" unicode="" d="M214 64q0-44-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m0 286q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 7 5 12t13 6h678q7 0 13-6t5-12z m-786 518q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="list-numbered" unicode="" d="M213-54q0-45-31-70t-75-26q-60 0-96 37l31 49q28-25 60-25 16 0 28 8t12 24q0 35-59 31l-14 31q4 6 18 24t24 31 20 21v1q-9 0-27-1t-27 0v-30h-59v85h186v-49l-53-65q28-6 45-27t17-49z m1 350v-89h-202q-4 20-4 30 0 29 14 52t31 38 37 27 31 24 14 25q0 14-9 22t-22 7q-25 0-45-32l-47 33q13 28 40 44t59 16q40 0 68-23t28-63q0-28-19-51t-42-36-42-28-20-30h71v34h59z m786-178v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 8 5 13t13 5h678q7 0 13-6t5-12z m-786 502v-56h-187v56h60q0 22 0 67t1 68v7h-1q-5-10-28-30l-40 42 76 71h59v-225h60z m786-216v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="strike" unicode="" d="M982 350q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12v36q0 8 5 13t13 5h964z m-712 36q-16 19-29 44-27 55-27 105 0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-101-33-38-32-38-81 0-41 37-78t156-72q38-12 96-37 33-16 53-29h-414z m283-143h229q4-22 4-51 0-62-23-119-13-31-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 8-40 15-5 5-5 12v8q0 60-1 87 0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-76 40z" horiz-adv-x="1000" /> + +<glyph glyph-name="underline" unicode="" d="M27 726q-21 1-25 2l-2 49q7 1 22 1 34 0 63-3 74-4 93-4 47 0 93 2 65 2 82 3 31 0 48 1l-1-8 1-36v-5q-33-5-69-5-33 0-44-14-7-7-7-73 0-7 0-18t0-15l1-127 8-157q3-69 28-112 20-33 54-52 49-26 98-26 59 0 107 16 31 10 55 28 27 20 37 36 20 31 29 63 12 41 12 128 0 44-2 72t-6 68-8 89l-2 33q-3 37-13 49-19 20-43 19l-56-1-8 2 1 48h47l114-6q43-2 110 6l10-1q3-22 3-29 0-4-2-17-25-7-47-8-41-6-44-9-8-8-8-23 0-4 0-15t1-17q5-11 13-221 3-109-9-170-8-42-23-68-21-36-62-69-42-31-102-49-61-19-142-19-93 0-159 26-66 26-99 68-34 42-47 109-9 45-9 132v186q0 105-9 119-14 20-82 22z m830-787v36q0 8-5 13t-13 5h-821q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h821q8 0 13 5t5 13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="superscript" unicode="" d="M501 86v-93h-139l-89 141-13 23q-4 5-6 12h-2q0-2-1-4t-2-4-2-4q-5-11-14-25l-86-139h-144v93h71l110 162-103 152h-76v94h154l77-127q1-2 13-24 4-5 6-11h2q1 5 6 11l14 24 78 127h143v-94h-69l-103-149 114-165h61z m355 379v-115h-287l-1 15q-3 16-3 26 0 36 15 65t36 48 47 37 47 30 36 30 15 36q0 21-17 35t-39 13q-29 0-54-21-8-6-20-22l-59 52q15 20 35 37 47 36 105 36 61 0 99-33t38-89q0-31-13-57t-35-43-45-33-46-28-37-28-17-36h130v45h70z" horiz-adv-x="857.1" /> + +<glyph glyph-name="subscript" unicode="" d="M501 86v-93h-139l-89 141-13 23q-4 5-6 12h-2q0-2-1-4t-2-4-2-4q-5-11-14-25l-86-139h-144v93h71l110 162-103 152h-76v94h154l77-127q1-2 13-24 4-5 6-11h2q1 5 6 11l14 24 78 127h143v-94h-69l-103-149 114-165h61z m356-121v-115h-287l-2 15q-2 25-2 26 0 35 15 65t36 48 47 37 47 30 36 30 15 36q0 21-17 35t-39 13q-28 0-54-21-8-6-20-22l-59 52q15 20 35 37 45 36 105 36 62 0 100-33t37-89q0-37-19-66t-47-48-55-35-49-35-23-41h130v45h70z" horiz-adv-x="857.1" /> + +<glyph glyph-name="table" unicode="" d="M286 82v107q0 8-5 13t-13 5h-179q-7 0-12-5t-6-13v-107q0-8 6-13t12-5h179q8 0 13 5t5 13z m0 214v108q0 7-5 12t-13 5h-179q-7 0-12-5t-6-12v-108q0-7 6-12t12-5h179q8 0 13 5t5 12z m285-214v107q0 8-5 13t-12 5h-179q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h179q7 0 12 5t5 13z m-285 429v107q0 8-5 13t-13 5h-179q-7 0-12-5t-6-13v-107q0-8 6-13t12-5h179q8 0 13 5t5 13z m285-215v108q0 7-5 12t-12 5h-179q-8 0-13-5t-5-12v-108q0-7 5-12t13-5h179q7 0 12 5t5 12z m286-214v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m-286 429v107q0 8-5 13t-12 5h-179q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h179q7 0 12 5t5 13z m286-215v108q0 7-5 12t-13 5h-178q-8 0-13-5t-5-12v-108q0-7 5-12t13-5h178q8 0 13 5t5 12z m0 215v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m72 178v-607q0-37-27-63t-63-26h-750q-36 0-63 26t-26 63v607q0 37 26 63t63 27h750q37 0 63-27t27-63z" horiz-adv-x="928.6" /> + +<glyph glyph-name="columns" unicode="" d="M89-7h340v643h-358v-625q0-7 6-13t12-5z m768 18v625h-357v-643h339q8 0 13 5t5 13z m72 678v-678q0-37-27-63t-63-27h-750q-36 0-63 27t-26 63v678q0 37 26 63t63 27h750q37 0 63-27t27-63z" horiz-adv-x="928.6" /> + +<glyph glyph-name="crop" unicode="" d="M311 136h332v332z m-25 25l332 332h-332v-332z m643-43v-107q0-8-5-13t-13-5h-125v-125q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v125h-482q-8 0-13 5t-5 13v482h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125v125q0 8 5 13t13 5h107q8 0 13-5t5-13v-125h475l137 138q6 5 13 5t13-5q5-6 5-13t-5-13l-138-137v-475h125q8 0 13-5t5-13z" horiz-adv-x="928.6" /> + +<glyph glyph-name="scissors" unicode="" d="M536 350q14 0 25-11t10-25-10-25-25-10-25 10-11 25 11 25 25 11z m167-36l283-222q16-11 14-31-3-20-19-28l-72-36q-7-4-16-4-10 0-17 4l-385 216-62-36q-4-3-7-3 8-28 6-54-4-43-31-83t-74-69q-74-47-154-47-76 0-124 44-51 47-44 116 4 42 31 82t73 69q74 47 155 47 46 0 84-18 5 8 13 13l68 40-68 41q-8 5-13 12-38-17-84-17-81 0-155 47-46 30-73 69t-31 82q-3 33 8 63t36 52q47 44 124 44 80 0 154-47 46-29 74-68t31-83q2-27-6-54 3-1 7-3l62-37 385 216q7 5 17 5 9 0 16-4l72-36q16-9 19-28 2-20-14-32z m-380 145q26 24 12 61t-59 65q-52 33-107 33-42 0-63-20-26-24-12-60t59-66q51-33 107-33 41 0 63 20z m-47-415q45 28 59 65t-12 60q-22 20-63 20-56 0-107-33-45-28-59-65t12-60q21-20 63-20 55 0 107 33z m99 342l54-33v7q0 20 18 31l8 4-44 26-15-14q-1-2-5-6t-7-7q-1-1-2-2t-2-1z m125-125l54-18 410 321-71 36-429-240v-64l-89-53 5-5q1-1 4-3 2-2 6-7t6-6l15-15z m393-232l71 35-290 228-99-77q-1-2-7-4z" horiz-adv-x="1000" /> + +<glyph glyph-name="paste" unicode="" d="M429-79h500v358h-233q-22 0-37 15t-16 38v232h-214v-643z m142 804v36q0 7-5 12t-12 6h-393q-7 0-13-6t-5-12v-36q0-7 5-13t13-5h393q7 0 12 5t5 13z m143-375h167l-167 167v-167z m286-71v-375q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v89h-303q-23 0-38 16t-16 37v750q0 23 16 38t38 16h607q22 0 38-16t15-38v-183q12-7 20-15l228-228q16-15 27-42t11-49z" horiz-adv-x="1000" /> + +<glyph glyph-name="briefcase" unicode="" d="M357 707h286v72h-286v-72z m643-357v-268q0-37-26-63t-63-26h-822q-36 0-63 26t-26 63v268h375v-89q0-15 11-25t25-11h178q15 0 25 11t11 25v89h375z m-429 0v-71h-142v71h142z m429 268v-214h-1000v214q0 37 26 63t63 26h197v89q0 23 15 38t38 16h322q22 0 38-16t15-38v-89h197q37 0 63-26t26-63z" horiz-adv-x="1000" /> + +<glyph glyph-name="suitcase" unicode="" d="M357 636h286v71h-286v-71z m-196 0v-715h-36q-51 0-88 37t-37 88v465q0 51 37 88t88 37h36z m625 0v-715h-572v715h72v89q0 22 15 38t38 16h322q22 0 38-16t15-38v-89h72z m214-125v-465q0-51-37-88t-88-37h-36v715h36q51 0 88-37t37-88z" horiz-adv-x="1000" /> + +<glyph glyph-name="ellipsis" unicode="" d="M214 439v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" /> + +<glyph glyph-name="ellipsis-vert" unicode="" d="M214 154v-108q0-22-15-37t-38-16h-107q-23 0-38 16t-16 37v108q0 22 16 38t38 15h107q22 0 38-15t15-38z m0 285v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m0 286v-107q0-22-15-38t-38-16h-107q-23 0-38 16t-16 38v107q0 22 16 38t38 16h107q22 0 38-16t15-38z" horiz-adv-x="214.3" /> + +<glyph glyph-name="off" unicode="" d="M857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 23-111t61-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-23 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" /> + +<glyph glyph-name="road" unicode="" d="M620 294v2l-13 179q-1 7-7 13t-12 5h-104q-7 0-13-5t-6-13l-13-179v-2q-1-6 4-11t12-4h136q7 0 12 4t4 11z m424-260q0-41-26-41h-393q7 0 12 5t5 13l-11 143q-1 7-7 12t-12 5h-152q-7 0-13-5t-6-12l-11-143q-1-7 4-13t12-5h-392q-26 0-26 41 0 30 14 64l233 583q5 11 15 18t21 8h189q-7 0-13-5t-6-13l-8-107q-1-8 4-13t12-5h93q7 0 12 5t5 13l-9 107q0 8-6 13t-13 5h190q11 0 21-8t14-18l233-583q15-34 15-64z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="list-alt" unicode="" d="M214 189v-35q0-8-5-13t-13-5h-35q-7 0-13 5t-5 13v35q0 8 5 13t13 5h35q8 0 13-5t5-13z m0 143v-36q0-7-5-12t-13-5h-35q-7 0-13 5t-5 12v36q0 7 5 13t13 5h35q8 0 13-5t5-13z m0 143v-36q0-7-5-12t-13-6h-35q-7 0-13 6t-5 12v36q0 7 5 13t13 5h35q8 0 13-5t5-13z m643-286v-35q0-8-5-13t-13-5h-535q-8 0-13 5t-5 13v35q0 8 5 13t13 5h535q8 0 13-5t5-13z m0 143v-36q0-7-5-12t-13-5h-535q-8 0-13 5t-5 12v36q0 7 5 13t13 5h535q8 0 13-5t5-13z m0 143v-36q0-7-5-12t-13-6h-535q-8 0-13 6t-5 12v36q0 7 5 13t13 5h535q8 0 13-5t5-13z m72-393v464q0 8-6 13t-12 5h-822q-7 0-12-5t-6-13v-464q0-7 6-12t12-6h822q7 0 12 6t6 12z m71 607v-607q0-37-26-63t-63-26h-822q-36 0-63 26t-26 63v607q0 37 26 63t63 27h822q37 0 63-27t26-63z" horiz-adv-x="1000" /> + +<glyph glyph-name="qrcode" unicode="" d="M214 207v-71h-71v71h71z m0 429v-72h-71v72h71z m429 0v-72h-72v72h72z m-572-571h215v214h-215v-214z m0 428h215v214h-215v-214z m429 0h214v214h-214v-214z m-143-143v-357h-357v357h357z m286-286v-71h-72v71h72z m143 0v-71h-72v71h72z m0 286v-214h-215v71h-71v-214h-71v357h214v-71h71v71h72z m-429 429v-358h-357v358h357z m429 0v-358h-357v358h357z" horiz-adv-x="785.7" /> + +<glyph glyph-name="barcode" unicode="" d="M35-7h-35v786h35v-786z m35 0h-17v786h17v-786z m53 0h-17v786h17v-786z m87 0h-17v786h17v-786z m88 0h-35v786h35v-786z m70 0h-17v786h17v-786z m36 0h-18v786h18v-786z m35 0h-18v786h18v-786z m87 0h-35v786h35v-786z m88 0h-35v786h35v-786z m70 0h-35v786h35v-786z m71 0h-36v786h36v-786z m52 0h-35v786h35v-786z m105 0h-52v786h52v-786z m36 0h-18v786h18v-786z m52 0h-35v786h35v-786z" horiz-adv-x="1000" /> + +<glyph glyph-name="book" unicode="" d="M915 583q22-31 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 64-32t10-72l-153-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t20 23l167 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-1 12t-12 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-7 0-14-5t-10-13z" horiz-adv-x="928.6" /> + +<glyph glyph-name="ajust" unicode="" d="M429 46v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="tint" unicode="" d="M286 207q0 20-11 39-1 0-9 12t-14 21-14 25-12 28q-2 9-12 9t-11-9q-4-13-12-28t-14-25-14-21-9-12q-11-19-11-39 0-29 21-50t50-21 51 21 21 50z m285 72q0-119-83-202t-202-84-202 84-84 202q0 81 45 153 4 5 35 51t56 84 56 99 46 113q5 16 19 26t29 9 29-9 18-26q16-52 47-113t55-99 56-84 35-51q45-71 45-153z" horiz-adv-x="571.4" /> + +<glyph glyph-name="toggle-off" unicode="" d="M643 350q0 58-23 111t-61 91-91 61-111 23-111-23-91-61-61-91-23-111 23-111 61-91 91-61 111-23 111 23 91 61 61 91 23 111z m428 0q0 58-22 111t-61 91-91 61-111 23h-216q67-50 106-125t38-161-38-161-106-125h216q58 0 111 23t91 61 61 91 22 111z m72 0q0-72-29-139t-76-113-114-77-138-28h-429q-72 0-138 28t-114 77-76 113-29 139 29 139 76 114 114 76 138 28h429q72 0 138-28t114-76 76-114 29-139z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="toggle-on" unicode="" d="M0 350q0 73 29 139t76 114 114 76 138 28h429q72 0 138-28t114-76 76-114 29-139-29-139-76-113-114-77-138-28h-429q-72 0-138 28t-114 77-76 113-29 139z m786-286q58 0 111 23t91 61 61 91 22 111-22 111-61 91-91 61-111 23-111-23-91-61-61-91-23-111 23-111 61-91 91-61 111-23z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="check" unicode="" d="M786 331v-177q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-6-5-13-5-1 0-5 1-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v141q0 8 5 13l36 35q6 6 13 6 3 0 7-2 11-4 11-16z m129 273l-455-454q-13-14-31-14t-32 14l-240 240q-14 13-14 31t14 32l61 62q14 13 32 13t32-13l147-147 361 361q13 13 31 13t32-13l62-61q13-14 13-32t-13-32z" horiz-adv-x="928.6" /> + +<glyph glyph-name="check-empty" unicode="" d="M625 707h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v464q0 37-26 63t-63 26z m161-89v-464q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q66 0 114-48t47-113z" horiz-adv-x="785.7" /> + +<glyph glyph-name="circle" unicode="" d="M857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="circle-empty" unicode="" d="M429 654q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="circle-notch" unicode="" d="M982 350q0-98-38-187t-103-154-153-103-188-38-187 38-154 103-103 154-38 187q0 119 54 222t148 171 209 84v-127q-124-25-205-123t-81-227q0-72 28-139t77-113 113-77 139-28 139 28 114 77 76 113 28 139q0 128-81 227t-205 123v127q115-17 209-84t148-171 54-222z" horiz-adv-x="1000" /> + +<glyph glyph-name="dot-circled" unicode="" d="M571 350q0-59-41-101t-101-42-101 42-42 101 42 101 101 42 101-42 41-101z m-142 304q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41z m428-304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="asterisk" unicode="" d="M827 264q26-14 33-43t-7-55l-35-61q-15-26-44-33t-54 7l-149 85v-171q0-29-21-50t-50-22h-71q-29 0-51 22t-21 50v171l-148-85q-26-15-55-7t-43 33l-36 61q-14 26-7 55t34 43l148 86-148 86q-26 14-34 43t7 55l36 61q15 26 43 33t55-7l148-85v171q0 29 21 50t51 22h71q29 0 50-22t21-50v-171l149 85q26 15 54 7t44-33l35-61q15-26 7-55t-33-43l-148-86z" horiz-adv-x="928.6" /> + +<glyph glyph-name="gift" unicode="" d="M518 93v400h-179v-400q0-14 10-21t26-8h107q16 0 26 8t10 21z m-255 471h109l-70 90q-15 17-39 17-22 0-38-15t-15-38 15-38 38-16z m384 54q0 22-15 38t-38 15q-24 0-39-17l-69-90h108q22 0 38 16t15 38z m210-143v-179q0-7-5-12t-13-5h-53v-233q0-22-16-37t-38-16h-607q-22 0-38 16t-16 37v233h-53q-8 0-13 5t-5 12v179q0 8 5 13t13 5h245q-51 0-88 36t-37 89 37 88 88 37q60 0 94-43l72-92 71 92q34 43 94 43 52 0 88-37t37-88-37-89-88-36h245q8 0 13-5t5-13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="fire" unicode="" d="M786-96v-36q0-7-6-13t-12-5h-750q-7 0-13 5t-5 13v36q0 7 5 12t13 5h750q7 0 12-5t6-12z m-143 589q0-44-14-80t-35-63-49-49-54-44-49-40-35-45-14-54q0-54 37-125l-2 0 1 0q-51 23-90 46t-77 56-63 68-41 84-15 103q0 44 14 80t35 63 49 49 54 44 49 40 35 45 14 54q0 53-37 125l2-1-1 1q50-23 89-46t78-56 63-68 41-84 15-103z" horiz-adv-x="785.7" /> + +<glyph glyph-name="magnet" unicode="" d="M857 386v-72q0-112-55-202t-153-140-220-51-221 51-153 140-55 202v72q0 14 11 25t25 10h214q15 0 25-10t11-25v-72q0-29 13-50t30-32 39-16 36-8 25-1 24 1 36 8 40 16 29 32 13 50v72q0 14 11 25t25 10h214q15 0 26-10t10-25z m-571 357v-214q0-15-11-25t-25-11h-214q-15 0-25 11t-11 25v214q0 14 11 25t25 11h214q15 0 25-11t11-25z m571 0v-214q0-15-10-25t-26-11h-214q-14 0-25 11t-11 25v214q0 14 11 25t25 11h214q15 0 26-11t10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="chart-bar" unicode="" d="M357 350v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="chart-area" unicode="" d="M1143-7v-72h-1143v858h71v-786h1072z m-214 571l142-500h-928v322l250 321 321-321z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="chart-pie" unicode="" d="M429 353l304-304q-59-61-138-94t-166-34q-117 0-216 58t-155 156-58 215 58 215 155 156 216 58v-426z m104-3h431q0-88-33-167t-94-138z m396 71h-429v429q117 0 215-57t156-156 58-216z" horiz-adv-x="1000" /> + +<glyph glyph-name="chart-line" unicode="" d="M1143-7v-72h-1143v858h71v-786h1072z m-72 696v-242q0-12-10-17t-20 4l-68 68-353-353q-6-6-13-6t-13 6l-130 130-232-233-107 108 327 326q5 6 12 6t13-6l130-130 259 259-67 68q-9 8-5 19t17 11h243q7 0 12-5t5-13z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="ticket" unicode="" d="M571 598l177-177-319-319-177 177z m-117-546l345 344q10 11 10 25t-10 26l-202 202q-10 10-26 10t-25-10l-344-345q-11-11-11-25t11-25l202-202q10-11 25-11t25 11z m496 355l-506-507q-21-20-51-20t-50 20l-71 70q32 32 32 76t-32 76-76 32-75-32l-70 71q-21 20-21 50t21 51l506 505q21 21 50 21t51-21l70-69q-32-32-32-76t32-76 76-32 76 32l70-70q20-21 20-51t-20-50z" horiz-adv-x="1000" /> + +<glyph glyph-name="credit-card" unicode="" d="M982 779q37 0 63-27t26-63v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893z m-893-72q-7 0-12-5t-6-13v-125h929v125q0 8-5 13t-13 5h-893z m893-714q7 0 13 5t5 13v339h-929v-339q0-7 6-13t12-5h893z m-839 71v72h143v-72h-143z m214 0v72h214v-72h-214z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="floppy" unicode="" d="M214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-7 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z" horiz-adv-x="857.1" /> + +<glyph glyph-name="megaphone" unicode="" d="M929 493q29 0 50-21t21-51-21-50-50-21v-214q0-29-22-50t-50-22q-233 194-453 212-32-10-51-36t-17-57 22-51q-11-19-13-37t4-32 19-31 26-28 35-28q-17-32-63-46t-94-7-73 31q-4 13-17 49t-18 53-12 50-9 56 2 55 12 62h-68q-36 0-63 26t-26 63v107q0 37 26 63t63 26h268q243 0 500 215 29 0 50-22t22-50v-214z m-72-337v532q-220-168-428-191v-151q210-23 428-190z" horiz-adv-x="1000" /> + +<glyph glyph-name="hdd" unicode="" d="M580 171q0-18-13-31t-31-13-32 13-13 31 13 32 32 13 31-13 13-32z m143 0q0-18-13-31t-31-13-32 13-13 31 13 32 32 13 31-13 13-32z m63-89v179q0 7-6 12t-12 6h-679q-7 0-12-6t-6-12v-179q0-7 6-12t12-6h679q7 0 12 6t6 12z m-687 268h659l-88 269q-2 7-9 12t-14 5h-437q-7 0-14-5t-9-12z m758-89v-179q0-37-26-63t-63-26h-679q-36 0-63 26t-26 63v179q0 14 9 42l110 338q9 29 35 48t56 18h437q31 0 56-18t35-48l110-338q9-28 9-42z" horiz-adv-x="857.1" /> + +<glyph glyph-name="fork" unicode="" d="M161 29q0 22-16 38t-38 15-38-15-15-38 15-38 38-16 38 16 16 38z m0 642q0 23-16 38t-38 16-38-16-15-38 15-37 38-16 38 16 16 37z m357-71q0 22-16 38t-38 16-38-16-15-38 15-38 38-16 38 16 16 38z m53 0q0-29-14-54t-39-39q-1-160-126-231-38-21-113-45-72-22-95-39t-23-56v-15q24-14 39-39t14-53q0-45-31-76t-76-32-76 32-31 76q0 29 15 53t39 39v458q-25 14-39 39t-15 53q0 45 31 76t76 32 76-32 31-76q0-29-14-53t-39-39v-277q30 14 86 31 30 10 49 17t39 17 33 22 22 29 16 38 5 51q-25 14-39 39t-15 54q0 45 31 76t76 31 76-31 31-76z" horiz-adv-x="571.4" /> + +<glyph glyph-name="rocket" unicode="" d="M804 600q0 22-16 38t-38 16-38-16-16-38 16-38 38-16 38 16 16 38z m125 161q0-139-43-240t-141-202q-45-44-109-98l-11-211q-1-9-9-15l-214-125q-4-2-9-2-7 0-13 5l-36 36q-7 7-4 17l47 155-156 156-154-47q-2-1-6-1-7 0-12 5l-36 36q-10 11-3 22l125 214q6 8 15 9l211 11q54 64 98 109 105 104 200 144t241 40q7 0 13-6t6-12z" horiz-adv-x="928.6" /> + +<glyph glyph-name="bug" unicode="" d="M911 314q0-14-11-25t-25-10h-125q0-96-37-162l116-117q10-11 10-25t-10-25q-10-11-25-11t-25 11l-111 110q-3-3-8-7t-24-16-36-21-46-16-54-7v500h-71v-500q-29 0-57 7t-49 19-36 22-25 18l-8 8-102-116q-11-12-27-12-13 0-24 9-11 10-11 25t8 26l113 127q-32 63-32 153h-125q-15 0-25 10t-11 25 11 25 25 11h125v164l-97 97q-11 10-11 25t11 25 25 10 25-10l97-97h471l96 97q11 10 25 10t26-10 10-25-10-25l-97-97v-164h125q15 0 25-11t11-25z m-268 322h-357q0 74 52 126t126 52 127-52 52-126z" horiz-adv-x="928.6" /> + +<glyph glyph-name="certificate" unicode="" d="M768 350l77-75q17-16 11-39-7-23-29-29l-105-27 30-103q6-23-11-39-16-18-39-11l-104 30-27-105q-5-23-28-30-7-1-11-1-17 0-28 13l-75 77-76-77q-15-17-39-12-23 7-28 30l-27 105-104-30q-23-7-39 11-17 16-10 39l29 103-105 27q-22 6-29 29-6 23 11 39l77 75-77 75q-17 16-11 39 7 23 29 29l105 27-29 103q-7 23 10 40 16 17 39 10l104-29 27 104q5 23 28 29 23 7 39-11l76-77 75 77q16 17 39 11 23-6 28-29l27-104 104 29q23 7 39-10 17-17 11-40l-30-103 105-27q22-6 29-29 6-23-11-39z" horiz-adv-x="857.1" /> + +<glyph glyph-name="tasks" unicode="" d="M571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" /> + +<glyph glyph-name="filter" unicode="" d="M783 685q9-22-8-39l-275-275v-414q0-23-22-33-7-3-14-3-15 0-25 11l-143 143q-10 11-10 25v271l-275 275q-18 17-8 39 9 22 33 22h714q23 0 33-22z" horiz-adv-x="785.7" /> + +<glyph glyph-name="beaker" unicode="" d="M852 42q31-50 12-85t-78-36h-643q-59 0-78 36t12 85l280 443v222h-36q-14 0-25 11t-10 25 10 25 25 11h286q15 0 25-11t11-25-11-25-25-11h-36v-222z m-435 405l-151-240h397l-152 240-11 17v243h-71v-243z" horiz-adv-x="928.6" /> + +<glyph glyph-name="magic" unicode="" d="M664 526l164 163-60 60-164-163z m250 163q0-15-10-25l-718-718q-10-10-25-10t-25 10l-111 111q-10 10-10 25t10 25l718 718q10 10 25 10t25-10l111-111q10-10 10-25z m-754 106l54-16-54-17-17-55-17 55-55 17 55 16 17 55z m195-90l109-34-109-33-34-109-33 109-109 33 109 34 33 109z m519-267l55-17-55-16-17-55-17 55-54 16 54 17 17 55z m-357 357l54-16-54-17-17-55-17 55-54 17 54 16 17 55z" horiz-adv-x="928.6" /> + +<glyph glyph-name="cab" unicode="" d="M268 243q0 37-26 63t-63 26-63-26-27-63 27-63 63-26 63 26 26 63z m20 178h567l-50 200q-1 4-8 9t-11 6h-429q-5 0-12-6t-7-9z m766-178q0 37-27 63t-63 26-63-26-26-63 26-63 63-26 63 26 27 63z m89 53v-214q0-8-5-13t-13-5h-54v-71q0-45-31-76t-76-31-76 31-31 76v71h-571v-71q0-45-31-76t-76-31-76 31-32 76v71h-53q-8 0-13 5t-5 13v214q0 52 37 89t88 36h16l58 234q13 53 58 88t100 36h429q54 0 100-36t58-88l58-234h16q52 0 88-36t37-89z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="taxi" unicode="" d="M1018 350q52 0 88-37t37-88v-214q0-8-5-13t-13-5h-54v-36q0-45-31-76t-76-31-76 31-31 76v36h-571v-36q0-45-31-76t-76-31-76 31-32 76v36h-53q-8 0-13 5t-5 13v214q0 52 37 88t88 37h16l58 234q13 52 58 88t100 35h72v125q0 8 5 13t12 5h250q8 0 13-5t5-13v-125h72q54 0 100-35t58-88l58-234h16z m-839-268q36 0 63 26t26 63-26 64-63 26-63-26-27-64 27-63 63-26z m109 268h567l-50 199q-1 5-8 10t-11 5h-429q-5 0-12-5t-7-10z m676-268q37 0 63 26t27 63-27 64-63 26-63-26-26-64 26-63 63-26z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="truck" unicode="" d="M357 64q0 29-21 51t-50 21-50-21-22-51 22-50 50-21 50 21 21 50z m-214 286h214v143h-88q-7 0-12-5l-109-109q-5-5-5-12v-17z m714-286q0 29-21 51t-50 21-50-21-22-51 22-50 50-21 50 21 21 50z m143 607v-571q0-8-2-15t-8-10-9-6-13-4-13-1-14 0-12 0q0-59-42-101t-101-42-101 42-42 101h-214q0-59-42-101t-101-42-101 42-42 101h-36q-1 0-12 0t-15 0-12 1-13 4-9 6-8 10-2 15q0 15 10 25t25 11v178q0 5 0 20t0 21 2 19 3 21 8 17 13 17l110 110q11 11 28 18t33 7h89v107q0 15 11 26t25 10h571q15 0 25-10t11-26z" horiz-adv-x="1000" /> + +<glyph glyph-name="bus" unicode="" d="M214 171q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m572 0q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-26 221l-40 215q-3 13-13 21t-22 8h-513q-12 0-22-8t-13-21l-40-215q-3-16 8-29t27-13h593q17 0 27 13t8 29z m-126 342q0 11-8 19t-19 8h-357q-11 0-19-8t-8-19 8-19 19-8h357q11 0 19 8t8 19z m223-405v-336h-71v-72q0-29-21-50t-51-21-50 21-21 50v72h-429v-72q0-29-21-50t-50-21-51 21-21 50v72h-71v336q0 63 14 125l57 253q6 44 55 77t128 49 175 17 174-17 128-49 55-77l58-253q13-57 13-125z" horiz-adv-x="857.1" /> + +<glyph glyph-name="bicycle" unicode="" d="M425 207h-175q-22 0-32 20t4 37l105 140q-37 17-77 17-74 0-126-52t-53-126 53-126 126-53q64 0 113 41t62 102z m-104 72h104q-10 47-42 82z m268 0l161 214h-268l-55-74q59-57 70-140h92z m625-36q0 74-52 126t-126 52q-34 0-68-13l97-145q9-13 6-27t-15-23q-9-6-20-6-20 0-30 16l-97 145q-52-53-52-125 0-74 53-126t126-53 126 53 52 126z m72 0q0-103-74-177t-176-73-177 73-73 177q0 54 22 102t61 84l-36 54-197-261q-10-15-29-15h-110q-13-91-83-153t-164-61q-103 0-177 73t-73 177 73 177 177 73q64 0 120-31l76 102h-125q-14 0-25 11t-10 25 10 25 25 11h215v-72h243l-48 72h-124q-14 0-25 10t-11 25 11 26 25 10h143q18 0 30-15l149-224q50 25 107 25 103 0 176-73t74-177z" horiz-adv-x="1285.7" /> + +<glyph glyph-name="money" unicode="" d="M429 207h214v54h-72v250h-63l-83-77 43-44q24 20 31 31h1v-160h-71v-54z m285 143q0-39-11-79t-34-75-56-56-77-22-77 22-57 56-33 75-12 79 12 79 33 75 57 56 77 22 77-22 56-56 34-75 11-79z m286-143v286q-59 0-101 42t-42 101h-643q0-59-42-101t-101-42v-286q60 0 101-42t42-101h643q0 59 42 101t101 42z m71 464v-642q0-15-10-25t-25-11h-1000q-15 0-25 11t-11 25v642q0 15 11 26t25 10h1000q14 0 25-10t10-26z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="euro" unicode="" d="M545 121l19-89q2-7-1-13t-10-8l-3 0q-2-1-6-2t-9-3-12-3-14-3-16-2-19-3-21-2-21 0q-131 0-228 73t-133 196h-53q-7 0-13 5t-5 13v63q0 7 5 12t13 6h37q-1 31 0 58h-37q-8 0-13 5t-5 13v64q0 8 5 13t13 5h55q37 117 135 188t224 72q57 0 108-13 6-2 11-9 4-6 2-13l-24-89q-2-7-8-11t-13-1l-2 1q-3 0-7 1l-10 2t-12 2-15 2-16 1-16 1q-71 0-126-36t-84-98h261q9 0 14-7 6-7 4-15l-13-63q-3-15-18-15h-273q-1-20 0-58h257q8 0 13-7 5-7 4-15l-14-63q-1-6-6-10t-11-4h-216q27-65 84-104t127-38q10 0 20 1t19 2 16 2 14 3 10 3l7 1 3 2q7 2 14-2 7-3 9-11z" horiz-adv-x="571.4" /> + +<glyph glyph-name="pound" unicode="" d="M569 216v-205q0-8-5-13t-13-5h-533q-8 0-13 5t-5 13v83q0 8 5 13t13 5h54v214h-53q-8 0-13 5t-5 13v73q0 8 5 13t13 5h53v124q0 96 69 158t175 62q104 0 187-70 5-5 6-12t-4-12l-57-71q-5-6-13-7-7-1-13 4-2 3-14 11t-39 18-51 10q-48 0-77-27t-29-68v-120h170q8 0 13-5t5-13v-73q0-7-5-13t-13-5h-170v-211h231v101q0 7 5 12t13 5h90q8 0 13-5t5-12z" horiz-adv-x="571.4" /> + +<glyph glyph-name="dollar" unicode="" d="M546 189q0-86-56-147t-144-77v-97q0-8-5-13t-13-5h-75q-7 0-13 5t-5 13v97q-37 5-71 18t-57 25-41 26-26 21-10 10q-9 12-1 23l58 76q3 5 12 6 9 1 14-5l1-1q63-55 135-70 21-4 42-4 45 0 79 24t35 68q0 16-9 30t-18 23-33 21-37 18-45 18q-21 9-34 14t-34 15-35 17-32 20-29 24-25 27-20 32-11 37-5 44q0 77 55 135t142 75v100q0 7 5 13t13 5h75q8 0 13-5t5-13v-98q32-3 62-13t48-19 36-20 21-17 9-7q9-11 3-22l-46-81q-4-9-12-9-8-2-15 4-2 2-9 7t-21 14-33 18-42 15-47 6q-53 0-87-24t-33-62q0-14 4-27t17-23 22-18 31-18 34-15 39-15q30-11 45-17t43-20 42-24 34-28 30-35 18-43 7-52z" horiz-adv-x="571.4" /> + +<glyph glyph-name="sort" unicode="" d="M571 243q0-15-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 10-11 25t11 25 25 11h500q14 0 25-11t10-25z m0 214q0-14-10-25t-25-11h-500q-15 0-25 11t-11 25 11 25l250 250q10 11 25 11t25-11l250-250q10-10 10-25z" horiz-adv-x="571.4" /> + +<glyph glyph-name="sort-down" unicode="" d="M571 243q0-15-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 10-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" /> + +<glyph glyph-name="sort-up" unicode="" d="M571 457q0-14-10-25t-25-11h-500q-15 0-25 11t-11 25 11 25l250 250q10 11 25 11t25-11l250-250q10-10 10-25z" horiz-adv-x="571.4" /> + +<glyph glyph-name="sort-alt-up" unicode="" d="M411 46q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m589-71v-107q0-8-5-13t-13-5h-464q-8 0-13 5t-5 13v107q0 8 5 13t13 5h464q8 0 13-5t5-13z m-107 286v-107q0-8-5-13t-13-5h-357q-8 0-13 5t-5 13v107q0 8 5 13t13 5h357q8 0 13-5t5-13z m-107 285v-107q0-7-5-12t-13-6h-250q-8 0-13 6t-5 12v107q0 8 5 13t13 5h250q8 0 13-5t5-13z m-107 286v-107q0-8-5-13t-13-5h-143q-8 0-13 5t-5 13v107q0 8 5 13t13 5h143q8 0 13-5t5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="sort-alt-down" unicode="" d="M679-25v-107q0-8-5-13t-13-5h-143q-8 0-13 5t-5 13v107q0 8 5 13t13 5h143q8 0 13-5t5-13z m-268 71q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m375 215v-107q0-8-5-13t-13-5h-250q-8 0-13 5t-5 13v107q0 8 5 13t13 5h250q8 0 13-5t5-13z m107 285v-107q0-7-5-12t-13-6h-357q-8 0-13 6t-5 12v107q0 8 5 13t13 5h357q8 0 13-5t5-13z m107 286v-107q0-8-5-13t-13-5h-464q-8 0-13 5t-5 13v107q0 8 5 13t13 5h464q8 0 13-5t5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="sort-name-up" unicode="" d="M665 622h98l-40 122-6 26q-2 9-2 11h-2l-1-11q0 0-2-10t-5-16z m-254-576q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m466-66v-130h-326v50l206 295q7 11 12 16l6 5v1q-1 0-3 0t-5 0q-6-2-16-2h-130v-64h-67v128h317v-50l-206-296q-4-4-12-14l-6-7v-1l8 1q5 2 16 2h139v66h67z m50 501v-60h-161v60h42l-26 80h-136l-26-80h42v-60h-160v60h39l128 369h91l128-369h39z" horiz-adv-x="928.6" /> + +<glyph glyph-name="sort-name-down" unicode="" d="M665 51h98l-40 122-6 26q-2 9-2 11h-2l-1-11q0-1-2-10t-5-16z m-254-5q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m516-137v-59h-161v59h42l-26 80h-136l-26-80h42v-59h-160v59h39l128 370h91l128-370h39z m-50 643v-131h-326v51l206 295q7 10 12 15l6 5v2q-1 0-3-1t-5 0q-6-2-16-2h-130v-64h-67v128h317v-50l-206-295q-4-5-12-15l-6-5v-2l8 2q5 0 16 0h139v67h67z" horiz-adv-x="928.6" /> + +<glyph glyph-name="sort-number-up" unicode="" d="M751 117q0 36-24 65t-58 30q-29 0-46-21t-17-52 20-53 58-22q28 0 48 15t19 38z m-340-71q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m418 39q0-35-7-68t-23-64-38-53-55-36-71-14q-35 0-60 9-14 4-24 8l22 63q9-4 17-6 21-7 42-7 47 0 75 33t37 81h-1q-11-13-34-21t-47-8q-59 0-97 40t-37 97q0 58 40 99t101 41q69 0 115-53t45-141z m-16 400v-64h-262v64h93v241q0 4 0 11t1 9v9h-2l-3-7q-5-7-15-17l-35-32-45 48 107 103h68v-365h93z" horiz-adv-x="857.1" /> + +<glyph glyph-name="sort-number-down" unicode="" d="M751 689q0 35-24 65t-58 29q-29 0-46-21t-17-52 20-53 58-21q28 0 48 15t19 38z m-340-643q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m402-132v-64h-262v64h93v241q0 4 0 10t1 10v9h-2l-3-7q-5-7-15-17l-35-33-45 48 107 104h68v-365h93z m16 742q0-34-7-67t-23-64-38-53-55-37-71-14q-35 0-60 9-14 5-24 9l22 63q9-4 17-6 21-8 42-8 47 0 75 33t37 81h-1q-11-13-34-20t-47-8q-59 0-97 40t-37 96q0 59 40 99t101 41q69 0 115-53t45-141z" horiz-adv-x="857.1" /> + +<glyph glyph-name="hammer" unicode="" d="M988-7q0-30-20-50l-60-61q-22-20-51-20-29 0-50 20l-203 204q-21 20-21 50 0 29 24 53l-143 143-70-70q-8-8-19-8t-19 8q1-1 7-7t7-7 6-6 5-8 4-8 3-9 0-10q0-21-15-38-2-1-9-10t-11-11-10-9-13-9-12-5-14-3q-23 0-38 16l-228 228q-16 15-16 38 0 7 3 14t5 12 9 13 9 10 11 11 10 9q17 15 38 15 6 0 10 0t9-3 8-4 8-5 6-6 7-7 7-7q-8 8-8 19t8 19l194 194q8 8 19 8t19-8q-1 1-7 7t-7 7-6 7-5 7-3 8-4 9 0 10q0 21 15 38 2 2 9 10t11 11 10 10 13 8 12 5 14 3q23 0 38-16l228-228q16-15 16-38 0-7-3-14t-5-12-8-13-10-10-11-11-10-9q-17-15-38-15-6 0-10 0t-9 4-8 3-7 5-7 6-7 7-7 7q8-8 8-19t-8-19l-70-70 143-143q24 24 53 24 29 0 51-21l203-202q20-22 20-51z" horiz-adv-x="1000" /> + +<glyph glyph-name="gauge" unicode="" d="M214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" /> + +<glyph glyph-name="sitemap" unicode="" d="M1000 154v-179q0-22-16-38t-38-16h-178q-22 0-38 16t-16 38v179q0 22 16 38t38 15h53v107h-285v-107h53q23 0 38-15t16-38v-179q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v179q0 22 16 38t38 15h53v107h-285v-107h53q23 0 38-15t16-38v-179q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v179q0 22 16 38t38 15h53v107q0 29 21 51t51 21h285v107h-53q-23 0-38 16t-16 37v179q0 22 16 38t38 16h178q23 0 38-16t16-38v-179q0-22-16-37t-38-16h-53v-107h285q29 0 51-21t21-51v-107h53q23 0 38-15t16-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="spinner" unicode="" d="M294 72q0-29-21-50t-51-21q-29 0-50 21t-21 50q0 30 21 51t50 21 51-21 21-51z m277-115q0-29-20-50t-51-21-50 21-21 50 21 51 50 21 51-21 20-51z m-392 393q0-30-21-50t-51-21-50 21-21 50 21 51 50 20 51-20 21-51z m670-278q0-29-21-50t-50-21q-30 0-51 21t-20 50 20 51 51 21 50-21 21-51z m-538 556q0-37-26-63t-63-26-63 26-26 63 26 63 63 26 63-26 26-63z m653-278q0-30-21-50t-50-21-51 21-21 50 21 51 51 20 50-20 21-51z m-357 393q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m296-115q0-52-37-88t-88-37q-52 0-88 37t-37 88q0 51 37 88t88 37q51 0 88-37t37-88z" horiz-adv-x="1000" /> + +<glyph glyph-name="coffee" unicode="" d="M929 493q0 45-32 76t-76 31h-35v-214h35q45 0 76 31t32 76z m-929-429h1000q0-59-42-101t-101-42h-714q-59 0-101 42t-42 101z m1036 429q0-89-63-152t-152-62h-35v-18q0-52-37-88t-88-37h-393q-51 0-88 37t-37 88v410q0 15 11 26t25 10h642q89 0 152-63t63-151z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="food" unicode="" d="M357 814v-357q0-34-20-62t-51-39v-435q0-29-21-50t-51-21h-71q-29 0-50 21t-22 50v435q-31 11-51 39t-20 62v357q0 15 11 25t25 11 25-11 10-25v-232q0-14 11-25t25-11 25 11 11 25v232q0 15 11 25t25 11 25-11 10-25v-232q0-14 11-25t25-11 25 11 11 25v232q0 15 10 25t25 11 26-11 10-25z m429 0v-893q0-29-21-50t-51-21h-71q-29 0-50 21t-22 50v286h-125q-7 0-12 5t-5 13v446q0 74 52 127t126 52h143q15 0 25-11t11-25z" horiz-adv-x="785.7" /> + +<glyph glyph-name="beer" unicode="" d="M357 350v214h-143v-143q0-29 21-50t51-21h71z m572-250v-107h-643v107l71 107h-71q-89 0-152 63t-63 151v179l-35 36 18 71h267l18 72h536l18-108-36-17v-447z" horiz-adv-x="928.6" /> + +<glyph glyph-name="user-md" unicode="" d="M214 100q0-14-10-25t-25-11-25 11-11 25 11 25 25 11 25-11 10-25z m572-34q0-68-41-106t-108-39h-488q-67 0-108 39t-41 106q0 38 3 73t14 77 26 74 45 58 67 33q-12-29-12-67v-113q-32-11-52-39t-20-62q0-45 32-76t76-31 76 31 31 76q0 34-20 62t-52 39v113q0 35 14 52 74-58 165-58t165 58q13-17 13-52v-35q-59 0-101-42t-41-101v-50q-18-16-18-40 0-22 15-37t38-16 38 16 16 37q0 24-18 40v50q0 29 21 50t50 21 51-21 21-50v-50q-18-16-18-40 0-22 16-37t38-16 38 16 15 37q0 24-18 40v50q0 38-19 71t-52 52q0 6 0 24t0 27-1 23-4 26-7 22q38-8 67-33t45-58 26-74 14-77 3-73z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" /> + +<glyph glyph-name="stethoscope" unicode="" d="M714 457q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m72 0q0-34-20-62t-52-39v-220q0-89-73-152t-177-63-176 63-74 152v73q-91 12-153 72t-61 140v286q0 15 11 25t25 11q3 0 9-1 9 17 26 27t36 10q30 0 51-21t21-51-21-50-51-21q-18 0-36 10v-225q0-59 53-101t126-41 126 41 53 101v225q-18-10-36-10-30 0-51 21t-21 50 21 51 51 21q19 0 36-10t26-27q6 1 9 1 15 0 25-11t11-25v-286q0-80-61-140t-153-72v-73q0-59 52-101t126-42 126 42 53 101v220q-32 12-52 39t-20 62q0 45 32 76t76 31 76-31 31-76z" horiz-adv-x="785.7" /> + +<glyph glyph-name="ambulance" unicode="" d="M357 64q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m-214 286h214v143h-88q-8-1-12-5l-109-109q-4-7-5-12v-17z m714-286q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m72 375v107q0 8-5 13t-13 5h-125v125q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-125h-125q-8 0-13-5t-5-13v-107q0-7 5-12t13-6h125v-125q0-7 5-12t13-5h107q8 0 13 5t5 12v125h125q8 0 13 6t5 12z m142 304v-643q0-14-10-25t-25-11h-107q0-59-42-101t-101-42-101 42-42 101h-214q0-59-42-101t-101-42-101 42-42 101h-72q-14 0-25 11t-10 25 10 25 25 11v232q0 14 8 32t18 29l110 110q11 11 29 18t32 7h89v179q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="medkit" unicode="" d="M714 225v107q0 8-5 13t-13 5h-125v125q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-125h-125q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h125v-125q0-8 5-13t12-5h108q7 0 12 5t5 13v125h125q8 0 13 5t5 13z m-357 411h286v71h-286v-71z m-214 0v-715h-18q-51 0-88 37t-37 88v465q0 51 37 88t88 37h18z m661 0v-715h-608v715h90v89q0 22 15 38t38 16h322q22 0 38-16t15-38v-89h90z m196-125v-465q0-51-37-88t-88-37h-18v715h18q51 0 88-37t37-88z" horiz-adv-x="1000" /> + +<glyph glyph-name="h-sigh" unicode="" d="M714 100v500q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-179h-285v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-500q0-14 11-25t25-11h71q15 0 25 11t11 25v179h285v-179q0-14 11-25t25-11h72q14 0 25 11t10 25z m143 518v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="hospital" unicode="" d="M214 118v-36q0-7-5-12t-13-6h-35q-7 0-13 6t-5 12v36q0 7 5 12t13 6h35q8 0 13-6t5-12z m0 143v-36q0-7-5-13t-13-5h-35q-7 0-13 5t-5 13v36q0 7 5 12t13 6h35q8 0 13-6t5-12z m143 0v-36q0-7-5-13t-13-5h-35q-8 0-13 5t-5 13v36q0 7 5 12t13 6h35q8 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-35q-7 0-13 5t-5 13v36q0 7 5 12t13 5h35q8 0 13-5t5-12z m429-286v-36q0-7-5-12t-13-6h-36q-7 0-12 6t-6 12v36q0 7 6 12t12 6h36q7 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-5 13v36q0 7 5 12t12 6h36q7 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-35q-8 0-13 5t-5 13v36q0 7 5 12t13 5h35q8 0 13-5t5-12z m286-143v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-6 13v36q0 7 6 12t12 6h36q7 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-5 13v36q0 7 5 12t12 5h36q7 0 13-5t5-12z m143 0v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-6 13v36q0 7 6 12t12 5h36q7 0 13-5t5-12z m-143-483h214v643h-143v-18q0-22-15-37t-38-16h-250q-22 0-38 16t-16 37v18h-143v-643h215v125q0 8 5 13t13 5h178q7 0 13-5t5-13v-125z m0 661v179q0 7-5 12t-13 6h-36q-7 0-12-6t-5-12v-54h-72v54q0 7-5 12t-13 6h-35q-8 0-13-6t-5-12v-179q0-7 5-12t13-6h35q8 0 13 6t5 12v54h72v-54q0-7 5-12t12-6h36q7 0 13 6t5 12z m286 18v-714q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v714q0 15 11 25t25 11h178v160q0 23 16 38t38 16h250q22 0 38-16t15-38v-160h179q15 0 25-11t11-25z" horiz-adv-x="785.7" /> + +<glyph glyph-name="building" unicode="" d="M214 118v-36q0-7-5-12t-13-6h-35q-7 0-13 6t-5 12v36q0 7 5 12t13 6h35q8 0 13-6t5-12z m0 143v-36q0-7-5-13t-13-5h-35q-7 0-13 5t-5 13v36q0 7 5 12t13 6h35q8 0 13-6t5-12z m143 0v-36q0-7-5-13t-13-5h-35q-8 0-13 5t-5 13v36q0 7 5 12t13 6h35q8 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-35q-7 0-13 5t-5 13v36q0 7 5 12t13 5h35q8 0 13-5t5-12z m429-286v-36q0-7-5-12t-13-6h-36q-7 0-12 6t-6 12v36q0 7 6 12t12 6h36q7 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-5 13v36q0 7 5 12t12 6h36q7 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-35q-8 0-13 5t-5 13v36q0 7 5 12t13 5h35q8 0 13-5t5-12z m-143 142v-35q0-7-5-13t-13-5h-35q-7 0-13 5t-5 13v35q0 8 5 13t13 5h35q8 0 13-5t5-13z m429-285v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-6 13v36q0 7 6 12t12 6h36q7 0 13-6t5-12z m-143 143v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-5 13v36q0 7 5 12t12 5h36q7 0 13-5t5-12z m-143 142v-35q0-7-5-13t-13-5h-35q-8 0-13 5t-5 13v35q0 8 5 13t13 5h35q8 0 13-5t5-13z m-143 143v-35q0-8-5-13t-13-5h-35q-7 0-13 5t-5 13v35q0 8 5 13t13 5h35q8 0 13-5t5-13z m429-285v-36q0-7-5-13t-13-5h-36q-7 0-12 5t-6 13v36q0 7 6 12t12 5h36q7 0 13-5t5-12z m-143 142v-35q0-7-5-13t-13-5h-36q-7 0-12 5t-5 13v35q0 8 5 13t12 5h36q7 0 13-5t5-13z m-143 143v-35q0-8-5-13t-13-5h-35q-8 0-13 5t-5 13v35q0 8 5 13t13 5h35q8 0 13-5t5-13z m286-143v-35q0-7-5-13t-13-5h-36q-7 0-12 5t-6 13v35q0 8 6 13t12 5h36q7 0 13-5t5-13z m-143 143v-35q0-8-5-13t-13-5h-36q-7 0-12 5t-5 13v35q0 8 5 13t12 5h36q7 0 13-5t5-13z m143 0v-35q0-8-5-13t-13-5h-36q-7 0-12 5t-6 13v35q0 8 6 13t12 5h36q7 0 13-5t5-13z m-143-768h214v858h-643v-858h215v125q0 8 5 13t13 5h178q7 0 13-5t5-13v-125z m286 893v-928q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v928q0 15 11 25t25 11h714q15 0 25-11t11-25z" horiz-adv-x="785.7" /> + +<glyph glyph-name="building-filled" unicode="" d="M750 850q15 0 25-11t11-25v-928q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v928q0 15 11 25t25 11h714z m-464-161v-35q0-8 5-13t13-5h35q8 0 13 5t5 13v35q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13z m0-143v-35q0-8 5-13t13-5h35q8 0 13 5t5 13v35q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13z m0-142v-36q0-8 5-13t13-5h35q8 0 13 5t5 13v36q0 7-5 12t-13 5h-35q-8 0-13-5t-5-12z m0-143v-36q0-8 5-13t13-5h35q8 0 13 5t5 13v36q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13z m-72-179v36q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h35q8 0 13 5t5 13z m0 143v36q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h35q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-35q-8 0-13-5t-5-12v-36q0-8 5-13t13-5h35q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-35q0-8 5-13t13-5h35q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-35q0-8 5-13t13-5h35q8 0 13 5t5 13z m286-715v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m0 286v36q0 8-5 13t-13 5h-36q-7 0-12-5t-5-13v-36q0-8 5-13t12-5h36q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-36q-7 0-12-5t-5-12v-36q0-8 5-13t12-5h36q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-36q-7 0-12-5t-5-13v-35q0-8 5-13t12-5h36q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-36q-7 0-12-5t-5-13v-35q0-8 5-13t12-5h36q8 0 13 5t5 13z m143-572v36q0 8-5 13t-13 5h-36q-7 0-12-5t-6-13v-36q0-8 6-13t12-5h36q8 0 13 5t5 13z m0 143v36q0 8-5 13t-13 5h-36q-7 0-12-5t-6-13v-36q0-8 6-13t12-5h36q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-36q-7 0-12-5t-6-12v-36q0-8 6-13t12-5h36q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-36q-7 0-12-5t-6-13v-35q0-8 6-13t12-5h36q8 0 13 5t5 13z m0 143v35q0 8-5 13t-13 5h-36q-7 0-12-5t-6-13v-35q0-8 6-13t12-5h36q8 0 13 5t5 13z" horiz-adv-x="857.1" /> + +<glyph glyph-name="bank" unicode="" d="M536 850l535-214v-72h-71q0-14-11-25t-27-10h-852q-16 0-27 10t-12 25h-71v72z m-393-357h143v-429h71v429h143v-429h71v429h143v-429h72v429h143v-429h33q15 0 27-10t11-25v-36h-929v36q0 14 12 25t27 10h33v429z m890-536q16 0 27-11t11-25v-71h-1071v71q0 15 11 25t28 11h994z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="smile" unicode="" d="M633 250q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="frown" unicode="" d="M633 164q4-14-2-27t-21-17-27 2-18 21q-14 45-52 72t-84 28-85-28-52-72q-4-14-17-21t-27-2q-15 4-21 17t-3 27q21 68 77 109t128 41 127-41 77-109z m-276 329q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="meh" unicode="" d="M643 243q0-15-11-25t-25-11h-357q-14 0-25 11t-11 25 11 25 25 11h357q15 0 25-11t11-25z m-286 250q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="anchor" unicode="" d="M536 707q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m464-518v-196q0-12-11-17-5-1-7-1-7 0-13 5l-52 52q-66-80-177-127t-240-46-240 46-177 127l-52-52q-5-5-13-5-2 0-7 1-11 5-11 17v196q0 8 5 13t13 5h196q13 0 17-11 5-11-4-19l-56-56q38-51 106-86t152-46v361h-108q-14 0-25 11t-10 25v71q0 15 10 25t25 11h108v91q-33 19-52 51t-20 72q0 59 42 101t101 42 101-42 42-101q0-39-20-72t-52-51v-91h108q14 0 25-11t10-25v-71q0-15-10-25t-25-11h-108v-361q84 11 152 46t106 86l-56 56q-8 8-4 19 4 11 17 11h196q8 0 13-5t5-13z" horiz-adv-x="1000" /> + +<glyph glyph-name="terminal" unicode="" d="M438 225h250v-62h-250v62z m-188-62l188 187-188 188-47-47 141-141-141-141 47-46z m625 500v-625c0-35-28-63-62-63h-750c-35 0-63 28-63 63v625c0 34 28 62 63 62h750c34 0 62-28 62-62z m-62 0h-750v-625h750v625z" horiz-adv-x="875" /> + +<glyph glyph-name="eraser" unicode="" d="M500 64l188 215h-429l-188-215h429z m565 601q9-19 6-40t-17-36l-500-572q-22-24-54-24h-429q-21 0-38 11t-27 31q-8 19-5 40t17 36l500 572q21 24 53 24h429q21 0 39-11t26-31z" horiz-adv-x="1071.4" /> + +<glyph glyph-name="puzzle" unicode="" d="M929 237q0-45-25-75t-69-30q-23 0-43 10t-33 21-32 21-39 10q-62 0-62-69 0-22 9-65t8-64v-3q-12 0-18 0-19-2-54-7t-65-7-54-3q-35 0-58 15t-23 47q0 20 9 39t22 32 21 33 10 43q0 44-31 69t-75 25q-47 0-80-26t-33-71q0-24 9-46t18-36 19-30 8-28q0-25-25-50-21-19-65-19-54 0-137 13-5 1-16 2t-15 3l-7 1q-1 0-2 0-1 0-1 1v571q1 0 10-2t19-2 12-2q83-14 137-14 44 0 65 20 25 24 25 49 0 13-8 29t-19 29-18 36-9 47q0 45 33 71t81 25q44 0 74-25t31-69q0-23-10-43t-21-33-22-31-9-40q0-32 23-47t58-14q35 0 100 8t91 9v-1q-1-1-2-9t-3-19-2-12q-13-84-13-137 0-45 19-65 25-26 50-26 12 0 28 8t30 19 36 19 46 8q46 0 71-33t26-80z" horiz-adv-x="928.6" /> + +<glyph glyph-name="shield" unicode="" d="M607 314v357h-250v-634q67 35 119 76 131 103 131 201z m107 429v-429q0-48-18-95t-47-84-66-71-70-57-68-43-50-28-23-11q-7-4-15-4t-14 4q-9 4-24 11t-50 28-67 43-71 57-66 71-46 84-19 95v429q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="714.3" /> + +<glyph glyph-name="extinguisher" unicode="" d="M286 743q0 14-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m500 18v-179q0-9-7-14-4-4-11-4-2 0-4 1l-250 53q-6 2-10 7t-4 11h-143v-57q62-13 103-62t40-113v-447q0-14-11-25t-25-11h-285q-15 0-25 11t-11 25v447q0 59 35 106t90 64v62h-18q-33 0-64-13t-51-30-37-37-23-30-7-14q-10-19-32-19-9 0-16 4-13 7-18 20t2 28q3 5 8 14t21 30 34 39 47 38 61 29q-14 23-14 48 0 37 26 63t63 26 63-26 26-63q0-19-7-36h168q0 6 4 11t10 6l250 54q2 1 4 1 7 0 11-4 7-5 7-14z" horiz-adv-x="785.7" /> + +<glyph glyph-name="bullseye" unicode="" d="M571 350q0-59-41-101t-101-42-101 42-42 101 42 101 101 42 101-42 41-101z m72 0q0 89-63 152t-151 62-152-62-63-152 63-151 152-63 151 63 63 151z m71 0q0-118-83-202t-202-84-202 84-84 202 84 202 202 84 202-84 83-202z m72 0q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="wheelchair" unicode="" d="M571 188l57-114q-33-100-117-162t-190-62q-87 0-161 43t-117 117-43 161q0 101 58 185t154 117l9-73q-68-30-109-92t-41-137q0-103 74-176t176-74q71 0 130 37t92 98 28 132z m306-56l32-64-143-71q-7-4-16-4-22 0-32 19l-133 267h-264q-13 0-23 9t-12 22l-54 435q-1 10 4 24 7 28 31 46t54 17q37 0 64-26t26-63q0-39-29-66t-67-23l20-161h236v-72h-227l9-71h254q23 0 32-19l127-254z" horiz-adv-x="928.6" /> + +<glyph glyph-name="attach" unicode="" d="M0 65q8 68 67 127l383 383q117 115 211 33 84-86-36-209l-353-351-66 70 349 349q2 0 8 6l8 8t7 9 6 10 6 9 4 11l0 10t-2 12q-19 17-74-37l-381-381q-37-35-41-69-4-39 35-78 41-33 70-28t71 46q82 80 218 215t194 195q2 2 18 17t17 17 15 16 15 17 12 17 13 19 11 20 10 25q16 57-33 123t-115 75q-68 7-152-73l-418-418-69 67 418 420q98 95 199 100t190-83q105-107 74-236-19-74-94-150-86-84-246-245l-209-209q-70-70-150-72-68 0-125 57-70 70-64 156z" horiz-adv-x="896" /> + +<glyph glyph-name="paw" unicode="" d="M435 587q0-34-10-64t-35-51-59-22q-42 0-77 32t-51 76-17 84q0 33 10 63t36 52 58 22q43 0 78-32t51-76 16-84z m-191-270q0-45-23-77t-66-33q-43 0-79 31t-56 74-20 85q0 45 23 78t67 33q42 0 79-31t56-75 19-85z m220 15q66 0 143-54t127-132 52-142q0-26-10-43t-27-25-36-12-42-3q-38 0-105 25t-102 26q-36 0-107-25t-112-25q-102 0-102 82 0 48 31 106t78 108 105 81 107 33z m134 118q-34 0-59 22t-35 51-11 64q0 41 17 84t51 76 77 32q34 0 59-22t35-52 11-63q0-41-17-84t-51-76-77-32z m241 58q43 0 66-33t24-78q0-41-20-85t-56-74-79-31q-43 0-66 33t-24 77q0 41 20 85t56 75 79 31z" horiz-adv-x="928.6" /> + +<glyph glyph-name="spoon" unicode="" d="M393 555q0-81-32-135t-85-76l25-458q2-15-9-25t-24-11h-107q-15 0-25 11t-9 25l25 458q-53 21-84 76t-32 135q0 72 23 140t66 111 89 44 90-44 65-111 24-140z" horiz-adv-x="428.6" /> + +<glyph glyph-name="cube" unicode="" d="M500-59l357 195v355l-357-130v-420z m-36 483l390 141-390 142-389-142z m465 140v-428q0-20-10-37t-28-26l-393-214q-15-9-34-9t-34 9l-393 214q-17 10-27 26t-10 37v428q0 23 13 41t34 26l393 143q12 5 24 5t25-5l393-143q21-8 34-26t13-41z" horiz-adv-x="1000" /> + +<glyph glyph-name="cubes" unicode="" d="M357-61l214 107v176l-214-92v-191z m-36 254l226 96-226 97-225-97z m608-254l214 107v176l-214-92v-191z m-36 254l225 96-225 97-226-97z m-250 163l214 92v149l-214-92v-149z m-36 212l246 105-246 106-246-106z m607-289v-233q0-20-10-37t-29-26l-250-125q-14-8-32-8t-32 8l-250 125q-2 1-4 2-1-1-4-2l-250-125q-14-8-32-8t-31 8l-250 125q-19 9-29 26t-11 37v233q0 21 12 39t32 26l242 104v223q0 22 12 40t31 26l250 107q13 6 28 6t28-6l250-107q20-9 32-26t12-40v-223l242-104q20-8 32-26t11-39z" horiz-adv-x="1285.7" /> + +<glyph glyph-name="recycle" unicode="" d="M467 198l-9-206-1-12-234 16q-20 2-38 18t-26 36q-6 15-8 31t2 36 7 31 12 36 11 29q43-6 284-15z m-216 327l100-212-82 52q-35-41-62-81t-41-70-22-53-10-35l-2-11-106 199q-10 14-10 31t3 26l4 10q20 35 64 105l-78 48z m687-289l-105-200q-7-16-21-26t-24-12l-10-2q-40-4-122-7l4-91-128 205 118 202 4-97q94-9 157-2t95 18z m-439 516q-26-35-147-243l-177 104-11 7 126 199q11 17 33 25t45 5q13-1 27-6t23-12 23-18 21-20 20-22 17-19z m366-171l118-203q10-21 7-42t-15-42q-7-11-18-20t-22-16-27-12-26-9-29-8-25-7q-19 40-148 244l174 108z m-80 126l79 46-122-208-234 11 84 48q-19 50-42 93t-42 69-36 44-26 26l-10 7 226 0q18 1 33-6t22-16l6-8q21-34 62-106z" horiz-adv-x="1000" /> + +<glyph glyph-name="tree" unicode="" d="M839 29q0-15-10-25t-25-11h-258q0-10 3-49t3-61q0-14-10-23t-24-10h-179q-14 0-24 10t-10 23q0 22 3 61t3 49h-257q-15 0-25 11t-11 25 11 25l224 225h-128q-14 0-25 10t-11 25 11 25l224 225h-110q-14 0-25 11t-10 25 10 25l215 214q10 11 25 11t25-11l214-214q11-10 11-25t-11-25-25-11h-110l224-225q11-10 11-25t-11-25-25-10h-128l225-225q10-11 10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="database" unicode="" d="M429 421q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-19-215 19-156 52-58 71v95q66-47 181-71t248-24z m0-428q132 0 247 24t181 71v-95q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v95q66-47 181-71t248-24z m0 214q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-20-215 20-156 52-58 71v95q66-47 181-71t248-24z m0 643q116 0 214-19t157-52 57-72v-71q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v71q0 39 58 72t156 52 215 19z" horiz-adv-x="857.1" /> + +<glyph glyph-name="lifebuoy" unicode="" d="M500 850q102 0 194-40t160-106 106-160 40-194-40-194-106-160-160-106-194-40-194 40-160 106-106 160-40 194 40 194 106 160 160 106 194 40z m0-71q-106 0-201-51l108-108q46 16 93 16t93-16l109 108q-96 51-202 51z m-378-630l108 108q-16 46-16 93t16 93l-108 109q-51-96-51-202t51-201z m378-228q106 0 202 51l-109 108q-46-16-93-16t-93 16l-108-108q95-51 201-51z m0 215q89 0 152 63t62 151-62 152-152 62-151-62-63-152 63-151 151-63z m270 121l108-108q51 95 51 201t-51 202l-108-109q16-46 16-93t-16-93z" horiz-adv-x="1000" /> + +<glyph glyph-name="birthday" unicode="" d="M1000 64v-214h-1000v214q25 0 47 8t33 15 27 21q16 15 28 22t32 6q13 0 24-4t18-9 18-15q16-14 26-21t33-15 48-8q25 0 47 8t33 15 26 21q12 11 18 15t18 9 24 4q20 0 32-6t28-22q16-13 27-21t32-15 48-8 47 8 33 15 26 21q17 15 29 22t32 6q19 0 31-6t28-22q16-13 27-21t33-15 47-8z m0 179v-107q-13 0-25 4t-17 8-18 15q-16 14-26 21t-33 15-47 8q-26 0-48-8t-33-15-26-21q-12-10-18-15t-18-8-24-4q-20 0-32 6t-28 21q-17 14-27 21t-32 15-48 8q-25 0-47-8t-33-15-27-21q-11-10-18-15t-17-8-24-4q-20 0-32 6t-29 21q-15 14-26 21t-33 15-47 8q-26 0-48-8t-32-15-27-21q-16-15-28-21t-32-6v107q0 45 31 76t76 31h36v250h143v-250h143v250h142v-250h143v250h143v-250h36q45 0 76-31t31-76z m-714 482q0-43-20-66t-52-23q-29 0-50 21t-21 50q0 16 5 29t13 19 18 15 17 18 13 25 5 37q22 0 47-41t25-84z m285 0q0-43-20-66t-51-23q-30 0-50 21t-21 50q0 16 5 29t13 19 17 15 18 18 13 25 5 37q21 0 46-41t25-84z m286 0q0-43-20-66t-51-23q-30 0-51 21t-21 50q0 16 6 29t13 19 17 15 17 18 13 25 6 37q21 0 46-41t25-84z" horiz-adv-x="1000" /> + +<glyph glyph-name="adn" unicode="" d="M429 503l112-171h-225z m203-296h53l-256 386-257-386h53l58 89h291z m225 143q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="android" unicode="" d="M275 581q9 0 16 6t6 15-6 16-16 6-15-6-6-16 6-15 15-6z m236 0q9 0 15 6t6 15-6 16-15 6-16-6-6-16 6-15 16-6z m-453-103q23 0 40-17t16-40v-240q0-24-16-41t-40-17-41 17-17 41v240q0 23 17 40t41 17z m591-11v-371q0-26-18-44t-43-18h-42v-127q0-24-16-40t-41-17-41 17-17 40v127h-77v-127q0-24-16-40t-41-17q-24 0-40 17t-17 40l-1 127h-41q-26 0-43 18t-18 44v371h512z m-129 226q59-30 95-85t36-121h-516q0 66 35 121t96 85l-39 73q-4 8 2 12 8 3 12-4l40-74q53 24 112 24t112-24l40 74q4 7 11 4 7-4 3-12z m266-272v-240q0-24-17-41t-41-17q-23 0-40 17t-17 41v240q0 24 17 40t40 17q24 0 41-17t17-40z" horiz-adv-x="785.7" /> + +<glyph glyph-name="angellist" unicode="" d="M532 639l-64-183 66-12q92 252 92 289 0 32-22 32-31 0-72-126z m-167-383l18-49q21 23 40 37l-18 3t-22 4-18 5z m-163 500q0-55 89-291 9 6 27 6 9 0 42-3l-68 196q-41 122-68 122-11 0-16-9t-6-21z m-44-424q0-20 29-66t65-86 56-39q8 0 14 8t7 15q0 13-18 57-7 17-18 40t-26 49-35 46-34 17q-12 0-26-15t-14-26z m-88-187q0-23 14-58 33-81 102-127t157-45q127 0 213 95 85 94 85 238 0 24 0 37t-7 35-17 31q-31 28-118 42t-151 15q-20 0-27-6-7-3-7-20 0-19 12-33t31-22 43-14 49-6 48-2 39 0h13q13 0 22-11 8-10 11-30-16-16-54-31-34-12-52-25-36-26-60-64t-25-76q0-18 10-50t10-49l-1-6q-3-7-3-8-76 5-81 120-5-1-23-1 1-4 1-11 0-30-22-50t-53-21q-46 0-93 44t-47 89q0 19 18 37 29-36 34-42 43-58 74-58 7 0 15 4t8 12q0 19-49 81t-65 62q-24 0-39-25t-15-51z m-64-5q0 57 24 91t76 49q-16 42-16 58 0 35 34 69t69 34q16 0 39-8-91 257-91 316 0 45 23 73t66 28q73 0 181-324 4-10 5-13 3 9 16 44t24 66 30 72 36 68 40 49 42 20q40 0 63-28t23-68q0-60-89-307 34-8 56-25t33-44 14-52 4-62q0-83-26-156t-74-126-117-83-156-31q-62 0-124 23-83 32-144 107t-61 160z" horiz-adv-x="714.3" /> + +<glyph glyph-name="apple" unicode="" d="M777 172q-21-70-68-139-72-110-144-110-27 0-78 18-48 18-84 18-34 0-79-19-45-19-74-19-85 0-168 145-82 146-82 281 0 127 63 208 63 81 159 81 40 0 98-17 58-17 77-17 25 0 80 19 57 19 97 19 66 0 119-36 29-20 58-56-44-37-64-66-36-52-36-115 0-69 38-125t88-70z m-209 655q0-34-17-76-16-42-52-77-30-30-60-40-20-7-58-10 2 83 44 143 41 60 139 83 1-2 2-6t1-6q0-2 0-6t1-5z" horiz-adv-x="785.7" /> + +<glyph glyph-name="behance" unicode="" d="M1031 661h-285v-69h285v69z m-140-238q-51 0-82-29t-34-80h227q-10 109-111 109z m9-326q35 0 68 18t42 48h123q-55-171-238-171-119 0-190 73t-70 194q0 116 72 193t188 77q77 0 134-38t86-100 28-139q0-9-1-26h-367q0-62 32-96t93-33z m-745 28h165q114 0 114 93 0 100-111 100h-168v-193z m0 299h156q44 0 69 21t26 63q0 80-106 80h-145v-164z m-155 284h332q48 0 86-8t71-26 50-54 17-86q0-101-96-147 64-17 96-64t33-114q0-41-14-76t-37-58-55-39-67-24-75-7h-341v703z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="bitbucket" unicode="" d="M455 371q4-35-28-57t-63-3q-21 9-29 32t-1 46 29 32q20 11 41 7t36-20 15-37z m62 11q-8 60-63 92t-110 7q-35-15-56-49t-20-72q3-51 44-87t92-31q51 4 85 47t28 93z m133 303q-11 15-31 25t-32 12-40 7q-162 26-316-1-24-4-37-7t-30-12-28-24q16-16 42-26t41-12 49-6q127-16 250-1 35 5 50 7t40 12 42 26z m32-578q-4-14-9-42t-7-47-16-39-33-32q-48-27-106-40t-112-12-113 10q-25 5-45 10t-43 15-41 25-29 34q-14 54-31 163l3 9 10 5q124-83 283-83t283 83q12-3 13-13t-3-25-4-21z m101 537q-15-94-62-366-3-17-15-31t-24-23-31-17q-140-70-340-49-139 15-220 78-8 6-14 14t-10 20-5 19-3 22-3 20q-5 27-15 83t-16 90-13 83-12 88q2 14 10 27t17 21 26 17 25 12 27 10q70 26 175 36 211 21 377-28 86-25 120-68 9-11 9-28t-3-30z" horiz-adv-x="785.7" /> + +<glyph glyph-name="bitbucket-squared" unicode="" d="M473 365q0 24-23 36t-43 1q-24-11-23-41t24-39q22-13 45 2t20 41z m45 8q4-36-20-67t-62-34-66 22-31 63q-1 28 14 52t40 36q40 17 79-6t46-66z m96 219q-11-12-30-19t-30-9-35-5q-86-11-181 0-24 4-35 6t-29 9-31 18q8 10 20 17t23 9 26 4q110 20 228 1 18-3 28-5t24-9 22-17z m23-417q0 4 3 15t2 18-10 9q-90-59-203-59t-205 59l-6-3-3-7q14-86 23-117 26-45 114-60 139-26 238 29 19 11 28 29t12 48 7 39z m73 387q5 30-5 42-24 31-86 49-121 35-272 20-74-7-126-26-21-8-33-13t-26-19-17-31q5-38 11-77t16-95 13-76q1-3 3-18t4-20 7-15 12-16q59-44 158-55 145-16 246 35 13 7 22 13t17 16 11 22q27 149 45 264z m147 56v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="css3" unicode="" d="M154 779h839l-148-744-449-149-389 149 39 198h166l-16-82 235-89 272 89 38 190h-675l33 165h674l22 107h-674z" horiz-adv-x="1000" /> + +<glyph glyph-name="delicious" unicode="" d="M821 82v268h-392v393h-268q-52 0-89-37t-36-88v-268h393v-393h267q52 0 89 37t36 88z m36 536v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="digg" unicode="" d="M183 693h114v-549h-297v389h183v160z m0-457v206h-69v-206h69z m160 297v-389h114v389h-114z m0 160v-114h114v114h-114z m160-160h297v-526h-297v91h183v46h-183v389z m183-297v206h-69v-206h69z m160 297h297v-526h-297v91h183v46h-183v389z m183-297v206h-69v-206h69z" horiz-adv-x="1142.9" /> + +<glyph glyph-name="dribbble" unicode="" d="M571 13q-23 134-78 278h-1l-1-1q-9-3-24-9t-56-27-77-46-73-64-57-82l-9 6q103-84 234-84 73 0 142 29z m-103 339q-11 27-29 62-174-52-376-52 0-4 0-12 0-69 24-132t69-112q28 49 69 93t80 69 73 45 55 27l21 7q2 1 7 2t7 3z m-59 118q-67 119-137 211-77-36-130-104t-72-152q169 0 339 45z m381-178q-117 33-228 16 49-133 71-262 62 42 104 106t53 140z m-449 414q-1 0-1-1 0 1 1 1z m329-81q-103 91-241 91-43 0-87-10 73-95 137-214 39 15 73 34t54 34 36 32 21 23z m125-271q-2 129-83 229l-1-1q-5-7-11-13t-24-25-40-34-55-36-74-36q14-30 25-53 1-3 3-10t5-9q20 2 41 4t41 1 39-1 35-2 32-3 27-4 20-3 14-3z m62-4q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="dropbox" unicode="" d="M224 456l276-171-191-159-273 178z m551-310v-60l-274-164v-1l0 1-1-1v1l-273 164v60l82-54 191 159v1l1-1 0 1v-1l192-159z m-466 638l191-159-276-169-188 150z m467-328l188-152-273-178-191 159z m-85 328l273-178-188-150-276 169z" horiz-adv-x="1000" /> + +<glyph glyph-name="facebook" unicode="" d="M535 843v-147h-87q-48 0-65-20t-17-60v-106h164l-22-165h-142v-424h-171v424h-142v165h142v122q0 104 58 161t155 57q82 0 127-7z" horiz-adv-x="571.4" /> + +<glyph glyph-name="facebook-squared" unicode="" d="M696 779q67 0 114-48t47-113v-536q0-66-47-113t-114-48h-104v333h111l16 129h-127v83q0 31 13 46t51 16l68 1v115q-35 5-100 5-75 0-121-44t-45-127v-95h-112v-129h112v-333h-297q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535z" horiz-adv-x="857.1" /> + +<glyph glyph-name="flickr" unicode="" d="M696 779q67 0 114-48t47-113v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535z m-306-429q0 49-35 84t-84 34-83-34-35-84 35-84 83-34 84 34 35 84z m314 0q0 49-34 84t-84 34-84-34-34-84 34-84 84-34 84 34 34 84z" horiz-adv-x="857.1" /> + +<glyph glyph-name="foursquare" unicode="" d="M558 608l21 108q3 13-5 22t-20 10h-397q-13 0-22-10t-8-20v-615q0-4 3 0l162 196q13 15 22 19t26 4h134q12 0 20 8t11 17q13 72 20 106 2 12-6 23t-21 10h-164q-16 0-26 11t-11 27v23q0 16 11 27t26 10h193q10 0 20 7t11 17z m127 124q-9-41-30-149t-39-195-19-97q-4-12-5-18t-8-18-14-19-21-12-33-5h-151q-7 0-12-6-5-5-238-275-12-14-33-16t-27 3q-30 12-30 54v787q0 31 21 58t67 26h495q53 0 71-30t6-88z m0 0l-88-441q2 9 19 97t39 195 30 149z" horiz-adv-x="714.3" /> + +<glyph glyph-name="git-squared" unicode="" d="M325 120q0-37-52-37-60 0-60 35 0 36 55 36 57 0 57-34z m-20 260q0-47-42-47-43 0-43 47 0 50 43 50 21 0 31-14t11-36z m92 42v70q-43-16-75-16-28 16-61 16-48 0-81-32t-33-80q0-28 16-57t41-37v-2q-21-9-21-47 0-30 23-43v-2q-63-21-63-77 0-26 11-44t30-29 40-14 46-4q125 0 125 105 0 37-27 55t-71 25q-15 3-28 12t-14 22q0 24 27 29 43 8 68 39t26 75q0 13-6 29 21 5 27 7z m33-234h77q-1 15-1 46v216q0 26 1 38h-77q2-12 2-39v-219q0-28-2-42z m284 9v68q-16-12-38-12-29 0-29 46v125h29q5 0 15 0t14-1v65h-58q0 46 1 57h-78q3-13 3-30v-27h-34v-65q20 2 21 2 1 0 6 0t7-1v-1h-2v-121q0-21 2-36t6-31 14-27 24-18 37-6q36 0 60 13z m-198 394q0 20-14 36t-33 15-34-15-14-36q0-20 14-35t34-15 33 16 14 34z m341 27v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="github-circled" unicode="" d="M429 779q116 0 215-58t156-156 57-215q0-140-82-252t-211-155q-15-3-22 4t-7 17q0 1 0 43t0 75q0 54-29 79 32 3 57 10t53 22 45 37 30 58 11 84q0 67-44 115 21 51-4 114-16 5-46-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22-47 7q-25-63-5-114-44-48-44-115 0-47 12-83t29-59 45-37 52-22 57-10q-21-20-27-58-12-5-25-8t-32-3-36 12-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7 5-8 7-6l4-3q12-6 24-21t18-29l6-13q7-21 24-34t37-17 39-3 31 1l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252q0 117 58 215t155 156 216 58z m-267-616q2 4-3 7-6 1-8-1-1-4 4-7 5-3 7 1z m18-19q4 3-1 9-6 5-9 2-4-3 1-9 5-6 9-2z m16-25q6 4 0 11-4 7-9 3-5-3 0-10t9-4z m24-23q4 4-2 10-7 7-11 2-5-5 2-11 6-6 11-1z m32-14q1 6-8 9-8 2-10-4t7-9q8-3 11 4z m35-3q0 7-10 6-9 0-9-6 0-7 10-6 9 0 9 6z m32 5q-1 7-10 5-9-1-8-8t10-4 8 7z" horiz-adv-x="857.1" /> + +<glyph glyph-name="gittip" unicode="" d="M431 123l196 264q9 13 13 33t-3 48-34 44q-23 14-47 14t-41-10-30-25q-20-22-54-22-33 0-53 22-13 16-30 25t-41 10-47-14q-26-17-34-44t-3-48 14-33z m426 227q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="google" unicode="" d="M429 411h404q7-37 7-71 0-121-51-216t-145-149-215-54q-88 0-167 34t-137 91-91 137-34 167 34 167 91 137 137 91 167 34q167 0 287-113l-117-112q-68 67-170 67-72 0-133-37t-97-98-36-136 36-136 97-98 133-37q48 0 89 14t67 33 46 46 28 49 13 43h-243v147z" horiz-adv-x="857.1" /> + +<glyph glyph-name="gplus" unicode="" d="M802 341q0-117-49-207t-138-142-206-51q-83 0-159 32t-131 87-87 131-32 159 32 159 87 131 131 87 159 32q160 0 274-107l-111-107q-65 63-163 63-69 0-127-34t-92-94-34-130 34-130 92-94 127-34q46 0 85 13t64 32 44 43 27 47 12 41h-232v141h386q7-36 7-68z m484 68v-118h-117v-116h-117v116h-117v118h117v116h117v-116h117z" horiz-adv-x="1285.7" /> + +<glyph glyph-name="gplus-squared" unicode="" d="M512 345q0 15-4 36h-202v-74h122q-2-13-10-28t-21-29-37-25-54-10q-55 0-94 40t-39 95 39 95 94 40q52 0 86-33l58 57q-60 55-144 55-89 0-151-62t-63-152 63-151 151-63q92 0 149 58t57 151z m192-26h61v62h-61v61h-61v-61h-61v-62h61v-61h61v61z m153 299v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="html5" unicode="" d="M631 517l9 98h-494l26-298h342l-12-128-110-29-110 29-7 78h-97l12-155 202-55h2v0l200 55 28 304h-359l-8 101h376z m-631 262h786l-72-803-322-90-321 90z" horiz-adv-x="785.7" /> + +<glyph glyph-name="instagramm" unicode="" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m77 0q0-91-64-156t-155-64-156 64-64 156 64 156 156 64 155-64 64-156z m61 229q0-21-15-36t-37-15-36 15-15 36 15 36 36 15 37-15 15-36z m-280 123q-4 0-43 0t-59 0-54-2-57-5-40-11q-28-11-49-32t-33-49q-6-16-10-40t-6-58-1-53 0-59 0-43 0-43 0-59 1-53 6-58 10-40q12-28 33-49t49-32q16-6 40-11t57-5 54-2 59 0 43 0 42 0 59 0 54 2 58 5 39 11q28 11 50 32t32 49q6 16 10 40t6 58 1 53 0 59 0 43 0 43 0 59-1 53-6 58-10 40q-11 28-32 49t-50 32q-16 6-39 11t-58 5-54 2-59 0-42 0z m428-352q0-128-3-177-5-116-69-180t-179-69q-50-3-177-3t-177 3q-116 6-180 69t-69 180q-3 49-3 177t3 177q5 116 69 180t180 69q49 3 177 3t177-3q116-6 179-69t69-180q3-49 3-177z" horiz-adv-x="857.1" /> + +<glyph glyph-name="linkedin-squared" unicode="" d="M132 61h129v387h-129v-387z m138 507q-1 29-21 48t-51 19-53-19-21-48q0-29 20-48t52-19h0q33 0 53 19t21 48z m326-507h129v222q0 86-41 130t-107 44q-76 0-117-65h1v56h-129q2-37 0-387h129v217q0 21 4 31 8 19 25 33t41 14q65 0 65-88v-207z m261 557v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="linux" unicode="" d="M370 621q-6-1-9-6t-4-5q-3-1-3 2 0 7 10 9h6z m49-8q-3-1-7 4t-10 2q14 6 18-1 2-3-1-5z m-196-238q-3 0-4-2t-2-7-3-8-6-7q-5-6 0-7 2 0 7 4t7 10q0 2 1 4t1 4 1 2 0 2v2t-1 1-1 2z m477-201q0 10-31 24 2 8 4 15t3 15 2 12 0 12 0 11-2 12-3 12-2 14-4 14q-5 27-26 58t-40 42q13-11 32-47 48-90 30-155-6-22-28-23-17-2-21 10t-5 47-6 60q-5 21-11 38t-11 25-9 14-7 8-4 4q-8 35-17 58t-17 31-13 19-8 22q-3 12 3 30t2 27-24 14q-9 2-25 10t-20 9q-4 1-6 15t4 28 20 15q21 2 29-16t2-33q-6-10-1-15t17 0q7 2 7 20v21q-3 17-8 28t-11 17-13 8-15 4q-60-4-50-74 0-9-1-9-5 5-16 6t-19 0-8 3q0 31-9 50t-25 19q-15 0-23-16t-10-33q0-8 2-20t8-21 8-8q6 2 9 8 2 5-4 4-4 0-8 8t-6 19q0 12 5 21t19 8q10 0 15-12t6-22-1-12q-12-9-17-16-5-7-16-13t-11-7q-7-8-9-16t4-10q8-4 14-10t9-11 11-7 19-4q27-1 57 8 1 1 13 4t19 6 17 7 12 10q5 8 11 5 2-2 3-5t-1-7-10-5q-11-3-31-12t-25-11q-25-11-40-13-14-3-44 1-5 1-5-1t10-10q14-13 37-13 10 1 20 4t20 8 19 10 17 9 13 7 10 1 5-6q0-1-1-2t-2-3-3-3-5-2-5-3-5-3-6-2q-15-8-37-25t-38-24-27 0q-12 6-35 41-12 17-14 12-1-2-1-6 0-14-8-31t-16-31-12-33 6-35q-13-3-35-50t-26-79q-1-10-1-38t-3-33q-4-14-16-2-18 17-20 53-1 15 2 31 2 10-1 10-1-1-2-3-20-36 6-92 3-7 14-16t13-11q11-13 58-51t52-42q9-9 10-22t-8-24-25-13q4-8 16-24t15-31 4-39q26 13 4 51-2 5-6 9t-5 7-1 3q2 3 7 6t11-2q26-29 93-20 74 9 99 49 13 21 19 17 6-4 5-30 0-13-13-51-5-13-3-21t14-8q1 10 8 43t7 50q1 12-3 41t-5 54 13 39q9 10 29 10 0 21 19 30t40 6 34-13z m-351 462q2 9-1 17t-6 8q-5 1-5-4 1-3 2-3 6 0 4-9-1-11 5-11 1 0 1 2z m234-110q-1 4-3 6t-8 3-8 3q-3 2-5 5t-4 4-3 4-2 2-3-1q-7-9 4-24t22-18q5 0 8 5t2 11z m-99 119q0 6-3 11t-6 7-5 1q-3 0-5-1t0-2 3-2q8-2 10-17 0-2 5 1 1 1 1 2z m30 130q0 1-1 3t-5 3-6 4q-8 8-13 8-5 0-7-4t0-7 0-7q-1-3-4-6t-3-5 2-5q2-2 4 0t6 5 9 5q0 1 5 1t8 1 5 4z m315-749q11-6 18-13t6-14-1-12-9-13-13-10-17-11-17-9-18-9-15-7q-21-11-48-31t-42-36q-9-9-38-11t-50 8q-10 5-16 13t-9 15-13 11-26 5q-24 0-72 0-11 0-32 0t-32-2q-25 0-45-8t-30-17-24-16-30-6q-16 0-62 17t-81 24q-11 2-29 5t-28 5-22 6-18 8-10 11q-5 12 4 37t10 30q1 9-2 23t-6 23-2 21 6 15q7 6 31 8t34 6q17 10 23 20t7 28q12-41-18-59-18-11-46-8-19 1-24-6-7-8 3-32 1-3 4-10t5-10 2-9 1-13q0-8-9-27t-8-27q1-9 20-14 12-4 47-11t56-11q13-3 41-12t46-13 31-2q24 3 36 15t13 27-4 33-11 29-11 20q-67 106-94 135-38 42-63 23-6-5-9 8-1 9-1 21 1 16 6 29t13 26 13 24q4 12 14 40t17 43 17 35 21 30q62 79 70 108-7 63-9 173-1 51 13 85t59 58q22 12 58 12 30 1 59-7t50-24q32-23 51-67t17-83q-3-53 16-119 19-63 75-122 30-33 55-91t33-106q5-28 3-48t-7-30-11-13q-5-1-13-10t-15-20-23-19-34-8q-10 1-17 3t-13 8-7 8-7 12-5 11q-12 20-23 16t-15-27 4-54q11-39 0-109-5-36 10-56t41-19 47 20q33 28 50 37t58 24q30 10 43 20t10 20-14 16-28 13q-19 6-28 27t-8 40 8 27q1-18 5-32t8-23 11-15 12-11 12-7 9-6z" horiz-adv-x="857.1" /> + +<glyph glyph-name="linkedin" unicode="" d="M195 501v-553h-184v553h184z m12 171q0-41-29-68t-75-27h-1q-46 0-74 27t-28 68q0 41 29 68t75 27 74-27 29-68z m650-407v-317h-183v296q0 59-23 92t-71 33q-35 0-58-19t-36-48q-6-17-6-45v-309h-184q1 223 1 361t0 165l-1 27h184v-80h-1q11 18 23 31t31 29 49 24 64 9q95 0 153-63t58-186z" horiz-adv-x="857.1" /> + +<glyph glyph-name="pagelines" unicode="" d="M782 235q-17-45-42-77t-51-50-55-26-57-8-54 5-48 12-39 15-25 13l-10 6q-63-128-161-201t-215-74q-10 0-18 7t-7 18 7 18 18 7q97 0 180 60t140 164q-20-8-40-13t-46-7-51 1-52 16-51 33-47 56-42 81q64 27 120 32t93-4 70-31 49-43 32-46q29 73 44 162-4 0-10-1t-26-2-39 1-46 5-49 13-47 24-42 36-30 53-16 71q39 16 74 20t63 0 52-17 41-28 31-34 23-35 16-31 9-22l2-9q6 68 6 109-4 3-12 8t-27 25-35 40-30 52-19 63 7 71 39 77q41-14 71-34t47-43 27-47 11-50 0-48-7-42-11-35-9-23l-4-9q0-2 0-28t0-40q2 4 5 10t17 24 29 33 39 31 51 25 63 8 74-14q-1-43-12-79t-28-58-39-40-45-25-48-14-44-5-38 1-26 2l-9 2q-13-82-41-158 3 4 10 10t28 23 43 29 55 24 66 11 72-13 76-43z" horiz-adv-x="785.7" /> + +<glyph glyph-name="pied-piper-squared" unicode="" d="M584 281q0-36-21-61t-51-25q-24 0-39 8v155q15 9 39 9 29 0 51-25t21-61z m-192 239q0-36-21-61t-51-26q-24 0-39 9v154q16 10 39 10 30 0 51-25t21-61z m314-241q0 75-49 128t-119 53q-11 0-22-2-13-43-43-76-49-53-118-56v-355l118 23v115q28-11 65-11 70 0 119 53t49 128z m-191 238q0 75-50 128t-119 53q-41 0-79-20h-103v-469l117 23v115q31-10 65-10 70 0 119 53t50 127z m342 101v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="pinterest-circled" unicode="" d="M857 350q0-117-57-215t-156-156-215-58q-62 0-122 18 33 52 43 92 6 19 31 118 11-22 40-38t64-16q68 0 121 38t82 105 29 151q0 64-34 120t-96 90-142 36q-59 0-110-17t-86-43-61-61-37-72-12-75q0-58 22-102t66-62q16-7 21 11 1 4 4 17t5 17q3 13-6 24-29 34-29 84 0 84 58 145t153 61q84 0 132-46t47-119q0-95-39-161t-98-67q-34 0-54 25t-13 58q4 19 15 52t17 57 6 43q0 28-15 46t-43 18q-35 0-59-31t-24-80q0-40 14-68l-55-233q-9-39-7-99-115 51-186 157t-71 236q0 117 58 215t155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> + +<glyph glyph-name="pinterest-squared" unicode="" d="M696 779q67 0 114-48t47-113v-536q0-66-47-113t-114-48h-404q47 68 60 118 5 19 30 116 11-21 41-37t63-16q101 0 164 83t64 208q0 47-19 91t-54 77-85 54-110 21q-58 0-109-16t-85-43-60-61-37-71-12-74q0-57 22-101t65-61q7-3 13 0t8 10q6 25 9 35 3 12-7 23-28 35-28 84 0 83 58 143t151 59q83 0 130-45t47-117q0-94-38-160t-97-66q-33 0-54 25t-13 57q5 19 15 52t16 57 6 42q0 27-14 45t-42 18q-34 0-58-31t-24-78q0-40 13-68l-54-231q-14-56-4-142h-102q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535z" horiz-adv-x="857.1" /> + +<glyph glyph-name="renren" unicode="" d="M632-26q-95-53-205-53-109 0-205 53 77 48 132 118t73 149q19-80 74-149t131-118z m-276 797v-271q0-140-71-256t-184-171q-101 120-101 276 0 104 47 195t128 150 181 77z m501-422q0-156-101-276-114 55-184 171t-71 256v271q100-17 182-77t128-150 46-195z" horiz-adv-x="857.1" /> + +<glyph glyph-name="skype" unicode="" d="M655 257q0 28-11 51t-27 38-41 27-46 19-49 13l-58 14q-17 4-25 6t-19 6-17 9-9 12-4 16q0 43 80 43 24 0 43-6t30-16 21-19 23-16 27-7q26 0 42 18t16 43q0 31-32 55t-79 38-101 13q-38 0-74-9t-67-26-49-48-19-72q0-34 10-60t32-42 44-27 58-18l81-20q51-12 63-20 18-11 18-34 0-21-23-36t-58-14q-29 0-51 9t-37 22-25 25-26 21-30 9q-28 0-42-17t-14-41q0-52 68-88t162-37q41 0 78 10t69 30 49 52 19 74z m202-121q0-89-63-152t-151-63q-73 0-131 45-43-9-83-9-80 0-153 31t-126 84-83 125-31 153q0 41 9 84-45 58-45 130 0 89 63 152t151 63q73 0 131-45 43 9 84 9 79 0 152-31t126-84 84-125 30-153q0-41-8-84 44-58 44-130z" horiz-adv-x="857.1" /> + +<glyph glyph-name="slideshare" unicode="" d="M487 437q0-46-35-79t-85-34-85 34-36 79q0 47 36 80t85 33 85-33 35-80z m280 0q0-46-35-79t-85-34q-50 0-85 34t-36 79q0 47 36 80t85 33q50 0 85-33t35-80z m126-100v372q0 48-18 69t-62 20h-620q-47 0-63-19t-17-70v-376q24-13 50-22t45-16 45-10 40-6 39-2 32-1 32 2 25 1q38 0 53-15 3-4 5-5 15-14 34-29 4 51 66 49 3 0 21-1t24-1 25-1 30 1 30 2 34 5 35 7 37 11 38 15 40 20z m91 2q-68-83-208-140 47-159-13-260-36-63-102-82-58-18-101 8-48 28-46 92l-1 182v0q-4 1-13 3t-13 3l-1-188q2-64-46-92-44-26-102-8-66 20-102 83-58 101-12 259-140 57-208 140-14 21-2 36t33-1q3-1 7-4t6-4v387q0 40 26 69t64 28h701q37 0 64-28t26-69v-387l12 8q21 15 33 1t-2-36z" horiz-adv-x="1000" /> + +<glyph glyph-name="stackexchange" unicode="" d="M703 151v-37q0-47-32-81t-78-33h-32l-145-150v150h-295q-45 0-77 33t-32 81v37h691z m0 182v-143h-691v143h691z m0 183v-143h-691v143h691z m0 78v-37h-691v37q0 47 32 80t77 33h472q45 0 78-33t32-80z" horiz-adv-x="714.3" /> + +<glyph glyph-name="stackoverflow" unicode="" d="M719-61h-624v268h-89v-357h803v357h-90v-268z m-525 293l18 87 437-92-18-87z m57 208l38 82 404-189-37-81z m112 199l57 69 343-287-57-68z m222 211l266-358-71-53-267 357z m-401-821v89h447v-89h-447z" horiz-adv-x="857.1" /> + +<glyph glyph-name="trello" unicode="" d="M393 100v571q0 8-5 13t-13 5h-268q-8 0-13-5t-5-13v-571q0-8 5-13t13-5h268q8 0 13 5t5 13z m375 214v357q0 8-5 13t-13 5h-268q-8 0-13-5t-5-13v-357q0-7 5-12t13-6h268q8 0 13 6t5 12z m89 429v-786q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v786q0 14 11 25t25 11h785q15 0 26-11t10-25z" horiz-adv-x="857.1" /> + +<glyph glyph-name="tumblr" unicode="" d="M527 108l44-132q-12-19-61-37t-99-18q-58-1-107 15t-79 41-53 59-31 67-9 66v304h-94v120q40 14 72 39t51 50 32 57 19 55 8 49q1 3 3 5t4 2h136v-237h186v-140h-186v-289q0-17 3-31t13-30 28-23 45-8q44 1 75 16z" horiz-adv-x="571.4" /> + +<glyph glyph-name="tumblr-squared" unicode="" d="M634 35l-35 102q-24-12-57-12-20-1-35 5t-21 18-10 23-3 24v222h144v108h-143v182h-105q-5 0-5-5-3-25-10-49t-22-53-43-53-66-38v-92h73v-233q0-32 12-64t36-62 68-48 98-17q39 0 76 14t48 28z m223 583v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="twitter-squared" unicode="" d="M714 510q-31-14-67-19 38 22 52 65-37-21-75-28-34 36-85 36-49 0-83-34t-35-83q0-16 3-27-72 4-135 37t-107 86q-16-28-16-59 0-64 51-98-27 1-56 15v-1q0-42 28-75t68-40q-16-5-28-5-7 0-22 3 12-36 42-59t67-23q-64-50-145-50-15 0-28 2 82-53 180-53 62 0 117 20t94 53 67 76 42 91 13 94q0 10 0 15 35 25 58 61z m143 108v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="twitter" unicode="" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" /> + +<glyph glyph-name="vimeo-squared" unicode="" d="M721 494q6 121-90 124-129 4-174-146 25 11 46 11 47 0 41-54-2-32-41-93t-59-61q-24 0-46 94-7 30-25 142-16 106-89 99-33-4-91-56l-46-40-45-40 29-37q43 29 49 29 32 0 59-100 9-31 26-92t25-92q38-100 91-100 88 0 214 164 123 158 126 248z m136 124v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="windows" unicode="" d="M381 289v-364l-381 53v311h381z m0 414v-367h-381v315z m548-414v-439l-507 70v369h507z m0 490v-443h-507v373z" horiz-adv-x="928.6" /> + +<glyph glyph-name="wordpress" unicode="" d="M71 350q0 91 37 175l205-561q-109 53-176 157t-66 229z m719 22q0-11-2-22t-5-27-7-25-9-33-10-32l-43-143-155 461q26 2 49 4 11 2 15 11t-2 17-15 8l-115-6q-42 1-113 6-6 0-11-3t-6-9-1-10 5-9 11-5l44-4 67-183-94-281-156 464q26 2 49 4 11 2 15 11t-2 17-15 8l-115-6q-4 0-13 0t-14 1q58 89 153 141t205 52q82 0 157-29t133-84h-6q-31 0-51-22t-21-53q0-7 1-14t2-12 5-13 5-11 7-13 7-12 8-13 8-13q35-60 35-118z m-283-59l133-361q0-4 2-7-70-24-142-24-62 0-121 18z m369 243q53-97 53-206 0-117-58-215t-156-156l132 379q33 94 33 154 0 23-4 44z m-376 294q102 0 194-40t160-106 106-160 40-194-40-194-106-160-160-106-194-40-194 40-160 106-106 160-40 194 40 194 106 160 160 106 194 40z m0-977q97 0 185 38t152 102 102 152 38 185-38 185-102 152-152 102-185 38-185-38-152-102-102-152-38-185 38-185 102-152 152-102 185-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="youtube" unicode="" d="M542 156v-118q0-37-22-37-13 0-25 12v168q12 12 25 12 22 0 22-37z m189-1v-25h-51v25q0 38 25 38t26-38z m-540 122h60v52h-174v-52h59v-318h55v318z m161-318h50v276h-50v-211q-17-23-32-23-10 0-11 11-1 2-1 20v203h-50v-218q0-28 5-41 7-21 32-21 27 0 57 34v-30z m240 83v110q0 41-5 55-10 31-40 31-28 0-52-30v121h-50v-370h50v27q25-31 52-31 30 0 40 31 5 15 5 56z m188 6v7h-51q0-29-1-34-4-20-22-20-26 0-26 38v49h100v57q0 44-15 65-22 28-59 28-38 0-60-28-15-21-15-65v-96q0-44 16-65 22-29 60-29 40 0 60 30 10 15 12 30 1 5 1 33z m-339 509v117q0 39-24 39t-24-39v-117q0-39 24-39t24 39z m401-419q0-131-14-195-8-33-33-56t-57-25q-102-12-309-12t-310 12q-32 3-57 25t-32 56q-15 62-15 195 0 131 15 195 7 33 32 56t57 26q103 11 310 11t309-11q33-4 58-26t32-56q14-62 14-195z m-557 712h57l-67-223v-151h-56v151q-8 42-34 119-21 57-37 104h60l39-147z m207-186v-97q0-46-16-66-21-29-59-29-37 0-59 29-15 21-15 66v97q0 45 15 66 22 28 59 28 38 0 59-28 16-21 16-66z m187 91v-279h-51v31q-30-35-58-35-25 0-33 21-4 13-4 42v220h51v-205q0-19 0-20 2-12 12-12 15 0 32 24v213h51z" horiz-adv-x="857.1" /> + +<glyph glyph-name="youtube-squared" unicode="" d="M513 123v88q0 27-16 27-10 0-19-8v-125q9-9 19-9 16 0 16 27z m103 68h36v19q0 28-18 28t-18-28v-19z m-319 148v-39h-45v-236h-41v236h-44v39h130z m112-70v-205h-37v23q-22-25-43-25-18 0-23 15-3 10-3 30v162h36v-151q0-13 1-14 0-8 8-8 11 0 24 17v156h37z m141-62v-81q0-29-4-41-7-23-30-23-19 0-38 22v-20h-37v275h37v-89q18 22 38 22 23 0 30-24 4-11 4-41z m140-72v-5q0-16-1-24-2-12-9-22-15-22-44-22-29 0-46 21-11 15-11 48v72q0 33 11 48 16 21 45 21t43-21q12-16 12-48v-43h-74v-36q0-28 19-28 13 0 16 14 0 1 1 4t0 9v12h38z m-252 460v-87q0-28-18-28t-18 28v87q0 29 18 29t18-29z m298-398q0 99-11 145-6 25-24 41t-42 20q-76 8-230 8-154 0-230-8-24-3-42-20t-24-41q-11-48-11-145 0-98 11-145 5-24 24-41t42-19q76-9 230-9t230 9q24 2 42 19t23 41q12 47 12 145z m-422 363l50 166h-41l-29-109-29 109h-44q4-13 13-39l13-38q20-58 26-89v-112h41v112z m161-45v73q0 32-11 48-16 22-44 22-28 0-43-22-12-16-12-48v-73q0-32 12-48 15-22 43-22 28 0 44 22 11 15 11 48z m102-67h37v207h-37v-158q-13-17-24-17-8 0-9 8 0 2 0 15v152h-38v-164q0-20 4-30 6-16 24-16 20 0 43 26v-23z m280 170v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="youtube-play" unicode="" d="M397 221l270 139-270 141v-280z m103 481q94 0 181-3t128-5l41-2q0 0 9-1t13-2 13-2 16-5 16-7 17-11 16-15q4-3 9-10t16-33 15-56q4-36 7-76t3-64v-98q1-81-10-162-4-30-14-55t-18-35l-8-9q-7-8-16-15t-17-10-16-7-16-5-13-2-13-2-9-1q-140-11-350-11-115 2-201 4t-111 4l-28 3-20 2q-20 3-30 5t-29 12-31 23q-4 3-9 10t-16 33-15 56q-4 36-7 76t-3 64v98q-1 81 10 162 4 31 14 55t18 35l8 9q8 9 16 15t17 11 16 7 16 5 13 2 13 2 9 1q140 10 350 10z" horiz-adv-x="1000" /> + +<glyph glyph-name="blank" unicode="" d="M857 618v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> + +<glyph glyph-name="lemon" unicode="" d="M785 389q0 25-4 63t-10 54q-6 17-9 25t-5 20-2 27q0 13 2 39t3 37q0 21-5 31-3 0-8 0-10 0-32-2t-33-3q-34 0-98 14t-98 13q-24 0-53-6t-47-13-50-19q-76-30-113-58-53-41-89-106t-49-131-14-139q0-22 7-67t7-67q0-13-6-38t-6-36 7-20 19-9q13 0 40 7t41 6q32 0 95-9t94-9q101 0 159 21 72 25 131 85t93 137 33 153z m72 1q0-92-39-183t-110-160-157-101q-69-25-182-25-31 0-94 8t-95 9q-13 0-40-9t-41-8q-41 0-69 31t-28 72q0 14 6 38t6 37q0 23-7 68t-7 68q0 61 10 121t31 117 56 108 83 87q44 33 130 67 108 44 176 44 34 0 98-14t97-13q10 0 32 3t32 2q45 0 66-28t21-75q0-13-3-38t-3-38q0-7 1-14t2-9 4-11 5-11q9-23 14-67t5-76z" horiz-adv-x="857.1" /> + +<glyph glyph-name="note" unicode="" d="M381 820q0-42 46-97t90-100 61-119-39-154q-20-34-26-16-2 6 0 16 10 18 6 56t-13 78-43 73-82 41l0-536q2-48-37-95t-105-71q-74-28-143-8t-89 76 20 115 116 85q86 30 158 4l0 652 80 0z" horiz-adv-x="582" /> + +<glyph glyph-name="note-beamed" unicode="" d="M206 714l534 116 0-704q2-42-30-81t-86-59q-64-24-110-8t-62 64q-18 48 7 97t85 73q52 20 106 10l0 376-354-82 0-490q0-42-32-81t-86-59q-64-22-109-7t-61 63q-18 48 6 97t84 73q54 20 108 10l0 592z" horiz-adv-x="740" /> + +<glyph glyph-name="alert" unicode="" d="M885 234q20-16 16-33t-28-23l-78-22q-24-6-40-28t-14-48l4-82q2-24-14-34t-38 0l-86 44q-22 12-47 4t-35-30l-46-88q-12-22-29-23t-33 19l-50 78q-34 48-88 20l-122-70q-22-14-32-6t-2 32l54 164q8 24-4 44t-36 22l-106 12q-24 4-29 18t15 30l86 76q20 16 20 41t-20 41l-86 76q-20 16-16 33t28 23l78 22q24 6 41 28t15 48l-6 82q0 26 15 36t37 0l80-38q24-10 49-2t37 30l46 80q12 22 30 21t30-23l50-86q12-22 35-29t45 7l136 84q22 14 30 6t0-32l-60-170q-10-22 2-41t38-21l114-12q26-2 30-16t-16-30l-86-76q-18-16-18-41t18-41z m-384-92l0 104-100 0 0-104 100 0z m0 160l0 260-100 0 0-260 100 0z" horiz-adv-x="901" /> + +<glyph glyph-name="graduation-cap" unicode="" d="M166 238l334-168 276 136q-4-22-8-47t-6-35-11-23-24-23-45-22q-40-18-80-41t-63-34-39-11-40 13-64 37-80 40q-72 32-103 69t-47 109z m810 246q24-14 24-33t-24-33l-78-44-308 102q-22 36-90 36-40 0-67-16t-27-40 27-40 67-16q26 0 36 4l292-68-268-152q-60-32-120 0l-416 234q-24 14-24 33t24 33l416 234q60 32 120 0z m-128-442q18 116 13 182t-19 90l-14 22 70 38q6-8 12-28t17-101-7-197q-4-26-22-30t-35 5-15 19z" horiz-adv-x="1000" /> + +<glyph glyph-name="water" unicode="" d="M168 844q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 58 33 112t73 123 50 155q2 4 7 4t5-4z m616 0q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 48 21 93t48 78 53 92 34 127q2 4 7 4t5-4z m-320-444q2 4 7 4t5-4q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 58 33 112t73 123 50 155z" horiz-adv-x="940" /> + +<glyph glyph-name="droplet" unicode="" d="M290 822q14-118 60-219t92-159 82-136 36-160q0-114-83-196t-197-82-197 82-83 196q0 82 36 160t82 136 92 159 60 219q2 8 11 8t9-8z m-42-392q2 4-2 14-6 6-14 6t-12-6l-40-58q-32-46-48-70t-34-75-18-101q0-24 17-41t41-17q58 0 58 68 0 94 42 246 2 6 5 17t5 17z" horiz-adv-x="560" /> + +<glyph glyph-name="key" unicode="" d="M802 714c-47 47-106 72-177 74-70-2-130-27-177-74s-70-105-72-176c0-19 2-38 6-56l-382-382v-62l63-63h125l62 63v62h63v63h62v62h125l68 69c19-4 38-6 57-6 71 2 130 26 177 73s71 105 73 177c-2 71-26 129-73 176z m-114-200c-48 0-86 38-86 86s38 86 86 86 85-38 85-86-38-86-85-86z" horiz-adv-x="875" /> + +<glyph glyph-name="infinity" unicode="" d="M0 350q0 93 66 159t159 66 159-66l72-69 73 70q65 65 157 65 92 0 158-66t67-159-67-159-158-66q-92 0-158 66l-72 69-73-70q-65-65-158-65t-159 66-66 159z m225 95q-39 0-67-28t-28-67q0-40 28-67t67-28 68 28l68 67-68 66q-29 29-68 29z m529-162q27 27 27 67t-27 68-67 27-67-27l-70-68 69-66q27-28 67-28t68 27z" horiz-adv-x="911" /> + +<glyph glyph-name="at" unicode="" d="M770 548c16-41 25-87 25-138 0-68-17-133-46-182-39-61-94-92-163-92-27 0-52 7-70 21-12 9-23 21-30 36-26-33-68-55-113-55-52 0-95 19-123 58-27 35-41 84-41 141s16 110 49 148c33 41 82 63 141 63 35 0 67-15 90-37l7 26 85 0-17-259c-1-21 1-36 9-43 3-5 10-8 20-8 76 0 102 123 102 183 0 72-24 132-67 176-50 51-122 76-209 76-84 0-158-26-214-76-65-59-101-143-101-245 0-86 31-165 85-221 52-53 122-82 205-82 95 0 167 14 241 47l15 7 0-96-6-3c-69-34-150-50-251-50-108 0-200 33-269 97-81 73-124 183-124 308 0 119 40 220 116 294 38 36 83 65 133 84 52 21 109 31 169 31 113 0 215-37 283-106 30-29 54-64 69-103z m-384-317c30 0 54 14 69 40 14 25 18 61 18 85 0 31-7 56-17 74-13 20-33 28-60 28-28 0-50-12-63-38-13-22-19-53-19-83 0-32 7-106 72-106z" horiz-adv-x="795" /> + +<glyph glyph-name="money-bag" unicode="" d="M238 378c13 16 35 21 65 24l0-85c-50 5-75 9-75 39 0 7 2 15 10 22z m107-241l0 91c59-5 78-9 78-35 0-30-24-49-78-56z m70 244c30-17 36-24 36-38l8-4 45 90-6 5c-7-5-10-7-14-7s-8 0-13 4c-61 23-90 35-126 35l0 17c0 9 5 15 20 19l0 9-79 0 0-9c14-4 17-11 17-19l0-15c-97-4-153-50-153-122 0-74 40-100 153-110l0-97c-78 8-117 38-117 64l-7 4-41-94 6-4c6 4 9 5 12 5 2 0 7 0 9-2 48-24 94-38 138-40l0-19c0-10-3-16-17-20l0-9 79 0 0 9c-15 4-20 9-20 20l0 19c96 5 159 54 159 126 0 70-53 106-149 115l-10 0 0 88c23-2 46-8 70-20z m41 181c117-50 198-165 198-300 0-181-146-328-326-328-182 0-328 147-328 328 0 135 81 250 198 300l-82 179c0 17 12 25 28 25l366 0c16 0 28-7 28-25z" horiz-adv-x="654" /> + +<glyph glyph-name="hash" unicode="" d="M2 207q4 25 25 42t46 16h149l36 170h-135q-28 0-46 21-17 22-12 51 5 25 26 42t46 16h149l31 150q6 26 26 42t46 17q30 0 47-21t11-51l-29-137h167l32 150q5 26 26 42t46 17q29 0 46-21 18-21 11-51l-29-137h136q28 0 46-21 17-22 11-51-5-25-25-42t-46-16h-149l-37-170h136q28 0 47-21 17-22 11-51-6-26-26-42t-46-17h-149l-32-150q-5-25-25-42t-46-16q-29 0-47 21-17 22-11 50l29 137h-167l-33-150q-5-25-25-42t-46-16q-28 0-46 21-17 22-12 50l30 137h-136q-29 0-46 22-18 21-11 51z m352 58h168l36 170h-168z" horiz-adv-x="912.1" /> + +<glyph glyph-name="airport" unicode="" d="M850 275l0-50-350 50 0-200 100-100c0-25-25-50-50-50-100 0-250 0-250 0-25 0-50 25-50 50l100 100 0 200-350-50 0 50 350 200s0 17 0 100c0 50 25 200 75 200s75-150 75-200c0-83 0-100 0-100z" horiz-adv-x="850" /> + +<glyph glyph-name="fast-food" unicode="" d="M350 675c-150 0-300-75-300-200l0-100 850 0 0 100c0 125-150 200-300 200z m25-50c14 0 25-11 25-25 0-14-11-25-25-25-14 0-25 11-25 25 0 14 11 25 25 25z m200 0c14 0 25-11 25-25 0-14-11-25-25-25-14 0-25 11-25 25 0 14 11 25 25 25z m-300-100c14 0 25-11 25-25 0-14-11-25-25-25-14 0-25 11-25 25 0 14 11 25 25 25z m200 0c14 0 25-11 25-25 0-14-11-25-25-25-14 0-25 11-25 25 0 14 11 25 25 25z m200 0c14 0 25-11 25-25 0-14-11-25-25-25-14 0-25 11-25 25 0 14 11 25 25 25z m-625-200c-25 0-50-25-50-50 0-25 25-50 50-50l850 0c25 0 50 25 50 50 0 25-25 50-50 50z m0-150l0-50c0-50 50-100 100-100l650 0c50 0 100 50 100 100l0 50z" horiz-adv-x="950" /> + +<glyph glyph-name="fuel" unicode="" d="M50 800c-28 0-50-22-50-50l0-800c0-25 25-50 50-50l450 0c25 0 50 25 50 50l0 250 25 0c25 0 25-25 25-25l0-100c0-50 25-75 75-75s75 25 75 75c0 58 0 225 0 275 0 50-100 100-100 150l0 150-50 0-50 50 0 50c0 28-22 50-50 50z m50-100l350 0 0-200-350 0z m450-150l50 0s0-42 0-75c0-50 100-100 100-150l0-250c0-25-25-25-25-25s-25 0-25 25c0 0 0 100 0 125 0 25-25 50-50 50-17 0-50 0-50 0z" horiz-adv-x="750" /> + +<glyph glyph-name="police" unicode="" d="M200 800c-25 0-50-25-50-50l0-50 350 0 0 50c0 25-25 50-50 50l-250 0z m-50-150l0-25c0-97 78-175 175-175s175 78 175 175l0 25-350 0z m-50-250c-75 0-100-125-100-200l0-300 134 0 341 500-375 0z m450 0l-341-500 441 0 0 300c0 75-25 200-100 200z" horiz-adv-x="650" /> + +<glyph glyph-name="religious-christian" unicode="" d="M325 800c-42 0-75-33-75-75l0-175-175 0c-42 0-75-33-75-75 0-41 33-75 75-75l175 0 0-425c0-42 33-75 75-75 42 0 75 33 75 75l0 425 175 0c42 0 75 34 75 75 0 42-33 75-75 75l-175 0 0 175c0 42-33 75-75 75z" horiz-adv-x="650" /> + +<glyph glyph-name="religious-islam" unicode="" d="M425 775c-235 0-425-190-425-425 0-235 190-425 425-425 131 0 249 60 327 153-51-33-112-53-177-53-179 0-325 146-325 325s146 325 325 325c65 0 126-20 177-53-78 93-196 153-327 153z m250-200l-50-150-175 0 150-100-75-175 150 100 150-100-75 175 150 100-175 0-50 150z" horiz-adv-x="900" /> + +<glyph glyph-name="religious-jewish" unicode="" d="M400 800l-150-200-250 0 150-250-150-250 250 0 150-200 150 200 250 0-150 250 150 250-250 0z" horiz-adv-x="800" /> + +<glyph glyph-name="school" unicode="" d="M638 800c-49 0-88-39-88-87s39-88 88-88 87 39 87 88-39 87-87 87z m-400-100c-49 0-88-39-88-87s39-88 88-88 87 39 87 88-39 87-87 87z m462-100c-19 0-49-7-75-25l-162-116c-45-31-4-86 37-59l106 70s70-150 44-220c-1-2-2-4-2-6l-109-291c-12-33 12-53 36-53 16 0 31 10 39 28l136 322s25-150 125-150c60 0 104 0 138 0 37 0 37 38 37 38s0 37-37 37c-25 0-88 0-113 0-53 0-50 80-50 125 0 75-50 200-50 200l150-100c45-30 85 23 41 56l-166 119c-30 22-40 25-75 25 0 0-25 0-50 0z m-525-100c-25 0-37-12-50-25l-106-106c-14-14-19-26-19-44 0-25 38-37 59-16l91 91c50 0 69-125 50-175l-75-200c-21-55 57-72 75-25l75 200s25-50 75-50l150 0c50 0 50 75 0 75l-125 0c-25 0-25 50-25 75 0 100-50 200-100 200z" horiz-adv-x="1050" /> + +<glyph glyph-name="swimming" unicode="" d="M556 675c-8 0-21-5-28-8l-200-97c-26-12-36-51-19-75l61-82-223-174 103-33 134 44c5 2 11 3 16 3s10-1 16-3l134-44 70 24-192 278 152 76c30 15 28 41 25 57-3 14-21 34-49 34z m94-150c-55 0-100-45-100-100 0-55 45-100 100-100 55 0 100 45 100 100 0 55-45 100-100 100z m-550-350c-5 0-12-2-16-3l-84-28 0-103 100 34 134-47c6-2 11-3 16-3 5 0 11 2 16 3l134 47 134-47c6-2 11-3 16-3 5 0 11 2 16 3l134 47 150-50 0 103-134 44c-5 2-11 3-16 3s-10-1-16-3l-134-44-134 44c-6 2-11 3-16 3s-11-2-16-3l-134-44-134 44c-7 2-11 3-16 3z" horiz-adv-x="850" /> + +<glyph glyph-name="toilet" unicode="" d="M475 850c-14 0-25-11-25-25l0-950c0-14 11-25 25-25 14 0 25 11 25 25l0 950c0 14-11 25-25 25z m-275-50c-55 0-100-45-100-100s45-100 100-100c55 0 100 45 100 100s-45 100-100 100z m525 0c-55 0-100-45-100-100s45-100 100-100c55 0 100 45 100 100s-45 100-100 100z m-600-250l-124-375c-5-13 11-25 25-25l74 0 0-225c0-14 11-25 25-25 14 0 25 11 25 25l0 225 100 0 0-225c0-14 11-25 25-25 14 0 25 11 25 25l0 225 75 0c14 0 30 12 25 25l-125 375z m525 0c-28 0-50-22-50-50l0-575c0-14 11-25 25-25 14 0 25 11 25 25l0 275 150 0 0-275c0-14 11-25 25-25 14 0 25 11 25 25l0 575c0 28-22 50-50 50z" horiz-adv-x="850" /> + +<glyph glyph-name="universal-access" unicode="" d="M0 350q0 207 147 354t353 146 354-146 146-354-146-354-354-146-353 146-147 354z m66 0q0-180 127-307t307-127 307 127 127 307-127 307-307 127-307-127-127-307z m163 2q-20 27 2 47l167 146q12 8 22 11t25 3l110 0q15 0 25-3t22-11q9-7 71-62t97-84q21-22 1-47-21-23-46-4-20 18-63 54t-64 57q0-2-3 0t-7 4-6 2q-6 0-6-13l0-58t4-67q2-22 2-26l49-271q4-41-33-49-18-4-33 7t-16 26q-39 221-39 225-6 23-10 23-4-2-6-5t-2-7-1-7l-1-4q0-2-10-59t-19-111l-10-55q-6-18-18-26t-31-7q-39 10-33 49l49 273q6 37 6 147 0 15-6 15t-16-8l-127-109q-27-19-46 4z m199 300q0 30 21 52t52 21 52-21 21-52-21-52-52-21-52 21-21 52z" horiz-adv-x="1000" /> + +<glyph glyph-name="travel" unicode="" d="M271 350c-58 0-104-47-104-104s46-104 104-104 104 46 104 104-47 104-104 104m0-125c-12 0-21 9-21 21s9 21 21 21 21-10 21-21-10-21-21-21m458 125c-57 0-104-47-104-104s47-104 104-104 104 46 104 104-46 104-104 104m0-125c-11 0-21 9-21 21s10 21 21 21 21-10 21-21-9-21-21-21m208 229l-43 15-2 6h25c23 0 41 18 41 41 0 1 0 1 0 1h0c0 41-37 83-82 83h-27l-30 72c-16 53-65 95-120 95h-397c-56 0-105-42-120-95l-31-72h-27c-45 0-82-42-82-83h0c0 0 0 0 0-1 0-23 18-41 41-41h25l-1-6-44-15c-37-12-60-48-56-87l31-313c2-21 20-37 41-37h46v-42c0-46 37-83 83-83h84c46 0 83 37 83 83v42h250v-42c0-46 37-83 83-83h84c46 0 83 37 83 83v42h46c21 0 39 16 41 37l31 313c4 39-19 75-56 87m-676 194c6 17 22 35 41 35h397c18 0 34-18 40-35l66-173h-610l66 173z m31-673h-84v42h84v-42z m500 0h-84v42h84v-42z m91 125h-8-250-250-250-8l-27 275 35 17h750l36-17-28-275z" horiz-adv-x="1000" /> + +<glyph glyph-name="symbols" unicode="" d="M0 850l458 0 0-83-458 0z m167-458l125 0 0 208 166 0 0 83-458 0 0-83 167 0z m479-250c57 0 104 46 104 104s-47 103-104 103-104-46-104-103 46-104 104-104m0 124c11 0 21-9 21-20 0-12-10-21-21-21s-21 9-21 21c0 11 9 20 21 20m250-208c-58 0-104-47-104-104s46-104 104-104 104 46 104 104-47 104-104 104m0-125c-12 0-21 9-21 21 0 11 9 20 21 20s21-9 21-20c0-12-10-21-21-21m21 375l-375-375 63-62 374 375z m-209 84c92 0 167 46 167 104v271c0 0 41 6 62-40 22-46 21-127 21-127s42 47 42 131c0 120-125 119-125 119h-83v-264c-25 9-53 14-84 14-92 0-166-47-166-104s74-104 166-104m-279-395l-61 66c-33-26-53-42-60-47-13 12-42 42-86 91 38 34 62 61 72 78s16 37 16 56c0 25-11 49-34 73-23 25-55 37-98 37-42 0-74-13-98-37-23-24-34-49-34-74 0-34 17-73 52-117-35-26-60-50-75-74-14-24-21-50-21-77 0-36 12-65 38-88 26-23 59-34 101-34 49 0 102 16 159 47l43-47h118l-87 99 55 48z m-281 266c9 8 19 12 31 12 13 0 23-4 31-11 8-7 12-16 12-27 0-22-18-47-53-75-22 28-33 52-33 73 0 11 4 20 12 28m8-330c-18 0-33 5-44 15-12 9-18 20-18 32 0 25 22 54 64 87 40-48 72-82 94-102-39-21-71-32-96-32" horiz-adv-x="1000" /> + +<glyph glyph-name="recent" unicode="" d="M542 683l-84 0 0-291-83 0 0-84 83 0 0-83 84 0 0 83 166 0 0 84-166 0z m-42 167c-276 0-500-224-500-500s224-500 500-500 500 224 500 500-224 500-500 500m0-917c-230 0-417 187-417 417s187 417 417 417 417-187 417-417-187-417-417-417" horiz-adv-x="1000" /> + +<glyph glyph-name="people" unicode="" d="M500 850c-276 0-500-224-500-500s224-500 500-500 500 224 500 500-224 500-500 500m0-917c-230 0-417 187-417 417s187 417 417 417 417-187 417-417-187-417-417-417m-167 625c-46 0-83-37-83-83s37-83 83-83 84 37 84 83-38 83-84 83m334 0c-46 0-84-37-84-83s38-83 84-83 83 37 83 83-37 83-83 83m-32-333c-29-50-78-83-140-83-61 0-110 33-139 83h269m125 83h-500c0-138 112-250 250-250s250 112 250 250" horiz-adv-x="1000" /> + +<glyph glyph-name="objects" unicode="" d="M500 850c-207 0-375-168-375-375 0-130 66-244 167-312l0-188c0 0 85-125 208-125 124 0 208 125 208 125l0 188c101 68 167 182 167 312 0 207-168 375-375 375z m0-83c161 0 292-131 292-292 0-161-131-292-292-292-161 0-292 131-292 292 0 161 131 292 292 292z m-125-645c39-14 81-22 125-22 44 0 86 8 125 22l0-35c-38-18-80-29-125-29-45 0-87 11-125 29l0 35z m10-128c36-12 75-19 115-19 40 0 79 7 115 19-25-26-67-61-115-61-46 0-88 33-115 61z m229 337h0c-35 1-49 36-65 76-18 45-30 65-50 66h0c-20 0-33-21-52-64-18-41-33-76-68-75l-11 2c-24 7-36 29-54 74-5 14-8 27-11 39-2 8-5 22-8 26 0 0-2 0-4 0-23 0-41 19-41 42 0 23 19 41 42 41h0c69 0 84-57 92-89 20 41 54 86 116 87 64-1 95-48 114-89 0 0 0 0 0 0 9 32 25 91 94 91h1c23 0 41-19 41-42 0-23-19-41-42-41h0c-6-3-11-20-13-29-4-13-7-27-13-40-18-41-33-75-68-75" horiz-adv-x="1000" /> + +<glyph glyph-name="nature" unicode="" d="M646 517c-35 0-63-28-63-63s28-62 63-62 62 28 62 62-28 63-62 63m-292 0c-34 0-62-28-62-63s28-62 62-62 63 28 63 62-28 63-63 63m435 333h-1c-41 0-89-33-126-62-53 15-109 21-162 21-53 0-109-6-161-22-37 30-86 63-127 63h-1c-71 0-208-111-211-293-2-103 12-176 43-209 11-10 37-28 54-36 8-133 39-219 106-266 37-27 91-40 133-46-1-8-3-17-3-25 0-74 98-125 167-125 69 0 167 51 167 125 0 8-2 16-3 24 107 17 226 79 247 317 16 9 37 23 46 32 31 33 45 106 43 209-3 182-140 293-211 293m-655-381c-10-11-35-48-36-51-7 17-16 56-15 137 3 137 104 207 129 210 11-1 31-12 53-27-46-49-95-122-98-214-6-23-17-36-33-55m366-536c-37 0-81 29-83 42 0 27 20 52 41 67v-25c0-23 19-42 42-42s42 19 42 42v25c22-15 41-40 41-67-2-13-45-42-83-42m125 145v0c-15 17-33 31-53 42 46 22 94 56 94 92 0 77-74 92-166 92-91 0-166-15-166-92 0-36 48-70 94-92-20-11-38-25-53-42v1c-42 3-91 11-124 35-54 38-76 127-77 270 0 0 1 1 1 1 21 35 62 61 75 130 0 85 57 151 99 188 41 15 91 22 149 22 60 0 112-8 155-24 42-37 97-102 97-186 13-69 54-95 75-130 2-2 3-4 4-7-2-249-81-292-204-300m276 342c-1 3-3 6-4 8-10 16-21 30-31 41-16 19-27 32-33 55-3 92-52 165-98 214 22 15 42 26 53 27 26-3 126-74 129-210 1-75-15-134-16-135" horiz-adv-x="1000" /> + +<glyph glyph-name="foods" unicode="" d="M708 643c-76 0-119-17-153-39 21 49 74 122 195 122 23 0 42 19 42 42s-19 41-42 41c-122 0-193-57-231-104-3-4-5-7-8-11-22 79-61 155-127 155-26 0-57-12-92-40-82-65-70-122-21-167-121-10-271-98-271-332 0-191 207-459 375-459 82 0 99 20 125 42 26-22 43-42 125-42 168 0 375 267 375 458 0 248-169 334-292 334m-365 101c27 21 40 22 41 22 17-7 40-59 56-128-63 27-114 57-124 77 3 5 10 15 27 29m282-810c-45 0-50 5-65 17l-7 6c-16 13-34 19-53 19s-37-6-53-19l-7-6c-15-12-20-17-65-17-117 0-292 225-292 376 0 242 187 249 209 249 81 0 103-19 141-52 4-4 9-8 13-11 16-13 35-20 54-20s38 7 54 20c4 4 9 7 13 11 38 33 61 52 141 52 22 0 209-7 209-250 0-150-175-375-292-375" horiz-adv-x="1000" /> + +<glyph glyph-name="activity" unicode="" d="M500 850c-276 0-500-224-500-500 0-276 224-500 500-500 276 0 500 224 500 500 0 276-224 500-500 500m415-458h-205c10 105 52 199 82 254 68-66 113-155 123-254m-373 0v373c67-7 130-30 184-66-35-62-89-175-99-307h-85z m-84 0h-85c-9 132-64 245-99 307 54 36 117 59 184 66v-373z m0-84v-373c-67 7-130 30-184 66 35 62 90 176 99 307h85z m169 0c10-131 64-245 99-307-54-36-117-59-184-66v373h85z m-419 338c30-55 72-149 82-254h-204c9 99 54 188 122 254m-122-338h204c-10-105-52-198-82-254-68 66-113 155-122 254m706-254c-30 56-72 149-82 254h205c-10-99-55-188-123-254" horiz-adv-x="1000" /> + +<glyph glyph-name="flags" unicode="" d="M0 850l254-1000h79l-253 1000h-80z m875-208h-167l-41 166h-500l125-500h125l41-166h84 458l-125 500z m-601 83h328l83-333h-328l-83 333z m368-417l-119-79-20 79h139z m150 0l-63 250h81l83-333h-226l125 83z" horiz-adv-x="1000" /> + +<glyph glyph-name="people-plus" unicode="" d="M333 558c-46 0-83-37-83-83s37-83 83-83 84 37 84 83-38 83-84 83m334 0c-46 0-84-37-84-83s38-83 84-83 83 37 83 83-37 83-83 83m-32-333c-29-50-78-83-140-83-61 0-110 33-139 83h269m125 83h-500c0-138 112-250 250-250 138 0 250 112 250 250m250 42c0-276-224-500-500-500s-500 224-500 500c0 30 3 59 8 87h85c-6-28-10-57-10-87 0-230 187-417 417-417 230 0 417 187 417 417 0 230-187 417-417 417-30 0-59-4-87-10v85c28 5 57 8 87 8 276 0 500-224 500-500z m-667 292h-125v-125h-83v125h-125v83h125v125h83v-125h125v-83z" horiz-adv-x="1000" /> + +<glyph glyph-name="file-pdf" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-287 331q18-14 47-31 33 4 65 4 82 0 99-27 9-13 1-29 0-1-1-1l-1-2v0q-3-21-39-21-27 0-64 11t-73 29q-123-13-219-46-85-146-135-146-8 0-15 4l-14 7q0 0-3 2-6 6-4 20 5 23 32 51t73 54q8 5 13-3 1-1 1-2 29 47 60 110 38 76 58 146-13 46-17 89t4 71q6 22 23 22h12q13 0 20-8 10-12 5-38-1-3-2-4 0-2 0-5v-17q-1-68-8-107 31-91 82-133z m-321-229q29 13 76 88-29-22-49-47t-27-41z m222 513q-9-23-2-73 1 4 4 24 0 2 4 24 1 3 3 5-1 0-1 1-1 1-1 2 0 12-7 20 0-1 0-1v-2z m-70-368q76 30 159 45-1 0-7 5t-9 8q-43 37-71 98-15-48-46-110-17-31-26-46z m361 9q-13 13-78 13 42-16 69-16 8 0 10 1 0 0-1 2z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-word" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-656 500v-59h39l92-369h88l72 271q4 11 5 25 2 9 2 14h2l1-14q1-1 2-11t3-14l72-271h89l91 369h39v59h-167v-59h50l-55-245q-3-11-4-25l-1-12h-3q0 2 0 4t-1 4 0 4q-1 2-2 11t-3 14l-81 304h-63l-81-304q-1-5-2-13t-2-12l-2-12h-2l-2 12q-1 14-3 25l-56 245h50v59h-167z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-excel" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-547 131v-59h157v59h-42l58 90q3 4 5 9t5 8 2 2h1q0-2 3-6 1-2 2-4t3-4 4-5l60-90h-43v-59h163v59h-38l-107 152 108 158h38v59h-156v-59h41l-57-89q-2-4-6-9t-5-8l-1-1h-1q0 2-3 5-3 6-9 13l-59 89h42v59h-162v-59h38l106-152-109-158h-38z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-powerpoint" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-554 131v-59h183v59h-52v93h76q43 0 66 9 37 12 60 48t22 82q0 45-21 78t-56 49q-27 10-72 10h-206v-59h52v-310h-52z m197 156h-66v150h67q29 0 46-10 31-19 31-64 0-50-34-67-18-9-44-9z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-image" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-72 250v-178h-571v107l107 107 71-71 215 214z m-464 108q-45 0-76 31t-31 76 31 76 76 31 76-31 31-76-31-76-76-31z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-archive" unicode="" d="M357 636v71h-71v-71h71z m72-72v72h-72v-72h72z m-72-71v71h-71v-71h71z m72-72v72h-72v-72h72z m390 217q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-71v-72h-72v72h-286v-858h715z m-350 403l60-195q4-15 4-29 0-46-40-77t-103-30-102 30-41 77q0 14 5 29 12 35 67 221v71h71v-71h44q13 0 22-7t13-19z m-79-260q30 0 51 11t21 25-21 25-51 11-50-11-21-25 21-25 50-11z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-audio" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-440 455q11-5 11-17v-304q0-12-11-16-4-1-7-1-6 0-12 5l-93 93h-73q-8 0-13 5t-5 13v107q0 8 5 13t13 5h73l93 93q8 8 19 4z m233-385q17 0 28 14 72 88 72 202t-72 203q-9 11-24 13t-27-8q-11-9-13-24t8-26q56-69 56-158t-56-157q-9-12-8-27t13-23q10-9 23-9z m-118 83q15 0 26 11 49 52 49 122t-49 122q-10 11-25 12t-26-10-11-25 10-26q29-32 29-73t-29-73q-10-11-10-26t11-25q12-9 25-9z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-video" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-357 500q29 0 50-21t21-50v-214q0-29-21-50t-50-22h-215q-29 0-50 22t-21 50v214q0 29 21 50t50 21h215z m274-1q11-4 11-16v-322q0-12-11-17-4-1-7-1-7 0-12 5l-148 149v50l148 148q5 5 12 5 3 0 7-1z" horiz-adv-x="857.1" /> + +<glyph glyph-name="file-code" unicode="" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-518 500q4 7 12 7t13-3l28-21q7-5 7-12t-3-13l-102-136 102-136q4-6 3-13t-7-12l-28-21q-6-4-13-4t-12 7l-126 168q-8 11 0 22z m447-167q8-11 0-22l-126-168q-4-6-11-7t-14 4l-28 21q-6 5-7 12t3 13l102 136-102 136q-4 6-3 13t7 12l28 21q6 4 14 3t11-7z m-346-258q-7 1-11 8t-3 13l77 464q1 7 7 11t14 3l35-5q7-2 11-8t3-13l-77-464q-1-7-7-11t-13-3z" horiz-adv-x="857.1" /> + +<glyph glyph-name="issue-closed" unicode="" d="M438 225h125v-125h-125v125z m125 375h-125v-312h125v312z m93-94l-62-62 156-156 250 281-62 62-188-218-94 93z m-156-512c-196 0-356 160-356 356s160 356 356 356c114 0 216-54 281-137l58 57c-80 99-202 162-339 162-241 0-437-197-437-438s196-438 437-438 438 197 438 438l-95-95c-41-150-179-261-343-261z" horiz-adv-x="1000" /> + +<glyph glyph-name="issue-opened" unicode="" d="M438 706c196 0 356-160 356-356s-160-356-356-356-357 160-357 356 160 356 357 356m0 82c-242 0-438-197-438-438s196-438 438-438 437 197 437 438-196 438-437 438z m62-188h-125v-312h125v312z m0-375h-125v-125h125v125z" horiz-adv-x="875" /> + +<glyph glyph-name="issue-reopened" unicode="" d="M500 288h-125v312h125v-312z m-125-188h125v125h-125v-125z m396 125h-146l94-94c-66-83-167-137-281-137-197 0-357 160-357 356 0 22 2 42 6 63h-82c-3-21-5-41-5-63 0-241 196-438 438-438 136 0 257 64 337 163l100-100v250h-104z m-666 250h145l-94 94c66 83 167 137 282 137 196 0 356-160 356-356 0-21-2-42-6-62h82c3 20 5 41 5 62 0 241-196 438-437 438-137 0-258-64-338-163l-100 100v-250h105z" horiz-adv-x="875" /> + +<glyph glyph-name="milestone" unicode="" d="M500 725h-125v125h125v-125z m250-312h-625c-34 0-62 28-62 62v125c0 34 28 63 62 63h625l125-125-125-125z m-250 187h-125v-125h125v125z m-125-750h125v500h-125v-500z" horiz-adv-x="875" /> + +<glyph glyph-name="mirror" unicode="" d="M969 556l-438 294-437-294c-19-12-31-28-31-50v-656l468 250 469-250v656c0 22-13 38-31 50z m-31-612l-375 203v78h-63v-78l-375-203v562l375 250v-281h63v281l375-250v-562z m-563 469h313v125l187-188-187-187v125h-313v-125l-187 187 187 188v-125z" horiz-adv-x="1000" /> + +<glyph glyph-name="plug" unicode="" d="M979 597q21-21 21-50t-21-51l-223-223 83-84-89-89q-91-91-217-104t-230 56l-202-202h-101v101l202 202q-69 103-56 230t104 217l89 89 84-83 223 223q21 21 51 21t50-21 21-50-21-51l-223-223 131-131 223 223q22 21 51 21t50-21z" horiz-adv-x="1000" /> + +<glyph glyph-name="repo-forked" unicode="" d="M500 788c-69 0-125-56-125-125 0-46 25-86 63-108v-80l-125-125-125 125v80c37 22 62 62 62 108 0 69-56 125-125 125s-125-56-125-125c0-46 25-86 63-108v-111l187-188v-111c-37-22-62-62-62-107 0-70 55-126 125-126s125 56 125 126c0 45-26 85-63 107v111l188 188v111c37 22 62 62 62 108 0 69-56 125-125 125z m-375-201c-41 0-75 35-75 76s34 75 75 75 75-34 75-75-34-76-75-76z m188-625c-41 0-76 34-76 76s35 75 76 75 75-34 75-75-34-76-75-76z m187 625c-41 0-75 35-75 76s34 75 75 75 75-34 75-75-34-76-75-76z" horiz-adv-x="625" /> + +<glyph glyph-name="repo-pull" unicode="" d="M813 350v125h-375v125h375v125l187-187-187-188z m-563 375h-62v-62h62v62z m438-312h62v-375c0-35-28-63-62-63h-313v-125l-94 94-93-94v125h-125c-35 0-63 28-63 63v750c0 34 28 62 63 62h625c34 0 62-28 62-62v-125h-62v125h-563v-563h563v188z m0-250h-625v-125h125v62h187v-62h313v125z m-438 312h-62v-62h62v62z m0 125h-62v-62h62v62z m-62-312h62v62h-62v-62z" horiz-adv-x="1000" /> + +<glyph glyph-name="repo-push" unicode="" d="M250 663h-62v62h62v-62z m-62-125h62v62h-62v-62z m250 0l-188-250h125v-438h125v438h125l-187 250z m250 312h-625c-35 0-63-28-63-62v-750c0-35 28-63 63-63h250v63h-250v125h250v62h-188v563h564l-1-563h-125v-62h125v-125h-125v-63h125c34 0 62 28 62 63v750c0 34-28 62-62 62z" horiz-adv-x="750" /> + +<glyph glyph-name="shield-key" unicode="" d="M438 850l-438-125v-376c0-292 332-499 438-499s437 207 437 499v376l-437 125z m-125-687l71 174c3 15-4 30-16 37-35 23-55 60-55 101 0 68 55 125 124 125 67 0 126-57 126-125 0-41-21-78-56-101-12-7-19-22-16-37l72-174h-250z" horiz-adv-x="875" /> + +<glyph glyph-name="repo-clone" unicode="" d="M938 850h-375v-437c0-35 28-63 62-63h63v-62h62v62h188c34 0 62 28 62 63v375c0 34-28 62-62 62z m-250-437h-63v62h63v-62z m250 0h-188v62h188v-62z m0 125h-250v250h250v-250z m-688 0h-62v62h62v-62z m0 125h-62v62h62v-62z m-125 125h375v62h-437c-35 0-63-28-63-62v-750c0-35 28-63 63-63h125v-125l93 94 94-94v125h313c34 0 62 28 62 63v187h-625v563z m563-625v-125h-313v62h-187v-62h-125v125h625z m-500 187h62v-62h-62v62z m62 63h-62v62h62v-62z" horiz-adv-x="1000" /> + +<glyph glyph-name="repo-force-push" unicode="" d="M625 288h-125v-438h-125v438h-125l141 187h-141l188 250 187-250h-141l141-187z m63 562h-625c-35 0-63-28-63-62v-750c0-35 28-63 63-63h250v63h-250v125h250v62h-188v563h563v-563h-125v-62h125v-125h-125v-63h125c34 0 62 28 62 63v750c0 34-28 62-62 62z" horiz-adv-x="750" /> + +<glyph glyph-name="unverified" unicode="" d="M980 409l-68 84c-11 13-17 30-19 47l-12 107c-5 44-39 78-83 83l-107 12c-18 2-35 9-48 20l-84 68c-35 27-83 27-118 0l-84-68c-13-11-30-17-47-19l-107-12c-44-5-78-39-83-83l-12-107c-2-18-9-35-20-48l-67-84c-28-35-28-83 0-118l67-84c11-13 18-30 19-47l12-107c5-44 39-78 83-83l107-12c18-2 35-9 48-20l84-68c35-27 83-27 118 0l84 68c13 11 30 17 47 19l107 12c44 5 78 39 83 83l12 107c2 18 9 35 20 48l68 84c27 35 27 83 0 118z m-417-278c0-17-14-31-32-31h-62c-17 0-31 14-31 31v63c0 17 14 31 31 31h62c18 0 32-14 32-31v-63z m97 306c-4-11-11-21-18-29-8-10-9-12-21-24-10-11-19-18-32-28-7-6-13-12-18-17s-8-10-11-16-5-12-7-19-2-8-2-15h-106c0 13 0 19 2 30 2 12 5 22 9 32 4 9 9 18 16 26 7 8 14 16 25 24 17 12 23 18 30 32s13 23 13 37c0 17-4 28-13 36-7 8-19 12-36 12-6 0-11-1-18-3s-11-6-16-10-9-7-13-12-5-9-5-18h-125c0 23 7 35 16 52 10 16 23 31 38 42s34 18 55 23 44 8 68 8c28 0 52-3 74-8 21-6 39-13 54-24 15-11 26-24 34-39 8-16 12-34 12-55 0-14 0-26-5-37z" horiz-adv-x="1000" /> + +<glyph glyph-name="verified" unicode="" d="M980 409l-68 84c-11 13-17 30-19 47l-12 107c-5 44-39 78-83 83l-107 12c-18 2-35 9-48 20l-84 68c-35 27-83 27-118 0l-84-68c-13-11-30-17-47-19l-107-12c-44-5-78-39-83-83l-12-107c-2-18-9-35-20-48l-67-84c-28-35-28-83 0-118l67-84c11-13 18-30 19-47l12-107c5-44 39-78 83-83l107-12c18-2 35-9 48-20l84-68c35-27 83-27 118 0l84 68c13 11 30 17 47 19l107 12c44 5 78 39 83 83l12 107c2 18 9 35 20 48l68 84c27 35 27 83 0 118z m-574-309l-218 219 93 94 125-125 313 312 94-97-407-403z" horiz-adv-x="1000" /> + +<glyph glyph-name="zap" unicode="" d="M625 413h-250l188 437-563-562h250l-187-438 562 563z" horiz-adv-x="625" /> + +<glyph glyph-name="pulse" unicode="" d="M719 350l-169 163-137-194-69 431-195-400h-149v-125h225l56 113 57-338 225 319 100-94h212v125h-156z" horiz-adv-x="875" /> + +<glyph glyph-name="versions" unicode="" d="M813 663h-375c-35 0-63-29-63-63v-500c0-34 28-62 63-62h375c34 0 62 28 62 62v500c0 34-28 63-62 63z m-63-500h-250v375h250v-375z m-500 437h63v-62h-63v-375h63v-63h-63c-34 0-62 28-62 63v375c0 34 28 62 62 62z m-187-62h62v-63h-62v-250h62v-62h-62c-35 0-63 28-63 62v250c0 34 28 63 63 63z" horiz-adv-x="875" /> + +<glyph glyph-name="text-size" unicode="" d="M1123-25h-141l-59 203h-254l-60-203h-140l-43 146h-205l-44-146h-136l206 600h156l136-397 181 547h157l246-750z m-725 242s-64 226-74 257h-5l-70-257h149z m495 66l-95 339h-4l-94-339h193z" horiz-adv-x="1125" /> + +<glyph glyph-name="markdown" unicode="" d="M928 663h-856c-40 0-72-33-72-73v-480c0-40 32-72 72-72h856c40 0 72 32 72 72v480c0 40-32 73-72 73z m-365-500l-125 0v187l-94-120-94 120v-187h-125v375h125l94-125 94 125 125 0v-375z m186-32l-155 219h94v188h125v-188h93l-157-219z" horiz-adv-x="1000" /> + +<glyph glyph-name="no-newline" unicode="" d="M1000 538v-188c0-34-28-62-62-62h-188v-125l-187 187 187 188v-125h125v125h125z m-500-188c0-138-112-250-250-250s-250 112-250 250 112 250 250 250 250-112 250-250z m-406-103l260 259c-31 20-66 32-104 32-103 0-187-84-187-188 0-38 11-73 31-103z m344 103c0 38-12 73-32 104l-259-260c30-20 65-31 103-31 104 0 188 84 188 187z" horiz-adv-x="1000" /> + +<glyph glyph-name="tools" unicode="" d="M280 396c16-16 80-83 80-83l35 36-55 57 105 112c0 0-47 47-26 28 20 74 1 157-55 215-56 58-135 77-206 57l120-125-31-122-119-33-120 125c-20-74-1-156 55-214 58-60 143-78 217-53z m402-121l-145-144 240-249c20-20 45-31 71-31 26 0 52 11 71 31 40 40 40 106 0 147l-237 246z m318 417l-153 158-451-466 55-57-270-279-62-33-87-142 23-23 137 90 32 64 270 279 55-57 451 466z" horiz-adv-x="1000" /> + +<glyph glyph-name="tape" unicode="" d="M770 580q96 0 163-67t67-163q0-94-67-162t-163-68l-540 0q-94 0-162 68t-68 162q0 96 68 163t162 67q96 0 163-67t67-163q0-72-40-130l160 0q-40 64-40 130 0 96 68 163t162 67z m-670-230q0-52 38-91t92-39 92 39 38 91q0 54-38 92t-92 38-92-38-38-92z m670-130q54 0 92 39t38 91q0 54-38 92t-92 38-92-38-38-92q0-52 38-91t92-39z" horiz-adv-x="1000" /> +</font> +</defs> +</svg> \ No newline at end of file diff --git a/public/fonts/fontello.ttf b/packages/rocketchat-theme/client/vendor/fontello/font/fontello.ttf similarity index 100% rename from public/fonts/fontello.ttf rename to packages/rocketchat-theme/client/vendor/fontello/font/fontello.ttf diff --git a/public/fonts/fontello.woff b/packages/rocketchat-theme/client/vendor/fontello/font/fontello.woff similarity index 100% rename from public/fonts/fontello.woff rename to packages/rocketchat-theme/client/vendor/fontello/font/fontello.woff diff --git a/public/fonts/fontello.woff2 b/packages/rocketchat-theme/client/vendor/fontello/font/fontello.woff2 similarity index 100% rename from public/fonts/fontello.woff2 rename to packages/rocketchat-theme/client/vendor/fontello/font/fontello.woff2 diff --git a/private/fontello.json b/packages/rocketchat-theme/client/vendor/fontello/fontello.json similarity index 100% rename from private/fontello.json rename to packages/rocketchat-theme/client/vendor/fontello/fontello.json diff --git a/private/utf8-rtl.html b/packages/rocketchat-theme/client/vendor/fontello/utf8-rtl.html similarity index 100% rename from private/utf8-rtl.html rename to packages/rocketchat-theme/client/vendor/fontello/utf8-rtl.html diff --git a/packages/rocketchat-theme/client/vendor/jscolor.js b/packages/rocketchat-theme/client/vendor/jscolor.js new file mode 100644 index 0000000000000000000000000000000000000000..8d65d83740ef0165880e2ab9f696b2619680a0d1 --- /dev/null +++ b/packages/rocketchat-theme/client/vendor/jscolor.js @@ -0,0 +1,1844 @@ +/** + * jscolor - JavaScript Color Picker + * + * @link http://jscolor.com + * @license For open source use: GPLv3 + * For commercial use: JSColor Commercial License + * @author Jan Odvarko + * @version 2.0.4 + * + * See usage examples at http://jscolor.com/examples/ + */ + + +"use strict"; + + +if (!window.jscolor) { window.jscolor = (function () { + + +var jsc = { + + + register : function () { + jsc.attachDOMReadyEvent(jsc.init); + jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown); + jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart); + jsc.attachEvent(window, 'resize', jsc.onWindowResize); + }, + + + init : function () { + if (jsc.jscolor.lookupClass) { + jsc.jscolor.installByClassName(jsc.jscolor.lookupClass); + } + }, + + + tryInstallOnElements : function (elms, className) { + var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); + + for (var i = 0; i < elms.length; i += 1) { + if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') { + if (jsc.isColorAttrSupported) { + // skip inputs of type 'color' if supported by the browser + continue; + } + } + var m; + if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) { + var targetElm = elms[i]; + var optsStr = null; + + var dataOptions = jsc.getDataAttr(targetElm, 'jscolor'); + if (dataOptions !== null) { + optsStr = dataOptions; + } else if (m[4]) { + optsStr = m[4]; + } + + var opts = {}; + if (optsStr) { + try { + opts = (new Function ('return (' + optsStr + ')'))(); + } catch(eParseError) { + jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr); + } + } + targetElm.jscolor = new jsc.jscolor(targetElm, opts); + } + } + }, + + + isColorAttrSupported : (function () { + var elm = document.createElement('input'); + if (elm.setAttribute) { + elm.setAttribute('type', 'color'); + if (elm.type.toLowerCase() == 'color') { + return true; + } + } + return false; + })(), + + + isCanvasSupported : (function () { + var elm = document.createElement('canvas'); + return !!(elm.getContext && elm.getContext('2d')); + })(), + + + fetchElement : function (mixed) { + return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; + }, + + + isElementType : function (elm, type) { + return elm.nodeName.toLowerCase() === type.toLowerCase(); + }, + + + getDataAttr : function (el, name) { + var attrName = 'data-' + name; + var attrValue = el.getAttribute(attrName); + if (attrValue !== null) { + return attrValue; + } + return null; + }, + + + attachEvent : function (el, evnt, func) { + if (el.addEventListener) { + el.addEventListener(evnt, func, false); + } else if (el.attachEvent) { + el.attachEvent('on' + evnt, func); + } + }, + + + detachEvent : function (el, evnt, func) { + if (el.removeEventListener) { + el.removeEventListener(evnt, func, false); + } else if (el.detachEvent) { + el.detachEvent('on' + evnt, func); + } + }, + + + _attachedGroupEvents : {}, + + + attachGroupEvent : function (groupName, el, evnt, func) { + if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { + jsc._attachedGroupEvents[groupName] = []; + } + jsc._attachedGroupEvents[groupName].push([el, evnt, func]); + jsc.attachEvent(el, evnt, func); + }, + + + detachGroupEvents : function (groupName) { + if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { + for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { + var evt = jsc._attachedGroupEvents[groupName][i]; + jsc.detachEvent(evt[0], evt[1], evt[2]); + } + delete jsc._attachedGroupEvents[groupName]; + } + }, + + + attachDOMReadyEvent : function (func) { + var fired = false; + var fireOnce = function () { + if (!fired) { + fired = true; + func(); + } + }; + + if (document.readyState === 'complete') { + setTimeout(fireOnce, 1); // async + return; + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireOnce, false); + + // Fallback + window.addEventListener('load', fireOnce, false); + + } else if (document.attachEvent) { + // IE + document.attachEvent('onreadystatechange', function () { + if (document.readyState === 'complete') { + document.detachEvent('onreadystatechange', arguments.callee); + fireOnce(); + } + }) + + // Fallback + window.attachEvent('onload', fireOnce); + + // IE7/8 + if (document.documentElement.doScroll && window == window.top) { + var tryScroll = function () { + if (!document.body) { return; } + try { + document.documentElement.doScroll('left'); + fireOnce(); + } catch (e) { + setTimeout(tryScroll, 1); + } + }; + tryScroll(); + } + } + }, + + + warn : function (msg) { + if (window.console && window.console.warn) { + window.console.warn(msg); + } + }, + + + preventDefault : function (e) { + if (e.preventDefault) { e.preventDefault(); } + e.returnValue = false; + }, + + + captureTarget : function (target) { + // IE + if (target.setCapture) { + jsc._capturedTarget = target; + jsc._capturedTarget.setCapture(); + } + }, + + + releaseTarget : function () { + // IE + if (jsc._capturedTarget) { + jsc._capturedTarget.releaseCapture(); + jsc._capturedTarget = null; + } + }, + + + fireEvent : function (el, evnt) { + if (!el) { + return; + } + if (document.createEvent) { + var ev = document.createEvent('HTMLEvents'); + ev.initEvent(evnt, true, true); + el.dispatchEvent(ev); + } else if (document.createEventObject) { + var ev = document.createEventObject(); + el.fireEvent('on' + evnt, ev); + } else if (el['on' + evnt]) { // alternatively use the traditional event model + el['on' + evnt](); + } + }, + + + classNameToList : function (className) { + return className.replace(/^\s+|\s+$/g, '').split(/\s+/); + }, + + + // The className parameter (str) can only contain a single class name + hasClass : function (elm, className) { + if (!className) { + return false; + } + return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); + }, + + + // The className parameter (str) can contain multiple class names separated by whitespace + setClass : function (elm, className) { + var classList = jsc.classNameToList(className); + for (var i = 0; i < classList.length; i += 1) { + if (!jsc.hasClass(elm, classList[i])) { + elm.className += (elm.className ? ' ' : '') + classList[i]; + } + } + }, + + + // The className parameter (str) can contain multiple class names separated by whitespace + unsetClass : function (elm, className) { + var classList = jsc.classNameToList(className); + for (var i = 0; i < classList.length; i += 1) { + var repl = new RegExp( + '^\\s*' + classList[i] + '\\s*|' + + '\\s*' + classList[i] + '\\s*$|' + + '\\s+' + classList[i] + '(\\s+)', + 'g' + ); + elm.className = elm.className.replace(repl, '$1'); + } + }, + + + getStyle : function (elm) { + return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle; + }, + + + setStyle : (function () { + var helper = document.createElement('div'); + var getSupportedProp = function (names) { + for (var i = 0; i < names.length; i += 1) { + if (names[i] in helper.style) { + return names[i]; + } + } + }; + var props = { + borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']), + boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow']) + }; + return function (elm, prop, value) { + switch (prop.toLowerCase()) { + case 'opacity': + var alphaOpacity = Math.round(parseFloat(value) * 100); + elm.style.opacity = value; + elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')'; + break; + default: + elm.style[props[prop]] = value; + break; + } + }; + })(), + + + setBorderRadius : function (elm, value) { + jsc.setStyle(elm, 'borderRadius', value || '0'); + }, + + + setBoxShadow : function (elm, value) { + jsc.setStyle(elm, 'boxShadow', value || 'none'); + }, + + + getElementPos : function (e, relativeToViewport) { + var x=0, y=0; + var rect = e.getBoundingClientRect(); + x = rect.left; + y = rect.top; + if (!relativeToViewport) { + var viewPos = jsc.getViewPos(); + x += viewPos[0]; + y += viewPos[1]; + } + return [x, y]; + }, + + + getElementSize : function (e) { + return [e.offsetWidth, e.offsetHeight]; + }, + + + // get pointer's X/Y coordinates relative to viewport + getAbsPointerPos : function (e) { + if (!e) { e = window.event; } + var x = 0, y = 0; + if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { + // touch devices + x = e.changedTouches[0].clientX; + y = e.changedTouches[0].clientY; + } else if (typeof e.clientX === 'number') { + x = e.clientX; + y = e.clientY; + } + return { x: x, y: y }; + }, + + + // get pointer's X/Y coordinates relative to target element + getRelPointerPos : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + var targetRect = target.getBoundingClientRect(); + + var x = 0, y = 0; + + var clientX = 0, clientY = 0; + if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { + // touch devices + clientX = e.changedTouches[0].clientX; + clientY = e.changedTouches[0].clientY; + } else if (typeof e.clientX === 'number') { + clientX = e.clientX; + clientY = e.clientY; + } + + x = clientX - targetRect.left; + y = clientY - targetRect.top; + return { x: x, y: y }; + }, + + + getViewPos : function () { + var doc = document.documentElement; + return [ + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0), + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) + ]; + }, + + + getViewSize : function () { + var doc = document.documentElement; + return [ + (window.innerWidth || doc.clientWidth), + (window.innerHeight || doc.clientHeight), + ]; + }, + + + redrawPosition : function () { + + if (jsc.picker && jsc.picker.owner) { + var thisObj = jsc.picker.owner; + + var tp, vp; + + if (thisObj.fixed) { + // Fixed elements are positioned relative to viewport, + // therefore we can ignore the scroll offset + tp = jsc.getElementPos(thisObj.targetElement, true); // target pos + vp = [0, 0]; // view pos + } else { + tp = jsc.getElementPos(thisObj.targetElement); // target pos + vp = jsc.getViewPos(); // view pos + } + + var ts = jsc.getElementSize(thisObj.targetElement); // target size + var vs = jsc.getViewSize(); // view size + var ps = jsc.getPickerOuterDims(thisObj); // picker size + var a, b, c; + switch (thisObj.position.toLowerCase()) { + case 'left': a=1; b=0; c=-1; break; + case 'right':a=1; b=0; c=1; break; + case 'top': a=0; b=1; c=-1; break; + default: a=0; b=1; c=1; break; + } + var l = (ts[b]+ps[b])/2; + + // compute picker position + if (!thisObj.smartPosition) { + var pp = [ + tp[a], + tp[b]+ts[b]-l+l*c + ]; + } else { + var pp = [ + -vp[a]+tp[a]+ps[a] > vs[a] ? + (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : + tp[a], + -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? + (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : + (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) + ]; + } + + var x = pp[a]; + var y = pp[b]; + var positionValue = thisObj.fixed ? 'fixed' : 'absolute'; + var contractShadow = + (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) && + (pp[1] + ps[1] < tp[1] + ts[1]); + + jsc._drawPosition(thisObj, x, y, positionValue, contractShadow); + } + }, + + + _drawPosition : function (thisObj, x, y, positionValue, contractShadow) { + var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px + + jsc.picker.wrap.style.position = positionValue; + jsc.picker.wrap.style.left = x + 'px'; + jsc.picker.wrap.style.top = y + 'px'; + + jsc.setBoxShadow( + jsc.picker.boxS, + thisObj.shadow ? + new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) : + null); + }, + + + getPickerDims : function (thisObj) { + var displaySlider = !!jsc.getSliderComponent(thisObj); + var dims = [ + 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width + + (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0), + 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height + + (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0) + ]; + return dims; + }, + + + getPickerOuterDims : function (thisObj) { + var dims = jsc.getPickerDims(thisObj); + return [ + dims[0] + 2 * thisObj.borderWidth, + dims[1] + 2 * thisObj.borderWidth + ]; + }, + + + getPadToSliderPadding : function (thisObj) { + return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness)); + }, + + + getPadYComponent : function (thisObj) { + switch (thisObj.mode.charAt(1).toLowerCase()) { + case 'v': return 'v'; break; + } + return 's'; + }, + + + getSliderComponent : function (thisObj) { + if (thisObj.mode.length > 2) { + switch (thisObj.mode.charAt(2).toLowerCase()) { + case 's': return 's'; break; + case 'v': return 'v'; break; + } + } + return null; + }, + + + onDocumentMouseDown : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + + if (target._jscLinkedInstance) { + if (target._jscLinkedInstance.showOnClick) { + target._jscLinkedInstance.show(); + } + } else if (target._jscControlName) { + jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse'); + } else { + // Mouse is outside the picker controls -> hide the color picker! + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + } + }, + + + onDocumentTouchStart : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + + if (target._jscLinkedInstance) { + if (target._jscLinkedInstance.showOnClick) { + target._jscLinkedInstance.show(); + } + } else if (target._jscControlName) { + jsc.onControlPointerStart(e, target, target._jscControlName, 'touch'); + } else { + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + } + }, + + + onWindowResize : function (e) { + jsc.redrawPosition(); + }, + + + onParentScroll : function (e) { + // hide the picker when one of the parent elements is scrolled + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + }, + + + _pointerMoveEvent : { + mouse: 'mousemove', + touch: 'touchmove' + }, + _pointerEndEvent : { + mouse: 'mouseup', + touch: 'touchend' + }, + + + _pointerOrigin : null, + _capturedTarget : null, + + + onControlPointerStart : function (e, target, controlName, pointerType) { + var thisObj = target._jscInstance; + + jsc.preventDefault(e); + jsc.captureTarget(target); + + var registerDragEvents = function (doc, offset) { + jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType], + jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset)); + jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType], + jsc.onDocumentPointerEnd(e, target, controlName, pointerType)); + }; + + registerDragEvents(document, [0, 0]); + + if (window.parent && window.frameElement) { + var rect = window.frameElement.getBoundingClientRect(); + var ofs = [-rect.left, -rect.top]; + registerDragEvents(window.parent.window.document, ofs); + } + + var abs = jsc.getAbsPointerPos(e); + var rel = jsc.getRelPointerPos(e); + jsc._pointerOrigin = { + x: abs.x - rel.x, + y: abs.y - rel.y + }; + + switch (controlName) { + case 'pad': + // if the slider is at the bottom, move it up + switch (jsc.getSliderComponent(thisObj)) { + case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break; + case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break; + } + jsc.setPad(thisObj, e, 0, 0); + break; + + case 'sld': + jsc.setSld(thisObj, e, 0); + break; + } + + jsc.dispatchFineChange(thisObj); + }, + + + onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { + return function (e) { + var thisObj = target._jscInstance; + switch (controlName) { + case 'pad': + if (!e) { e = window.event; } + jsc.setPad(thisObj, e, offset[0], offset[1]); + jsc.dispatchFineChange(thisObj); + break; + + case 'sld': + if (!e) { e = window.event; } + jsc.setSld(thisObj, e, offset[1]); + jsc.dispatchFineChange(thisObj); + break; + } + } + }, + + + onDocumentPointerEnd : function (e, target, controlName, pointerType) { + return function (e) { + var thisObj = target._jscInstance; + jsc.detachGroupEvents('drag'); + jsc.releaseTarget(); + // Always dispatch changes after detaching outstanding mouse handlers, + // in case some user interaction will occur in user's onchange callback + // that would intrude with current mouse events + jsc.dispatchChange(thisObj); + }; + }, + + + dispatchChange : function (thisObj) { + if (thisObj.valueElement) { + if (jsc.isElementType(thisObj.valueElement, 'input')) { + jsc.fireEvent(thisObj.valueElement, 'change'); + } + } + }, + + + dispatchFineChange : function (thisObj) { + if (thisObj.onFineChange) { + var callback; + if (typeof thisObj.onFineChange === 'string') { + callback = new Function (thisObj.onFineChange); + } else { + callback = thisObj.onFineChange; + } + callback.call(thisObj); + } + }, + + + setPad : function (thisObj, e, ofsX, ofsY) { + var pointerAbs = jsc.getAbsPointerPos(e); + var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth; + var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; + + var xVal = x * (360 / (thisObj.width - 1)); + var yVal = 100 - (y * (100 / (thisObj.height - 1))); + + switch (jsc.getPadYComponent(thisObj)) { + case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break; + case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break; + } + }, + + + setSld : function (thisObj, e, ofsY) { + var pointerAbs = jsc.getAbsPointerPos(e); + var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; + + var yVal = 100 - (y * (100 / (thisObj.height - 1))); + + switch (jsc.getSliderComponent(thisObj)) { + case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break; + case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break; + } + }, + + + _vmlNS : 'jsc_vml_', + _vmlCSS : 'jsc_vml_css_', + _vmlReady : false, + + + initVML : function () { + if (!jsc._vmlReady) { + // init VML namespace + var doc = document; + if (!doc.namespaces[jsc._vmlNS]) { + doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml'); + } + if (!doc.styleSheets[jsc._vmlCSS]) { + var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image']; + var ss = doc.createStyleSheet(); + ss.owningElement.id = jsc._vmlCSS; + for (var i = 0; i < tags.length; i += 1) { + ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);'); + } + } + jsc._vmlReady = true; + } + }, + + + createPalette : function () { + + var paletteObj = { + elm: null, + draw: null + }; + + if (jsc.isCanvasSupported) { + // Canvas implementation for modern browsers + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var drawFunc = function (width, height, type) { + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0); + hGrad.addColorStop(0 / 6, '#F00'); + hGrad.addColorStop(1 / 6, '#FF0'); + hGrad.addColorStop(2 / 6, '#0F0'); + hGrad.addColorStop(3 / 6, '#0FF'); + hGrad.addColorStop(4 / 6, '#00F'); + hGrad.addColorStop(5 / 6, '#F0F'); + hGrad.addColorStop(6 / 6, '#F00'); + + ctx.fillStyle = hGrad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height); + switch (type.toLowerCase()) { + case 's': + vGrad.addColorStop(0, 'rgba(255,255,255,0)'); + vGrad.addColorStop(1, 'rgba(255,255,255,1)'); + break; + case 'v': + vGrad.addColorStop(0, 'rgba(0,0,0,0)'); + vGrad.addColorStop(1, 'rgba(0,0,0,1)'); + break; + } + ctx.fillStyle = vGrad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + paletteObj.elm = canvas; + paletteObj.draw = drawFunc; + + } else { + // VML fallback for IE 7 and 8 + + jsc.initVML(); + + var vmlContainer = document.createElement('div'); + vmlContainer.style.position = 'relative'; + vmlContainer.style.overflow = 'hidden'; + + var hGrad = document.createElement(jsc._vmlNS + ':fill'); + hGrad.type = 'gradient'; + hGrad.method = 'linear'; + hGrad.angle = '90'; + hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0' + + var hRect = document.createElement(jsc._vmlNS + ':rect'); + hRect.style.position = 'absolute'; + hRect.style.left = -1 + 'px'; + hRect.style.top = -1 + 'px'; + hRect.stroked = false; + hRect.appendChild(hGrad); + vmlContainer.appendChild(hRect); + + var vGrad = document.createElement(jsc._vmlNS + ':fill'); + vGrad.type = 'gradient'; + vGrad.method = 'linear'; + vGrad.angle = '180'; + vGrad.opacity = '0'; + + var vRect = document.createElement(jsc._vmlNS + ':rect'); + vRect.style.position = 'absolute'; + vRect.style.left = -1 + 'px'; + vRect.style.top = -1 + 'px'; + vRect.stroked = false; + vRect.appendChild(vGrad); + vmlContainer.appendChild(vRect); + + var drawFunc = function (width, height, type) { + vmlContainer.style.width = width + 'px'; + vmlContainer.style.height = height + 'px'; + + hRect.style.width = + vRect.style.width = + (width + 1) + 'px'; + hRect.style.height = + vRect.style.height = + (height + 1) + 'px'; + + // Colors must be specified during every redraw, otherwise IE won't display + // a full gradient during a subsequential redraw + hGrad.color = '#F00'; + hGrad.color2 = '#F00'; + + switch (type.toLowerCase()) { + case 's': + vGrad.color = vGrad.color2 = '#FFF'; + break; + case 'v': + vGrad.color = vGrad.color2 = '#000'; + break; + } + }; + + paletteObj.elm = vmlContainer; + paletteObj.draw = drawFunc; + } + + return paletteObj; + }, + + + createSliderGradient : function () { + + var sliderObj = { + elm: null, + draw: null + }; + + if (jsc.isCanvasSupported) { + // Canvas implementation for modern browsers + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var drawFunc = function (width, height, color1, color2) { + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); + grad.addColorStop(0, color1); + grad.addColorStop(1, color2); + + ctx.fillStyle = grad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + sliderObj.elm = canvas; + sliderObj.draw = drawFunc; + + } else { + // VML fallback for IE 7 and 8 + + jsc.initVML(); + + var vmlContainer = document.createElement('div'); + vmlContainer.style.position = 'relative'; + vmlContainer.style.overflow = 'hidden'; + + var grad = document.createElement(jsc._vmlNS + ':fill'); + grad.type = 'gradient'; + grad.method = 'linear'; + grad.angle = '180'; + + var rect = document.createElement(jsc._vmlNS + ':rect'); + rect.style.position = 'absolute'; + rect.style.left = -1 + 'px'; + rect.style.top = -1 + 'px'; + rect.stroked = false; + rect.appendChild(grad); + vmlContainer.appendChild(rect); + + var drawFunc = function (width, height, color1, color2) { + vmlContainer.style.width = width + 'px'; + vmlContainer.style.height = height + 'px'; + + rect.style.width = (width + 1) + 'px'; + rect.style.height = (height + 1) + 'px'; + + grad.color = color1; + grad.color2 = color2; + }; + + sliderObj.elm = vmlContainer; + sliderObj.draw = drawFunc; + } + + return sliderObj; + }, + + + leaveValue : 1<<0, + leaveStyle : 1<<1, + leavePad : 1<<2, + leaveSld : 1<<3, + + + BoxShadow : (function () { + var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) { + this.hShadow = hShadow; + this.vShadow = vShadow; + this.blur = blur; + this.spread = spread; + this.color = color; + this.inset = !!inset; + }; + + BoxShadow.prototype.toString = function () { + var vals = [ + Math.round(this.hShadow) + 'px', + Math.round(this.vShadow) + 'px', + Math.round(this.blur) + 'px', + Math.round(this.spread) + 'px', + this.color + ]; + if (this.inset) { + vals.push('inset'); + } + return vals.join(' '); + }; + + return BoxShadow; + })(), + + + // + // Usage: + // var myColor = new jscolor(<targetElement> [, <options>]) + // + + jscolor : function (targetElement, options) { + + // General options + // + this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB() + this.valueElement = targetElement; // element that will be used to display and input the color code + this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor + this.required = true; // whether the associated text <input> can be left empty + this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace) + this.hash = true; // whether to prefix the HEX color code with # symbol + this.uppercase = true; // whether to uppercase the color code + this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code) + this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it + this.minS = 0; // min allowed saturation (0 - 100) + this.maxS = 100; // max allowed saturation (0 - 100) + this.minV = 0; // min allowed value (brightness) (0 - 100) + this.maxV = 100; // max allowed value (brightness) (0 - 100) + + // Accessing the picked color + // + this.hsv = [0, 0, 100]; // read-only [0-360, 0-100, 0-100] + this.rgb = [255, 255, 255]; // read-only [0-255, 0-255, 0-255] + + // Color Picker options + // + this.width = 181; // width of color palette (in px) + this.height = 101; // height of color palette (in px) + this.showOnClick = true; // whether to display the color picker when user clicks on its target element + this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls + this.position = 'bottom'; // left | right | top | bottom - position relative to the target element + this.smartPosition = true; // automatically change picker position when there is not enough space for it + this.sliderSize = 16; // px + this.crossSize = 8; // px + this.closable = false; // whether to display the Close button + this.closeText = 'Close'; + this.buttonColor = '#000000'; // CSS color + this.buttonHeight = 18; // px + this.padding = 8; // px + this.backgroundColor = '#FFFFFF'; // CSS color + this.borderWidth = 1; // px + this.borderColor = '#BBBBBB'; // CSS color + this.borderRadius = 4; // px + this.insetWidth = 1; // px + this.insetColor = '#BBBBBB'; // CSS color + this.shadow = false; // whether to display shadow + this.shadowBlur = 15; // px + this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color + this.pointerColor = '#4C4C4C'; // px + this.pointerBorderColor = '#FFFFFF'; // px + this.pointerBorderWidth = 1; // px + this.pointerThickness = 2; // px + this.zIndex = 1000; + this.container = null; // where to append the color picker (BODY element by default) + + + for (var opt in options) { + if (options.hasOwnProperty(opt)) { + this[opt] = options[opt]; + } + } + + + this.hide = function () { + if (isPickerOwner()) { + detachPicker(); + } + }; + + + this.show = function () { + drawPicker(); + }; + + + this.redraw = function () { + if (isPickerOwner()) { + drawPicker(); + } + }; + + + this.importColor = function () { + if (!this.valueElement) { + this.exportColor(); + } else { + if (jsc.isElementType(this.valueElement, 'input')) { + if (!this.refine) { + if (!this.fromString(this.valueElement.value, jsc.leaveValue)) { + if (this.styleElement) { + this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; + this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; + this.styleElement.style.color = this.styleElement._jscOrigStyle.color; + } + this.exportColor(jsc.leaveValue | jsc.leaveStyle); + } + } else if (!this.required && /^\s*$/.test(this.valueElement.value)) { + this.valueElement.value = ''; + if (this.styleElement) { + this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; + this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; + this.styleElement.style.color = this.styleElement._jscOrigStyle.color; + } + this.exportColor(jsc.leaveValue | jsc.leaveStyle); + + } else if (this.fromString(this.valueElement.value)) { + // managed to import color successfully from the value -> OK, don't do anything + } else { + this.exportColor(); + } + } else { + // not an input element -> doesn't have any value + this.exportColor(); + } + } + }; + + + this.exportColor = function (flags) { + if (!(flags & jsc.leaveValue) && this.valueElement) { + var value = this.toString(); + if (this.uppercase) { value = value.toUpperCase(); } + if (this.hash) { value = '#' + value; } + + if (jsc.isElementType(this.valueElement, 'input')) { + this.valueElement.value = value; + } else { + this.valueElement.innerHTML = value; + } + } + // if (!(flags & jsc.leaveStyle)) { + // if (this.styleElement) { + // this.styleElement.style.backgroundImage = 'none'; + // this.styleElement.style.backgroundColor = '#' + this.toString(); + // this.styleElement.style.color = this.isLight() ? '#000' : '#FFF'; + // } + // } + if (!(flags & jsc.leavePad) && isPickerOwner()) { + redrawPad(); + } + if (!(flags & jsc.leaveSld) && isPickerOwner()) { + redrawSld(); + } + }; + + + // h: 0-360 + // s: 0-100 + // v: 0-100 + // + this.fromHSV = function (h, s, v, flags) { // null = don't change + if (h !== null) { + if (isNaN(h)) { return false; } + h = Math.max(0, Math.min(360, h)); + } + if (s !== null) { + if (isNaN(s)) { return false; } + s = Math.max(0, Math.min(100, this.maxS, s), this.minS); + } + if (v !== null) { + if (isNaN(v)) { return false; } + v = Math.max(0, Math.min(100, this.maxV, v), this.minV); + } + + this.rgb = HSV_RGB( + h===null ? this.hsv[0] : (this.hsv[0]=h), + s===null ? this.hsv[1] : (this.hsv[1]=s), + v===null ? this.hsv[2] : (this.hsv[2]=v) + ); + + this.exportColor(flags); + }; + + + // r: 0-255 + // g: 0-255 + // b: 0-255 + // + this.fromRGB = function (r, g, b, flags) { // null = don't change + if (r !== null) { + if (isNaN(r)) { return false; } + r = Math.max(0, Math.min(255, r)); + } + if (g !== null) { + if (isNaN(g)) { return false; } + g = Math.max(0, Math.min(255, g)); + } + if (b !== null) { + if (isNaN(b)) { return false; } + b = Math.max(0, Math.min(255, b)); + } + + var hsv = RGB_HSV( + r===null ? this.rgb[0] : r, + g===null ? this.rgb[1] : g, + b===null ? this.rgb[2] : b + ); + if (hsv[0] !== null) { + this.hsv[0] = Math.max(0, Math.min(360, hsv[0])); + } + if (hsv[2] !== 0) { + this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1])); + } + this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2])); + + // update RGB according to final HSV, as some values might be trimmed + var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); + this.rgb[0] = rgb[0]; + this.rgb[1] = rgb[1]; + this.rgb[2] = rgb[2]; + + this.exportColor(flags); + }; + + + this.fromString = function (str, flags) { + var m; + if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) { + // HEX notation + // + + if (m[1].length === 6) { + // 6-char notation + this.fromRGB( + parseInt(m[1].substr(0,2),16), + parseInt(m[1].substr(2,2),16), + parseInt(m[1].substr(4,2),16), + flags + ); + } else { + // 3-char notation + this.fromRGB( + parseInt(m[1].charAt(0) + m[1].charAt(0),16), + parseInt(m[1].charAt(1) + m[1].charAt(1),16), + parseInt(m[1].charAt(2) + m[1].charAt(2),16), + flags + ); + } + return true; + + } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) { + var params = m[1].split(','); + var re = /^\s*(\d*)(\.\d+)?\s*$/; + var mR, mG, mB; + if ( + params.length >= 3 && + (mR = params[0].match(re)) && + (mG = params[1].match(re)) && + (mB = params[2].match(re)) + ) { + var r = parseFloat((mR[1] || '0') + (mR[2] || '')); + var g = parseFloat((mG[1] || '0') + (mG[2] || '')); + var b = parseFloat((mB[1] || '0') + (mB[2] || '')); + this.fromRGB(r, g, b, flags); + return true; + } + } + return false; + }; + + + this.toString = function () { + return ( + (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) + + (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) + + (0x100 | Math.round(this.rgb[2])).toString(16).substr(1) + ); + }; + + + this.toHEXString = function () { + return '#' + this.toString().toUpperCase(); + }; + + + this.toRGBString = function () { + return ('rgb(' + + Math.round(this.rgb[0]) + ',' + + Math.round(this.rgb[1]) + ',' + + Math.round(this.rgb[2]) + ')' + ); + }; + + + this.isLight = function () { + return ( + 0.213 * this.rgb[0] + + 0.715 * this.rgb[1] + + 0.072 * this.rgb[2] > + 255 / 2 + ); + }; + + + this._processParentElementsInDOM = function () { + if (this._linkedElementsProcessed) { return; } + this._linkedElementsProcessed = true; + + var elm = this.targetElement; + do { + // If the target element or one of its parent nodes has fixed position, + // then use fixed positioning instead + // + // Note: In Firefox, getComputedStyle returns null in a hidden iframe, + // that's why we need to check if the returned style object is non-empty + var currStyle = jsc.getStyle(elm); + if (currStyle && currStyle.position.toLowerCase() === 'fixed') { + this.fixed = true; + } + + if (elm !== this.targetElement) { + // Ensure to attach onParentScroll only once to each parent element + // (multiple targetElements can share the same parent nodes) + // + // Note: It's not just offsetParents that can be scrollable, + // that's why we loop through all parent nodes + if (!elm._jscEventsAttached) { + jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); + elm._jscEventsAttached = true; + } + } + } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body')); + }; + + + // r: 0-255 + // g: 0-255 + // b: 0-255 + // + // returns: [ 0-360, 0-100, 0-100 ] + // + function RGB_HSV (r, g, b) { + r /= 255; + g /= 255; + b /= 255; + var n = Math.min(Math.min(r,g),b); + var v = Math.max(Math.max(r,g),b); + var m = v - n; + if (m === 0) { return [ null, 0, 100 * v ]; } + var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); + return [ + 60 * (h===6?0:h), + 100 * (m/v), + 100 * v + ]; + } + + + // h: 0-360 + // s: 0-100 + // v: 0-100 + // + // returns: [ 0-255, 0-255, 0-255 ] + // + function HSV_RGB (h, s, v) { + var u = 255 * (v / 100); + + if (h === null) { + return [ u, u, u ]; + } + + h /= 60; + s /= 100; + + var i = Math.floor(h); + var f = i%2 ? h-i : 1-(h-i); + var m = u * (1 - s); + var n = u * (1 - s * f); + switch (i) { + case 6: + case 0: return [u,n,m]; + case 1: return [n,u,m]; + case 2: return [m,u,n]; + case 3: return [m,n,u]; + case 4: return [n,m,u]; + case 5: return [u,m,n]; + } + } + + + function detachPicker () { + jsc.unsetClass(THIS.targetElement, THIS.activeClass); + jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); + delete jsc.picker.owner; + } + + + function drawPicker () { + + // At this point, when drawing the picker, we know what the parent elements are + // and we can do all related DOM operations, such as registering events on them + // or checking their positioning + THIS._processParentElementsInDOM(); + + if (!jsc.picker) { + jsc.picker = { + owner: null, + wrap : document.createElement('div'), + box : document.createElement('div'), + boxS : document.createElement('div'), // shadow area + boxB : document.createElement('div'), // border + pad : document.createElement('div'), + padB : document.createElement('div'), // border + padM : document.createElement('div'), // mouse/touch area + padPal : jsc.createPalette(), + cross : document.createElement('div'), + crossBY : document.createElement('div'), // border Y + crossBX : document.createElement('div'), // border X + crossLY : document.createElement('div'), // line Y + crossLX : document.createElement('div'), // line X + sld : document.createElement('div'), + sldB : document.createElement('div'), // border + sldM : document.createElement('div'), // mouse/touch area + sldGrad : jsc.createSliderGradient(), + sldPtrS : document.createElement('div'), // slider pointer spacer + sldPtrIB : document.createElement('div'), // slider pointer inner border + sldPtrMB : document.createElement('div'), // slider pointer middle border + sldPtrOB : document.createElement('div'), // slider pointer outer border + btn : document.createElement('div'), + btnT : document.createElement('span') // text + }; + + jsc.picker.pad.appendChild(jsc.picker.padPal.elm); + jsc.picker.padB.appendChild(jsc.picker.pad); + jsc.picker.cross.appendChild(jsc.picker.crossBY); + jsc.picker.cross.appendChild(jsc.picker.crossBX); + jsc.picker.cross.appendChild(jsc.picker.crossLY); + jsc.picker.cross.appendChild(jsc.picker.crossLX); + jsc.picker.padB.appendChild(jsc.picker.cross); + jsc.picker.box.appendChild(jsc.picker.padB); + jsc.picker.box.appendChild(jsc.picker.padM); + + jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm); + jsc.picker.sldB.appendChild(jsc.picker.sld); + jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB); + jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB); + jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB); + jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS); + jsc.picker.box.appendChild(jsc.picker.sldB); + jsc.picker.box.appendChild(jsc.picker.sldM); + + jsc.picker.btn.appendChild(jsc.picker.btnT); + jsc.picker.box.appendChild(jsc.picker.btn); + + jsc.picker.boxB.appendChild(jsc.picker.box); + jsc.picker.wrap.appendChild(jsc.picker.boxS); + jsc.picker.wrap.appendChild(jsc.picker.boxB); + } + + var p = jsc.picker; + + var displaySlider = !!jsc.getSliderComponent(THIS); + var dims = jsc.getPickerDims(THIS); + var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); + var padToSliderPadding = jsc.getPadToSliderPadding(THIS); + var borderRadius = Math.min( + THIS.borderRadius, + Math.round(THIS.padding * Math.PI)); // px + var padCursor = 'crosshair'; + + // wrap + p.wrap.style.clear = 'both'; + p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px'; + p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px'; + p.wrap.style.zIndex = THIS.zIndex; + + // picker + p.box.style.width = dims[0] + 'px'; + p.box.style.height = dims[1] + 'px'; + + p.boxS.style.position = 'absolute'; + p.boxS.style.left = '0'; + p.boxS.style.top = '0'; + p.boxS.style.width = '100%'; + p.boxS.style.height = '100%'; + jsc.setBorderRadius(p.boxS, borderRadius + 'px'); + + // picker border + p.boxB.style.position = 'relative'; + p.boxB.style.border = THIS.borderWidth + 'px solid'; + p.boxB.style.borderColor = THIS.borderColor; + p.boxB.style.background = THIS.backgroundColor; + jsc.setBorderRadius(p.boxB, borderRadius + 'px'); + + // IE hack: + // If the element is transparent, IE will trigger the event on the elements under it, + // e.g. on Canvas or on elements with border + p.padM.style.background = + p.sldM.style.background = + '#FFF'; + jsc.setStyle(p.padM, 'opacity', '0'); + jsc.setStyle(p.sldM, 'opacity', '0'); + + // pad + p.pad.style.position = 'relative'; + p.pad.style.width = THIS.width + 'px'; + p.pad.style.height = THIS.height + 'px'; + + // pad palettes (HSV and HVS) + p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS)); + + // pad border + p.padB.style.position = 'absolute'; + p.padB.style.left = THIS.padding + 'px'; + p.padB.style.top = THIS.padding + 'px'; + p.padB.style.border = THIS.insetWidth + 'px solid'; + p.padB.style.borderColor = THIS.insetColor; + + // pad mouse area + p.padM._jscInstance = THIS; + p.padM._jscControlName = 'pad'; + p.padM.style.position = 'absolute'; + p.padM.style.left = '0'; + p.padM.style.top = '0'; + p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px'; + p.padM.style.height = dims[1] + 'px'; + p.padM.style.cursor = padCursor; + + // pad cross + p.cross.style.position = 'absolute'; + p.cross.style.left = + p.cross.style.top = + '0'; + p.cross.style.width = + p.cross.style.height = + crossOuterSize + 'px'; + + // pad cross border Y and X + p.crossBY.style.position = + p.crossBX.style.position = + 'absolute'; + p.crossBY.style.background = + p.crossBX.style.background = + THIS.pointerBorderColor; + p.crossBY.style.width = + p.crossBX.style.height = + (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; + p.crossBY.style.height = + p.crossBX.style.width = + crossOuterSize + 'px'; + p.crossBY.style.left = + p.crossBX.style.top = + (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px'; + p.crossBY.style.top = + p.crossBX.style.left = + '0'; + + // pad cross line Y and X + p.crossLY.style.position = + p.crossLX.style.position = + 'absolute'; + p.crossLY.style.background = + p.crossLX.style.background = + THIS.pointerColor; + p.crossLY.style.height = + p.crossLX.style.width = + (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px'; + p.crossLY.style.width = + p.crossLX.style.height = + THIS.pointerThickness + 'px'; + p.crossLY.style.left = + p.crossLX.style.top = + (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px'; + p.crossLY.style.top = + p.crossLX.style.left = + THIS.pointerBorderWidth + 'px'; + + // slider + p.sld.style.overflow = 'hidden'; + p.sld.style.width = THIS.sliderSize + 'px'; + p.sld.style.height = THIS.height + 'px'; + + // slider gradient + p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000'); + + // slider border + p.sldB.style.display = displaySlider ? 'block' : 'none'; + p.sldB.style.position = 'absolute'; + p.sldB.style.right = THIS.padding + 'px'; + p.sldB.style.top = THIS.padding + 'px'; + p.sldB.style.border = THIS.insetWidth + 'px solid'; + p.sldB.style.borderColor = THIS.insetColor; + + // slider mouse area + p.sldM._jscInstance = THIS; + p.sldM._jscControlName = 'sld'; + p.sldM.style.display = displaySlider ? 'block' : 'none'; + p.sldM.style.position = 'absolute'; + p.sldM.style.right = '0'; + p.sldM.style.top = '0'; + p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px'; + p.sldM.style.height = dims[1] + 'px'; + p.sldM.style.cursor = 'default'; + + // slider pointer inner and outer border + p.sldPtrIB.style.border = + p.sldPtrOB.style.border = + THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; + + // slider pointer outer border + p.sldPtrOB.style.position = 'absolute'; + p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; + p.sldPtrOB.style.top = '0'; + + // slider pointer middle border + p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; + + // slider pointer spacer + p.sldPtrS.style.width = THIS.sliderSize + 'px'; + p.sldPtrS.style.height = sliderPtrSpace + 'px'; + + // the Close button + function setBtnBorder () { + var insetColors = THIS.insetColor.split(/\s+/); + var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; + p.btn.style.borderColor = outsetColor; + } + p.btn.style.display = THIS.closable ? 'block' : 'none'; + p.btn.style.position = 'absolute'; + p.btn.style.left = THIS.padding + 'px'; + p.btn.style.bottom = THIS.padding + 'px'; + p.btn.style.padding = '0 15px'; + p.btn.style.height = THIS.buttonHeight + 'px'; + p.btn.style.border = THIS.insetWidth + 'px solid'; + setBtnBorder(); + p.btn.style.color = THIS.buttonColor; + p.btn.style.font = '12px sans-serif'; + p.btn.style.textAlign = 'center'; + try { + p.btn.style.cursor = 'pointer'; + } catch(eOldIE) { + p.btn.style.cursor = 'hand'; + } + p.btn.onmousedown = function () { + THIS.hide(); + }; + p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; + p.btnT.innerHTML = ''; + p.btnT.appendChild(document.createTextNode(THIS.closeText)); + + // place pointers + redrawPad(); + redrawSld(); + + // If we are changing the owner without first closing the picker, + // make sure to first deal with the old owner + if (jsc.picker.owner && jsc.picker.owner !== THIS) { + jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass); + } + + // Set the new picker owner + jsc.picker.owner = THIS; + + // The redrawPosition() method needs picker.owner to be set, that's why we call it here, + // after setting the owner + if (jsc.isElementType(container, 'body')) { + jsc.redrawPosition(); + } else { + jsc._drawPosition(THIS, 0, 0, 'relative', false); + } + + if (p.wrap.parentNode != container) { + container.appendChild(p.wrap); + } + + jsc.setClass(THIS.targetElement, THIS.activeClass); + } + + + function redrawPad () { + // redraw the pad pointer + switch (jsc.getPadYComponent(THIS)) { + case 's': var yComponent = 1; break; + case 'v': var yComponent = 2; break; + } + var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1)); + var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); + var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); + var ofs = -Math.floor(crossOuterSize / 2); + jsc.picker.cross.style.left = (x + ofs) + 'px'; + jsc.picker.cross.style.top = (y + ofs) + 'px'; + + // redraw the slider + switch (jsc.getSliderComponent(THIS)) { + case 's': + var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]); + var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]); + var color1 = 'rgb(' + + Math.round(rgb1[0]) + ',' + + Math.round(rgb1[1]) + ',' + + Math.round(rgb1[2]) + ')'; + var color2 = 'rgb(' + + Math.round(rgb2[0]) + ',' + + Math.round(rgb2[1]) + ',' + + Math.round(rgb2[2]) + ')'; + jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); + break; + case 'v': + var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100); + var color1 = 'rgb(' + + Math.round(rgb[0]) + ',' + + Math.round(rgb[1]) + ',' + + Math.round(rgb[2]) + ')'; + var color2 = '#000'; + jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); + break; + } + } + + + function redrawSld () { + var sldComponent = jsc.getSliderComponent(THIS); + if (sldComponent) { + // redraw the slider pointer + switch (sldComponent) { + case 's': var yComponent = 1; break; + case 'v': var yComponent = 2; break; + } + var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); + jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px'; + } + } + + + function isPickerOwner () { + return jsc.picker && jsc.picker.owner === THIS; + } + + + function blurValue () { + THIS.importColor(); + } + + + // Find the target element + if (typeof targetElement === 'string') { + var id = targetElement; + var elm = document.getElementById(id); + if (elm) { + this.targetElement = elm; + } else { + jsc.warn('Could not find target element with ID \'' + id + '\''); + } + } else if (targetElement) { + this.targetElement = targetElement; + } else { + jsc.warn('Invalid target element: \'' + targetElement + '\''); + } + + if (this.targetElement._jscLinkedInstance) { + jsc.warn('Cannot link jscolor twice to the same element. Skipping.'); + return; + } + this.targetElement._jscLinkedInstance = this; + + // Find the value element + this.valueElement = jsc.fetchElement(this.valueElement); + // Find the style element + this.styleElement = jsc.fetchElement(this.styleElement); + + var THIS = this; + var container = + this.container ? + jsc.fetchElement(this.container) : + document.getElementsByTagName('body')[0]; + var sliderPtrSpace = 3; // px + + // For BUTTON elements it's important to stop them from sending the form when clicked + // (e.g. in Safari) + if (jsc.isElementType(this.targetElement, 'button')) { + if (this.targetElement.onclick) { + var origCallback = this.targetElement.onclick; + this.targetElement.onclick = function (evt) { + origCallback.call(this, evt); + return false; + }; + } else { + this.targetElement.onclick = function () { return false; }; + } + } + + /* + var elm = this.targetElement; + do { + // If the target element or one of its offsetParents has fixed position, + // then use fixed positioning instead + // + // Note: In Firefox, getComputedStyle returns null in a hidden iframe, + // that's why we need to check if the returned style object is non-empty + var currStyle = jsc.getStyle(elm); + if (currStyle && currStyle.position.toLowerCase() === 'fixed') { + this.fixed = true; + } + + if (elm !== this.targetElement) { + // attach onParentScroll so that we can recompute the picker position + // when one of the offsetParents is scrolled + if (!elm._jscEventsAttached) { + jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); + elm._jscEventsAttached = true; + } + } + } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body')); + */ + + // valueElement + if (this.valueElement) { + if (jsc.isElementType(this.valueElement, 'input')) { + var updateField = function () { + THIS.fromString(THIS.valueElement.value, jsc.leaveValue); + jsc.dispatchFineChange(THIS); + }; + jsc.attachEvent(this.valueElement, 'keyup', updateField); + jsc.attachEvent(this.valueElement, 'input', updateField); + jsc.attachEvent(this.valueElement, 'blur', blurValue); + this.valueElement.setAttribute('autocomplete', 'off'); + } + } + + // styleElement + if (this.styleElement) { + this.styleElement._jscOrigStyle = { + backgroundImage : this.styleElement.style.backgroundImage, + backgroundColor : this.styleElement.style.backgroundColor, + color : this.styleElement.style.color + }; + } + + if (this.value) { + // Try to set the color from the .value option and if unsuccessful, + // export the current color + this.fromString(this.value) || this.exportColor(); + } else { + this.importColor(); + } + } + +}; + + +//================================ +// Public properties and methods +//================================ + + +// By default, search for all elements with class="jscolor" and install a color picker on them. +// +// You can change what class name will be looked for by setting the property jscolor.lookupClass +// anywhere in your HTML document. To completely disable the automatic lookup, set it to null. +// +jsc.jscolor.lookupClass = 'jscolor'; + + +jsc.jscolor.installByClassName = function (className) { + var inputElms = document.getElementsByTagName('input'); + var buttonElms = document.getElementsByTagName('button'); + + jsc.tryInstallOnElements(inputElms, className); + jsc.tryInstallOnElements(buttonElms, className); +}; + + +jsc.register(); + + +return jsc.jscolor; + + +})(); } diff --git a/packages/rocketchat-theme/package.js b/packages/rocketchat-theme/package.js index 594e04f00f3b2ce866072c4b5e7531724bc8ac2d..7bd0066eb036b8073196c6c7e5696b6f54b36df5 100644 --- a/packages/rocketchat-theme/package.js +++ b/packages/rocketchat-theme/package.js @@ -11,33 +11,33 @@ Package.onUse(function(api) { api.use('rocketchat:assets'); api.use('coffeescript'); api.use('ecmascript'); + api.use('less'); api.use('underscore'); api.use('webapp'); api.use('webapp-hashing'); - api.use('templating', 'client'); - + // Server side files api.addFiles('server/server.coffee', 'server'); api.addFiles('server/variables.coffee', 'server'); - api.addFiles('client/minicolors/jquery.minicolors.css', 'client'); - api.addFiles('client/minicolors/jquery.minicolors.js', 'client'); + // Colorpicker + api.addFiles('client/vendor/jscolor.js', 'client'); + + // Fontello + api.addFiles('client/vendor/fontello/css/fontello.css', 'client'); + api.addAssets('client/vendor/fontello/font/fontello.eot', 'client'); + api.addAssets('client/vendor/fontello/font/fontello.svg', 'client'); + api.addAssets('client/vendor/fontello/font/fontello.ttf', 'client'); + api.addAssets('client/vendor/fontello/font/fontello.woff', 'client'); + api.addAssets('client/vendor/fontello/font/fontello.woff2', 'client'); + // Compiled stylesheets + api.addFiles('client/main.less', 'client'); - api.addAssets('assets/stylesheets/global/_variables.less', 'server'); - api.addAssets('assets/stylesheets/utils/_mixins.import.less', 'server'); - api.addAssets('assets/stylesheets/utils/_colors.import.less', 'server'); - api.addAssets('assets/stylesheets/utils/_keyframes.import.less', 'server'); - api.addAssets('assets/stylesheets/utils/_lesshat.import.less', 'server'); - api.addAssets('assets/stylesheets/utils/_preloader.import.less', 'server'); - api.addAssets('assets/stylesheets/utils/_reset.import.less', 'server'); - api.addAssets('assets/stylesheets/utils/_chatops.less', 'server'); - api.addAssets('assets/stylesheets/animation.css', 'server'); - api.addAssets('assets/stylesheets/base.less', 'server'); - api.addAssets('assets/stylesheets/fontello.css', 'server'); - api.addAssets('assets/stylesheets/rtl.less', 'server'); - api.addAssets('assets/stylesheets/swipebox.min.css', 'server'); + // Run-time stylesheets + api.addAssets('server/lesshat.less', 'server'); + api.addAssets('server/colors.less', 'server'); }); Npm.depends({ diff --git a/packages/rocketchat-theme/server/colors.less b/packages/rocketchat-theme/server/colors.less new file mode 100755 index 0000000000000000000000000000000000000000..737a4c432849bb87c0c2fb066d1b6a65aefd6c27 --- /dev/null +++ b/packages/rocketchat-theme/server/colors.less @@ -0,0 +1,776 @@ +/** ---------------------------------------------------------------------------- + * Derivative colours (fixed variants of inherited variables) + */ +@default-action-color: darken(@secondary-background-color, 15%); +@default-action-contrast: contrast(@default-action-color, #444444); +@primary-background-contrast: contrast(@primary-background-color, #444444); +@primary-action-contrast: contrast(@primary-action-color, #444444); +@secondary-background-contrast: contrast(@secondary-background-color, #444444); +@secondary-action-contrast: contrast(@secondary-action-color, #444444); +@selection-background: lighten(@selection-color, 30%); +@success-background: lighten(@success-color, 45%); +@success-border: lighten(@success-color, 30%); +@error-background: lighten(@error-color, 45%); +@error-border: lighten(@error-color, 30%); +@error-contrast: contrast(@error-color); +@pending-background: lighten(@pending-color, 45%); +@pending-border: lighten(@pending-color, 30%); + +/** ---------------------------------------------------------------------------- + * Transparency variables + */ + +@transparent-darkest: rgba(0, 0, 0, 0.5); +@transparent-darker: rgba(0, 0, 0, 0.15); +@transparent-dark: rgba(0, 0, 0, 0.05); +@transparent-light: rgba(255, 255, 255, 0.1); +@transparent-lighter: rgba(255, 255, 255, 0.3); +@transparent-lightest: rgba(255, 255, 255, 0.6); + +/** ---------------------------------------------------------------------------- + * Classes for variables + */ + +// Major colors + +.content-background-color { + background-color: @content-background-color; +} + +.color-content-background-color { + color: @content-background-color; +} + +.primary-background-color { + background-color: @primary-background-color; +} + +.global-font-family { + font-family: @body-font-family; +} + +.color-primary-font-color { + color: @primary-font-color; +} + +.color-primary-action-color { + color: @primary-action-color; +} + +.background-primary-action-color { + background-color: @primary-action-color; +} + +.secondary-background-color { + background-color: @secondary-background-color; +} + +.border-secondary-background-color { + border-color: @secondary-background-color; +} + +.secondary-font-color { + color: @secondary-font-color; +} + +.border-component-color { + border-color: @component-color; +} + +.background-component-color { + background-color: @component-color; +} + +.success-color { + color: @success-color; +} + +.pending-color { + color: @pending-color; +} + +.pending-background { + background-color: @pending-background; +} + +.pending-border { + border-color: @pending-border; +} + +.error-color { + color: @error-color; +} + +.background-error-color { + background-color: @error-color; +} + +.color-info-font-color { + color: @info-font-color; +} + +.background-info-font-color { + background-color: @info-font-color; +} + +.background-attention-color { + background-color: @attention-color; +} + +// Minor Colors + +.tertiary-background-color { + background-color: @tertiary-background-color; +} + +.border-tertiary-background-color { + border-color: @tertiary-background-color; +} + +// Derivative Colors + +.color-tertiary-font-color { + color: @tertiary-font-color; +} + +.error-background { + background-color: @error-background; +} + +.error-border { + border-color: @error-border; +} + +.color-error-contrast { + color: @error-contrast; +} + +// transparent + +.background-transparent-darkest { + background-color: @transparent-darkest; +} + +.background-transparent-darker { + background-color: @transparent-darker; +} + +.background-transparent-darker-hover:hover { + background-color: @transparent-darker; +} + +.background-transparent-darker-before::before { + background-color: @transparent-darker; +} + +.background-transparent-dark { + background-color: @transparent-dark; +} + +.background-transparent-dark-hover:hover { + background-color: @transparent-dark; +} + +.border-transparent-dark { + border-color: @transparent-dark; +} + +.background-transparent-light { + background-color: @transparent-light; +} + +.background-transparent-lightest { + background-color: @transparent-lightest; +} + +// Derivative Colors +.color-primary-action-contrast { + color: @primary-action-contrast; +} + +/** ---------------------------------------------------------------------------- + * Special components + */ + +* { + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { + height: 8px; + width: 8px; + background: @transparent-dark; + } + + &::-webkit-scrollbar-thumb { + background-color: @custom-scrollbar-color; + -webkit-border-radius: 50px; + } + + &::-webkit-scrollbar-corner { + background-color: @transparent-dark; + } +} + +.filter-item { + &:hover { + border-color: @info-font-color; + } + + &.active { + border-color: @primary-background-color; + } +} + +/** ---------------------------------------------------------------------------- + * Document components + */ + +.page-container { + tr:hover td { + background-color: @content-background-color; + } +} + +.burger i { + background-color: @primary-font-color; +} + +/** ---------------------------------------------------------------------------- + * Forms + */ + +.input-shade(@primary-font-color, @content-background-color); + +.flex-nav { + .input-shade(@primary-background-contrast, @primary-background-color); + + input { + &:focus { + border-color: fade(@primary-background-contrast, 50%); + } + } + + .input.checkbox.toggle { + input:checked + label::before { + background-color: @primary-action-color; + } + } +} + +.input-line { + &.setting-changed > label { + color: @selection-color; + } +} + +input:-webkit-autofill { + color: @primary-font-color !important; + background-color: transparent !important; +} + +.input { + &.radio { + label { + &::before { + border-color: lighten(@secondary-background-contrast, 30%); + background-color: @content-background-color; + } + + &::after { + background-color: @secondary-background-contrast; + } + } + } + + &.checkbox.toggle { + input:checked + label::before { + background-color: @secondary-background-contrast; + } + + input:disabled + label::before { + background-color: lighten(@secondary-background-contrast, 50%) !important; + } + + label { + &::before { + background-color: lighten(@secondary-background-contrast, 30%); + } + + &::after { + background-color: @content-background-color; + } + + &:hover { + &::before { + background-color: lighten(@secondary-background-contrast, 20%); + } + } + } + } +} + +/** ---------------------------------------------------------------------------- + * Misc typography variants + */ + +a:active, +a:hover { + color: @primary-action-color; +} + +.message, +.flex-tab { + a i, + a[class^="icon-"] { + color: @primary-font-color; + + &:hover { + color: darken(@primary-font-color, 10%); + } + } +} + +.error { + border-color: @error-color; +} + +/** ---------------------------------------------------------------------------- + * Admin and settings styles + */ + +.page-list, +.page-settings { + a { + color: @primary-font-color; + + &:hover { + color: @primary-action-color; + } + } +} + +.admin-table-row { + background-color: @transparent-light; + + &:nth-of-type(even) { + background-color: @transparent-lightest; + } +} + +.new-logs { + background: @primary-action-contrast; +} + +.avatar-suggestion-item { + .question-mark::before { + color: @secondary-font-color; + } +} + +/** ---------------------------------------------------------------------------- + * Asides (external to main application views) + */ + +.full-page, +.page-loading { + .gradient(shade(@primary-background-color), @primary-background-color); + + a { + color: @tertiary-font-color; + } + + a:hover { + color: @primary-background-contrast; + } +} + +#login-card { + .input-shade(@primary-font-color, @content-background-color); + + .input-text { + input:-webkit-autofill { + -webkit-box-shadow: 0 0 0 20px @content-background-color inset; + } + } +} + +/** ---------------------------------------------------------------------------- + * Room components + */ + +.toggle-favorite { + color: @component-color; +} + +.upload-progress-progress { + background-color: @success-background; +} + +.messages-container { + .edit-room-title { + .linkColors(@secondary-font-color, @primary-font-color); + } + + .footer { + background: @content-background-color; + } +} + +.message-form { + .message-buttons { + .buttonColors(lighten(@primary-font-color, 25%), @secondary-background-color); + + &:hover { + background-color: mix(@secondary-background-color, contrast(@primary-font-color), 20%); + } + } + + .message-form-text { + &.editing { + background-color: lighten(@pending-color, 40%); + } + } +} + +.popup-item { + &.selected { + color: @primary-action-contrast; + background-color: @primary-action-color; + } +} + +.messages-box { + &.selectable .selected { + background-color: @selection-background; + } + + // .editing .body, + // textarea.editing { + // background-color: lighten(@pending-color, 40%); + // } +} + +/** ---------------------------------------------------------------------------- + * Message content + */ +.first-unread { + .body { + &::before { + background: @transparent-darker; + } + + &::after { + color: @primary-font-color; + } + } +} + +.first-unread-opaque { + .body { + &::before { + background: @transparent-dark; + } + } +} + +.message { + &.new-day::before { + background-color: @content-background-color; + } + + &.new-day::after { + border-color: @component-color; + } + + .message-dropdown, + .options-menu { + color: lighten(@primary-font-color, 13%); + + ul li:hover { + background-color: @tertiary-background-color; + } + } + + .is-bot, + .role-tag { + color: contrast(@info-font-color); + } + + a { + .linkColors(@link-font-color, darken(@link-font-color, 10%)); + } + + .mention-link { + &.mention-link-me { + color: @primary-action-contrast; + } + + &.mention-link-all { + color: @primary-action-contrast; + } + } + + .highlight-text { + background-color: @selection-background; + } +} + +/** ---------------------------------------------------------------------------- + * Side nav + */ +.side-nav { + a, + .info { + .linkColors(@tertiary-font-color, @tertiary-font-color); + } + + .arrow::before, + .arrow::after { + background-color: @tertiary-font-color; + } + + .rooms-list { + background-color: lighten(@primary-background-color, 2.5%); + } + + li.active { + background-color: @transparent-light !important; + } + + i.status-offline { + color: @transparent-lighter !important; + } + + i { + color: @transparent-lighter; + } + + .opt i:hover { + color: @transparent-lightest; + } + + .has-alert .name { + color: @primary-background-contrast; + } + + .unread { + background-color: @success-color; + color: contrast(@success-color, #000000, #ffffff, 50%); + } + + .button { + .buttonColors(@tertiary-font-color, mix(@primary-action-color, @primary-background-color)); + } + + .options button { + .buttonColors(@tertiary-font-color, @primary-background-color); + } +} + +/** ---------------------------------------------------------------------------- + * Flex tabs / admin fly-out panels + */ +.flex-tab { + input, + select, + textarea { + &:focus { + border-color: lighten(@secondary-background-contrast, 30%); + } + } + + .content, + .user-view, + .list-view { + background-color: @secondary-background-color; + } + + .message { + &.new-day::before { + background-color: @secondary-background-color; + } + } + + .channel-settings { + .buttons { + .button { + .buttonColors(lighten(@primary-font-color, 25%), @secondary-background-color); + } + } + + .button.edit { + .buttonColors(lighten(@primary-font-color, 25%), @secondary-background-color); + } + + .input.checkbox.toggle { + input:checked + label::before { + background-color: @primary-background-color; + } + } + } +} + +.flex-tab-bar { + .tab-button { + &:hover { + background-color: @secondary-background-color; + } + + &.active { + background-color: @secondary-background-color; + border-right-color: @selection-color; + } + + &.attention { + .blink(@selection-color); + } + } + + .counter { + background: @secondary-font-color; + color: white; + } +} + +/** ---------------------------------------------------------------------------- + * User status / user meta + */ +i.status-online { + color: @status-online; +} + +.account-box .status-online .thumb::after, +.account-box .status.online::after, +.popup-user-status-online, +.status-online::after, +.user-image.status-online .avatar::after { + background-color: @status-online; + border-color: darken(@status-online, 10%); +} + +.account-box .status-offline .thumb::after, +.account-box .status.offline::after { + background-color: @transparent-lighter; +} + +i.status-away { + color: @status-away; +} + +.account-box .status-away .thumb::after, +.account-box .status.away::after, +.popup-user-status-away, +.status-pending::after, +.user-image.status-away .avatar::after { + background-color: @status-away; + border-color: darken(@status-away, 10%); +} + +i.status-busy { + color: @status-busy; +} + +.account-box .status-busy .thumb::after, +.account-box .status.busy::after, +.popup-user-status-busy, +.status-busy::after, +.user-image.status-busy .avatar::after { + background-color: @status-busy; + border-color: darken(@status-busy, 10%); +} + +i.status-offline { + color: @status-offline; +} + +.popup-user-status-offline, +.status-offline::after, +.user-image.status-offline .avatar::after { + background-color: @status-offline; + border-color: darken(@status-offline, 10%); +} + +// .popup-user-status-system { +// border-color: transparent; +// } + +// .user-view { +// .box::after, +// .stats li, +// .tags li { +// background-color: @component-color; +// } +// } + +/** ---------------------------------------------------------------------------- + * Buttons! + */ +.actionLinks li .action-link { + .buttonColors(@primary-action-contrast, @primary-action-color); +} + +// new layout buttons + +.button { + .buttonColors(@default-action-contrast, @default-action-color); + + &.primary { + .buttonColors(@primary-action-contrast, @primary-action-color); + + &[disabled] { + background-color: lighten(desaturate(@primary-action-color, 50%), 30%); + } + } + + &.secondary { + .buttonColors(@secondary-action-contrast, @secondary-action-color); + + &[disabled] { + background-color: lighten(desaturate(@secondary-action-color, 50%), 30%); + } + } + + &.tertiary { + .buttonColors(@primary-action-contrast, @selection-color); + + &[disabled] { + background-color: lighten(desaturate(@selection-color, 50%), 30%); + } + } + + &.danger { + .buttonColors(@error-contrast, @error-color); + + &[disabled] { + background-color: lighten(desaturate(@error-color, 50%), 30%); + } + } +} + +/** ---------------------------------------------------------------------------- + * Feedback and overlay content + */ + +.alert-warning { + color: darken(@pending-color, 25%); + background-color: @pending-background; +} + +.alert-link { + .linkColors(@link-font-color, darken(@link-font-color, 10%)); +} + +label.required::after { + color: @error-color; +} + +/** ---------------------------------------------------------------------------- + * Loading + */ + +.main-content, +.flex-tab { + .loading-animation > div { + background-color: @primary-font-color; + } +} diff --git a/packages/rocketchat-theme/server/lesshat.less b/packages/rocketchat-theme/server/lesshat.less new file mode 100644 index 0000000000000000000000000000000000000000..cf7cf4758d6f9deee5601a25c46ae3ab82b2e38f --- /dev/null +++ b/packages/rocketchat-theme/server/lesshat.less @@ -0,0 +1,101 @@ +.calc(...) { + @process: ~`(function(a){function c(c,t){var r=");\n",s=e.split(","),l=s[0]+":"+c+"("+(s[1].trim()||0)+r;"start"==t?a="0;\n"+l:a+=l}a=a||8121991;var t="@{state}",e=a;if(8121991==a)return a;switch(t){case"1":c("-webkit-calc","start"),c("-moz-calc"),c("calc");break;case"2":c("-webkit-calc","start"),c("-moz-calc");break;case"3":c("-webkit-calc","start"),c("calc");break;case"4":c("-webkit-calc","start");break;case"5":c("-moz-calc","start"),c("calc");break;case"6":c("-moz-calc","start");break;case"7":c("calc","start")}return a=a.replace(/;$/g,"")})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + @state: 1; -lh-property: @process; +} + +.transform(...) { + @process: ~`(function(e){e=e||"none";var r={translate:"px",rotate:"deg",rotate3d:"deg",skew:"deg"};/^\w*\(?[a-z0-9.]*\)?/.test(e)&&(e=e.replace(/(?:,)(?![^(]*\))/g,""));for(var t in r)e.indexOf(t)>=0&&(e=e.replace(new RegExp(t+"[\\w]?\\([a-z0-9, %]*\\)"),function(e){var n=/(\d+\.?\d*)(?!\w|%)/g;return"rotate3d"==t&&(n=/,\s*\d+$/),e.replace(n,function(e){return e+r[t]})}));return e})((function(){var r="@{arguments}";return r=r.replace(/^\[|\]$/g,"")})())`; + -webkit-transform: @process; + -moz-transform: @process; + -ms-transform: @process; + -o-transform: @process; + transform: @process; +} + +.gradient(@startColor: #eee, @endColor: white) { + background: linear-gradient(to top, @startColor, @endColor); +} + +.input-shade(@color, @bg) { + input, + select, + textarea { + color: @color; + background-color: transparent; + border-color: mix(contrast(@bg), @bg, 10%); + border-style: solid; + + &::placeholder { + color: mix(@color, @bg, 75%); + } + + &[disabled] { + background-color: mix(contrast(@bg), @bg, 10%); + } + } + + .diabled label, + [disabled] label { + color: mix(@color, @bg, 75%); + } + + .-autocomplete-container { + background-color: mix(contrast(@bg), @bg, 10%); + } + + .-autocomplete-item.selected { + background-color: mix(contrast(@bg), @bg, 20%); + } + + input[type="button"], + input[type="submit"] { + color: @color; + background: mix(contrast(@bg), @bg, 10%); + border-color: mix(contrast(@bg), @bg, 10%); + } +} + +.blink(@color) { + animation-duration: 1000ms; + animation-name: blink; + animation-iteration-count: infinite; + animation-direction: alternate; + + @keyframes blink { + from { + color: @color; + } + + to { + opacity: inherit; + } + } + + @-webkit-keyframes blink { + from { + color: @color; + } + + to { + color: inherit; + } + } +} + +.linkColors(@color, @hover) { + color: @color; + + &:hover { + color: @hover; + } +} + +.buttonColors(@color, @bg) { + color: @color; + background-color: @bg; + + &:hover { + color: mix(@color, contrast(@bg), 60%); + background-color: mix(@bg, contrast(@color), 60%); + } +} diff --git a/packages/rocketchat-theme/server/server.coffee b/packages/rocketchat-theme/server/server.coffee index b36ade8333a2e5d0da056ec0ebf920a04e067273..44c76c3c3317aedf6103d49d64b80280672d5744 100644 --- a/packages/rocketchat-theme/server/server.coffee +++ b/packages/rocketchat-theme/server/server.coffee @@ -47,19 +47,8 @@ RocketChat.theme = new class variables: {} packageCallbacks: [] files: [ - 'assets/stylesheets/global/_variables.less' - 'assets/stylesheets/utils/_keyframes.import.less' - 'assets/stylesheets/utils/_lesshat.import.less' - 'assets/stylesheets/utils/_preloader.import.less' - 'assets/stylesheets/utils/_reset.import.less' - 'assets/stylesheets/utils/_chatops.less' - 'assets/stylesheets/animation.css' - 'assets/stylesheets/base.less' - 'assets/stylesheets/fontello.css' - 'assets/stylesheets/rtl.less' - 'assets/stylesheets/swipebox.min.css' - 'assets/stylesheets/utils/_mixins.import.less' - 'assets/stylesheets/utils/_colors.import.less' + 'server/lesshat.less' + 'server/colors.less' ] constructor: -> diff --git a/packages/rocketchat-theme/server/variables.coffee b/packages/rocketchat-theme/server/variables.coffee index 75631d4044bab581ed088fe3909580f2831ead62..2608955bfd1be505f9271349713a69805baa143d 100755 --- a/packages/rocketchat-theme/server/variables.coffee +++ b/packages/rocketchat-theme/server/variables.coffee @@ -9,10 +9,12 @@ # New colors, used for shades on solid backgrounds # Defined range of transparencies reduces random colour variances alphaColors= + 'transparent-darkest': 'rgba(0,0,0,0.5)' 'transparent-darker': 'rgba(0,0,0,0.15)' - 'transparent-dark': 'rgba(0,0,0,0.03)' - 'transparent-light': 'rgba(255,255,255,0.60)' - 'transparent-lighter': 'rgba(255,255,255,0.25)' + 'transparent-dark': 'rgba(0,0,0,0.05)' + 'transparent-light': 'rgba(255,255,255,0.10)' + 'transparent-lighter': 'rgba(255,255,255,0.30)' + 'transparent-lightest': 'rgba(255,255,255,0.60)' # Major colors form the core of the scheme # Names changed to reflect usage, comments show pre-refactor names @@ -23,20 +25,21 @@ majorColors= 'primary-action-color': '#13679A' # was action-buttons-color 'secondary-background-color': '#F4F4F4' 'secondary-font-color': '#A0A0A0' - 'secondary-action-color': '#E5E5E5' + 'secondary-action-color': '#DDDDDD' 'component-color': '#EAEAEA' 'success-color': '#1DCE73' 'pending-color': '#FCB316' 'error-color': '#BC2031' - 'selection-color': '#02ACEC' + 'selection-color': '#02ACEC', + 'attention-color': '#9C27B0' # Minor colours implement major colours by default, but can be overruled minorColors= 'tertiary-background-color': '@component-color' - 'tertiary-font-color': '@transparent-light' + 'tertiary-font-color': '@transparent-lightest' 'link-font-color': '@primary-action-color' 'info-font-color': '@secondary-font-color' - 'custom-scrollbar-color': '@transparent-dark' + 'custom-scrollbar-color': '@transparent-darker' 'status-online': '@success-color' 'status-away': '@pending-color' 'status-busy': '@error-color' @@ -45,8 +48,6 @@ minorColors= # Bulk-add settings for color scheme for key, value of majorColors RocketChat.theme.addPublicColor key, value, 'Colors' -for key, value of alphaColors - RocketChat.theme.addPublicColor key, value, 'Colors (alphas)' for key, value of minorColors RocketChat.theme.addPublicColor key, value, 'Colors (minor)', 'expression' diff --git a/packages/rocketchat-tooltip/loadStylesheet.js b/packages/rocketchat-tooltip/loadStylesheet.js deleted file mode 100644 index 7cda63dac9501e4e7896999d9963942c72cecdeb..0000000000000000000000000000000000000000 --- a/packages/rocketchat-tooltip/loadStylesheet.js +++ /dev/null @@ -1,3 +0,0 @@ -RocketChat.theme.addPackageAsset(function() { - return Assets.getText('tooltip.less'); -}); diff --git a/packages/rocketchat-tooltip/package.js b/packages/rocketchat-tooltip/package.js index 747c6da6b7638c204c5dc2a4198c60f562c865c4..857ebbde68a4dc63532c97ef79f807276a6ebabb 100644 --- a/packages/rocketchat-tooltip/package.js +++ b/packages/rocketchat-tooltip/package.js @@ -12,9 +12,9 @@ Package.onUse(function(api) { api.use('rocketchat:lib'); api.use('rocketchat:theme'); api.use('rocketchat:ui-master'); + api.use('less'); - api.addAssets('tooltip.less', 'server'); - api.addFiles('loadStylesheet.js', 'server'); + api.addFiles('tooltip.less', 'client'); api.addFiles('rocketchat-tooltip.html', 'client'); api.addFiles('rocketchat-tooltip.js', 'client'); diff --git a/packages/rocketchat-tooltip/tooltip.less b/packages/rocketchat-tooltip/tooltip.less index 9be601d13553002b26e108b8098c43ece191e596..e79d9a547abd373c3685b3c16a04deccd769a897 100644 --- a/packages/rocketchat-tooltip/tooltip.less +++ b/packages/rocketchat-tooltip/tooltip.less @@ -10,8 +10,7 @@ opacity: 0; max-width: 400px; text-align: center; - - .transition(opacity 0.3s ease); + transition: opacity 0.3s ease; &.show { visibility: visible; @@ -34,7 +33,7 @@ .tooltip-arrow { top: -5px; border-top: none; - border-bottom: 5px solid #000; + border-bottom: 5px solid #000000; } } } diff --git a/packages/rocketchat-ui-account/account/account.html b/packages/rocketchat-ui-account/account/account.html index c7b4d54829bdb7964eac9aca8e45ed501223a56e..537dbcdf8dc2f142935416f634e36815927dc2ef 100644 --- a/packages/rocketchat-ui-account/account/account.html +++ b/packages/rocketchat-ui-account/account/account.html @@ -1,6 +1,6 @@ <template name="account"> <section class="page-container page-home page-static"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "User_Settings"}}</span> diff --git a/packages/rocketchat-ui-account/account/accountFlex.html b/packages/rocketchat-ui-account/account/accountFlex.html index 759372dd0a766e3e7ca1e34e218c79bd321c5d4d..6c3e46ddcd6fdf139b7ffe968edbb215ccdf549e 100644 --- a/packages/rocketchat-ui-account/account/accountFlex.html +++ b/packages/rocketchat-ui-account/account/accountFlex.html @@ -9,13 +9,16 @@ <ul> <li> <a href="{{pathFor 'account' group='preferences'}}" class="account-link">{{_ "Preferences"}}</a> + </li> + <li> {{#if allowUserProfileChange}} <a href="{{pathFor 'account' group='profile'}}" class="account-link">{{_ "Profile"}}</a> {{/if}} + </li> + <li> {{#if allowUserAvatarChange}} <a href="{{pathFor 'changeAvatar'}}" class="account-link">{{_ "Avatar"}}</a> {{/if}} - {{!-- move this to profile --}} </li> </ul> </div> diff --git a/packages/rocketchat-ui-account/account/accountPreferences.html b/packages/rocketchat-ui-account/account/accountPreferences.html index f45a8c40cd4e9d9b4e6af72f194b3ff0aa0a9b58..13d9f8c86b9b5b5e1292b9f51985c109db03a7b8 100644 --- a/packages/rocketchat-ui-account/account/accountPreferences.html +++ b/packages/rocketchat-ui-account/account/accountPreferences.html @@ -1,6 +1,6 @@ <template name="accountPreferences"> <section class="page-container page-home page-static"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Preferences"}}</span> @@ -11,7 +11,7 @@ <fieldset> <div class="section"> <h1>{{_ "Localization"}}</h1> - <div class="section-content"> + <div class="section-content border-component-color"> <div class="input-line"> <label for="language">{{_ "Language"}}</label> <div> @@ -26,18 +26,18 @@ </div> <div class="section"> <h1>{{_ "Messages"}}</h1> - <div class="section-content"> + <div class="section-content border-component-color"> <div class="input-line double-col"> <label>{{_ "Desktop_Notifications"}}</label> <div> {{#if desktopNotificationEnabled}} <label>{{_ "Desktop_Notifications_Enabled"}}</label> - <label><button class="button test-notifications"><i class="icon-comment-empty"></i> <span>{{_ "Test_Desktop_Notifications"}}</span></button></label> + <label><button class="button test-notifications"><i class="icon-comment-empty secondary-font-color"></i> <span>{{_ "Test_Desktop_Notifications"}}</span></button></label> {{else}} {{#if desktopNotificationDisabled}} <label>{{_ "Desktop_Notifications_Disabled"}}</label> {{else}} - <label><button class="button enable-notifications"><i class="icon-comment-empty"></i> <span>{{_ "Enable_Desktop_Notifications"}}</span></button></label> + <label><button class="button enable-notifications"><i class="icon-comment-empty secondary-font-color"></i> <span>{{_ "Enable_Desktop_Notifications"}}</span></button></label> {{/if}} {{/if}} </div> @@ -155,7 +155,7 @@ </div> <div class="section"> <h1>{{_ "Highlights"}}</h1> - <div class="section-content"> + <div class="section-content border-component-color"> <div class="input-line double-col"> <label>{{_ "Highlights_List"}}</label> <div> @@ -167,7 +167,7 @@ </div> <div class="section"> <h1>{{_ "Sound"}}</h1> - <div class="section-content"> + <div class="section-content border-component-color"> <div class="input-line double-col"> <label>{{_ "New_Room_Notification"}}</label> <div> diff --git a/packages/rocketchat-ui-account/account/accountProfile.coffee b/packages/rocketchat-ui-account/account/accountProfile.coffee index 3f191d2b811860165c1a5cf570bd3a03b48b75d2..ec9df5f8fe82b12d2f1371b4b304d35f66256edd 100644 --- a/packages/rocketchat-ui-account/account/accountProfile.coffee +++ b/packages/rocketchat-ui-account/account/accountProfile.coffee @@ -30,6 +30,9 @@ Template.accountProfile.helpers passwordChangeDisabled: -> return t('Password_Change_Disabled') + customFields: -> + return Meteor.user().customFields + Template.accountProfile.onCreated -> settingsTemplate = this.parentTemplate(3) settingsTemplate.child ?= [] @@ -79,7 +82,11 @@ Template.accountProfile.onCreated -> else data.email = _.trim $('#email').val() - Meteor.call 'saveUserProfile', data, (error, results) -> + customFields = {} + $('[data-customfield=true]').each () -> + customFields[this.name] = $(this).val() or '' + + Meteor.call 'saveUserProfile', data, customFields, (error, results) -> if results toastr.remove(); toastr.success t('Profile_saved_successfully') diff --git a/packages/rocketchat-ui-account/account/accountProfile.html b/packages/rocketchat-ui-account/account/accountProfile.html index 77fc7d16491f00ad4f1135bb27ab7d1bdc96d5aa..2d67e2cfba7bf692c58c3e58d1155005eb8ad954 100644 --- a/packages/rocketchat-ui-account/account/accountProfile.html +++ b/packages/rocketchat-ui-account/account/accountProfile.html @@ -1,6 +1,6 @@ <template name="accountProfile"> <section class="page-container page-home page-static"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Profile"}}</span> @@ -30,7 +30,7 @@ <div> {{#if emailVerified}} <div class="right"> - <i class="icon-ok" title="{{_ "Email_verified" }}"></i> + <i class="icon-ok success-color" title="{{_ "Email_verified" }}"></i> </div> {{/if}} {{#if allowEmailChange}} @@ -56,6 +56,9 @@ </div> </div> </fieldset> + <fieldset> + {{> customFieldsForm hideFromForm=false formData=customFields}} + </fieldset> <div class="submit"> <button class="button primary send"><i class="icon-send"></i><span>{{_ "Save_changes"}}</span></button> </div> diff --git a/packages/rocketchat-ui-account/account/avatar/prompt.html b/packages/rocketchat-ui-account/account/avatar/prompt.html index a3291b8c72d49071493985acd93879b42a31fe48..6a238d35ed3a6b0e4fa2a654b14623de1fc256c2 100644 --- a/packages/rocketchat-ui-account/account/avatar/prompt.html +++ b/packages/rocketchat-ui-account/account/avatar/prompt.html @@ -1,7 +1,7 @@ <template name="avatarSuggestion"> {{#if .}} - <div class="avatar-suggestion-item"> - <div class="avatar" style="background-image: url({{blob}});"> + <div class="avatar-suggestion-item border-component-color"> + <div class="avatar tertiary-background-color" style="background-image: url({{blob}});"> </div> <div class="action"> <button type="button" class="button primary {{service}} select-service">{{_ "Use_service_avatar" service}}</button> @@ -12,8 +12,8 @@ <template name="avatarSuggestionLogin"> {{#if .}} - <div class="avatar-suggestion-item"> - <div class="avatar question-mark icon-user"></div> + <div class="avatar-suggestion-item border-component-color"> + <div class="avatar question-mark icon-user tertiary-background-color"></div> <div class="action"> <button type="button" class="button primary {{.}} login-with-service">{{_ "Login_with" .}}</button> </div> @@ -22,8 +22,8 @@ </template> <template name="avatarPrompt"> - <section class="page-container page-home page-static"> - <header class="fixed-title"> + <section class="page-container page-home page-static content-background-color"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Select_an_avatar"}}</span> @@ -31,12 +31,12 @@ </header> <div class="content"> <div class="avatarPrompt"> - <header> + <header class="content-background-color border-component-color"> <p>{{_ "Select_service_to_login"}}</p> </header> <div> <div class="avatar-suggestions"> - <div class="avatar-suggestion-item"> + <div class="avatar-suggestion-item border-component-color"> {{> avatar username=initialsUsername }} {{#with service='initials'}} <div class="action"> @@ -44,8 +44,8 @@ </div> {{/with}} </div> - <div class="avatar-suggestion-item"> - <div style="background-image: url({{upload.blob}});" class="avatar {{#unless upload}}question-mark icon-upload{{/unless}}"> + <div class="avatar-suggestion-item border-component-color"> + <div style="background-image: url({{upload.blob}});" class="avatar tertiary-background-color {{#unless upload}}question-mark icon-upload{{/unless}}"> </div> <div class="action"> <div class="button primary">{{_ "Select_file"}} @@ -56,9 +56,9 @@ {{/with}} </div> </div> - <div class="avatar-suggestion-item"> + <div class="avatar-suggestion-item border-component-color"> {{#with service='url'}} - <div class="avatar question-mark icon-upload"></div> + <div class="avatar question-mark icon-upload tertiary-background-color"></div> <div class="action"> <div class="input-line"> <input type="text" name="avatarurl" id="avatarurl" /> diff --git a/packages/rocketchat-ui-account/styles/account.less b/packages/rocketchat-ui-account/styles/account.less deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/packages/rocketchat-ui-admin/admin/admin.coffee b/packages/rocketchat-ui-admin/admin/admin.coffee index 12a1d056607aecbf4be626da542397137b39ba33..e6ce79cc7f06a2344d82593a13ac7b7da4feb501 100644 --- a/packages/rocketchat-ui-admin/admin/admin.coffee +++ b/packages/rocketchat-ui-admin/admin/admin.coffee @@ -2,11 +2,26 @@ import toastr from 'toastr' TempSettings = new Meteor.Collection null RocketChat.TempSettings = TempSettings -updateColorComponent = -> - $('input.minicolors').minicolors - theme: 'rocketchat' - format: 'hex' - opacity: true +getDefaultSetting = (settingId) -> + return RocketChat.settings.cachedCollectionPrivate.collection.findOne({_id: settingId}) + +setFieldValue = (settingId, value, type, editor) -> + input = $('.page-settings').find('[name="' + settingId + '"]') + + switch type + when 'boolean' + $('.page-settings').find('[name="' + settingId + '"][value="' + Number(value) + '"]').prop('checked', true).change() + when 'code' + input.next()[0].CodeMirror.setValue(value) + when 'color' + input.parents('.horizontal').find('select[name="color-editor"]').val(editor).change() + input.val(value).change() + + if editor is 'color' + new jscolor(input) + + else + input.val(value).change() Template.admin.onCreated -> if not RocketChat.settings.cachedCollectionPrivate? @@ -60,14 +75,10 @@ Template.admin.helpers return selected group: -> - group = FlowRouter.getParam('group') - group ?= TempSettings.findOne({ type: 'group' })?._id - return TempSettings.findOne { _id: group, type: 'group' } + return TempSettings.findOne { _id: FlowRouter.getParam('group'), type: 'group' } sections: -> - group = FlowRouter.getParam('group') - group ?= TempSettings.findOne({ type: 'group' })?._id - settings = TempSettings.find({ group: group }, {sort: {section: 1, sorter: 1, i18nLabel: 1}}).fetch() + settings = TempSettings.find({ group: FlowRouter.getParam('group') }, {sort: {section: 1, sorter: 1, i18nLabel: 1}}).fetch() sections = {} for setting in settings @@ -250,8 +261,12 @@ Template.admin.helpers getColorVariable: (color) -> return color.replace(/theme-color-/, '@') + isDefaultValue: (settingId) -> + setting = TempSettings.findOne({_id: settingId}, {fields: {value: 1, packageValue: 1}}) + return setting.value is setting.packageValue + Template.admin.events - "change .input-monitor": (e, t) -> + "change .input-monitor, keyup .input-monitor": (e, t) -> value = _.trim $(e.target).val() switch @type @@ -271,22 +286,50 @@ Template.admin.events $set: editor: value - Meteor.setTimeout updateColorComponent, 100 - - "click .submit .save": (e, t) -> + "click .submit .discard": -> group = FlowRouter.getParam('group') query = group: group changed: true - if @section is '' - query.$or = [ - {section: ''} - {section: {$exists: false}} - ] + settings = TempSettings.find(query, {fields: {_id: 1, value: 1, packageValue: 1}}).fetch() + console.log(settings) + + settings.forEach (setting) -> + oldSetting = RocketChat.settings.cachedCollectionPrivate.collection.findOne({_id: setting._id}, {fields: {value: 1, type:1, editor: 1}}) + + setFieldValue(setting._id, oldSetting.value, oldSetting.type, oldSetting.editor) + + "click .reset-setting": (e, t) -> + e.preventDefault(); + settingId = $(e.target).data('setting') + if typeof settingId is 'undefined' then settingId = $(e.target).parent().data('setting') + + defaultValue = getDefaultSetting(settingId) + + setFieldValue(settingId, defaultValue.packageValue, defaultValue.type, defaultValue.editor) + + "click .reset-group": (e, t) -> + e.preventDefault(); + group = FlowRouter.getParam('group') + section = $(e.target).data('section') + + if section is "" + settings = TempSettings.find({group: group, section: {$exists: false}}, {fields: {_id: 1}}).fetch() else - query.section = @section + settings = TempSettings.find({group: group, section: section}, {fields: {_id: 1}}).fetch() + + settings.forEach (setting) -> + defaultValue = getDefaultSetting(setting._id) + setFieldValue(setting._id, defaultValue.packageValue, defaultValue.type, defaultValue.editor) + + "click .submit .save": (e, t) -> + group = FlowRouter.getParam('group') + + query = + group: group + changed: true settings = TempSettings.find(query, {fields: {_id: 1, value: 1, editor: 1}}).fetch() @@ -321,6 +364,14 @@ Template.admin.events if err handleError(err) + "click .submit .refresh-oauth": (e, t) -> + toastr.info TAPi18n.__ 'Refreshing' + Meteor.call 'refreshOAuthService', (err) -> + if err + handleError(err) + else + toastr.success TAPi18n.__ 'Done' + "click .submit .remove-custom-oauth": (e, t) -> name = this.section.replace('Custom OAuth: ', '') config = @@ -390,12 +441,12 @@ Template.admin.events "click .button-fullscreen": -> codeMirrorBox = $('.code-mirror-box[data-editor-id="'+this._id+'"]') - codeMirrorBox.addClass('code-mirror-box-fullscreen') + codeMirrorBox.addClass('code-mirror-box-fullscreen content-background-color') codeMirrorBox.find('.CodeMirror')[0].CodeMirror.refresh() "click .button-restore": -> codeMirrorBox = $('.code-mirror-box[data-editor-id="'+this._id+'"]') - codeMirrorBox.removeClass('code-mirror-box-fullscreen') + codeMirrorBox.removeClass('code-mirror-box-fullscreen content-background-color') codeMirrorBox.find('.CodeMirror')[0].CodeMirror.refresh() 'autocompleteselect .autocomplete': (event, instance, doc) -> @@ -427,12 +478,12 @@ Template.admin.onRendered -> SideNav.setFlex "adminFlex" SideNav.openFlex() - Meteor.setTimeout -> - updateColorComponent() - , 1000 - Tracker.autorun -> FlowRouter.watchPathChange() - Meteor.setTimeout -> - updateColorComponent() - , 400 + + hasColor = TempSettings.findOne { group: FlowRouter.getParam('group'), type: 'color' } + if hasColor + Meteor.setTimeout -> + $('.colorpicker-input').each (index, el) -> + new jscolor(el) + , 400 diff --git a/packages/rocketchat-ui-admin/admin/admin.html b/packages/rocketchat-ui-admin/admin/admin.html index fb9af6f0685b38f4ea8c4b847d7ac08a9991d254..d72c5e6b115b7998a35b609d8aeaa6c5da320773 100644 --- a/packages/rocketchat-ui-admin/admin/admin.html +++ b/packages/rocketchat-ui-admin/admin/admin.html @@ -1,13 +1,21 @@ <template name="admin"> <section class="page-container page-home page-static page-settings"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{#with group}}{{label}}{{/with}}</span> </h2> + {{#unless $eq group._id 'Assets'}} + <div class="submit"> + {{#if hasChanges}} + <button class="button danger discard"><i class="icon-send"></i><span>{{_ "Cancel"}}</span></button> + {{/if}} + <button class="button primary save" disabled="{{not hasChanges}}"><i class="icon-send"></i><span>{{_ "Save_changes"}}</span></button> + </div> + {{/unless}} </header> - <div class="content"> + <div class="content background-transparent-dark"> {{#unless hasPermission 'view-privileged-setting'}} <p>{{_ "You_are_not_authorized_to_view_this_page"}}</p> {{else}} @@ -27,11 +35,11 @@ {{translateSection section}} </div> <div class="section-title-right"> - <button class="button secondary expand"><span>{{_ "Expand"}}</span></button> + <button class="button primary expand"><span>{{_ "Expand"}}</span></button> </div> </div> {{/if}} - <div class="section-content"> + <div class="section-content border-component-color"> {{#if section}} {{#if sectionIsCustomOAuth section}} <div class="section-helper"> @@ -43,8 +51,8 @@ {{/if}} {{#each settings}} <div class="input-line double-col {{#if changed}}setting-changed{{/if}}" {{isDisabled}}> - <label>{{label}}</label> - <div> + <label class="setting-label">{{label}}</label> + <div class="setting-field"> {{#if $eq type 'string'}} {{#if multiline}} <textarea class="input-monitor" name="{{_id}}" rows="4" style="height: auto" {{isDisabled}}>{{value}}</textarea> @@ -72,7 +80,7 @@ {{#if $eq type 'select'}} <div class="select-arrow"> - <i class="icon-down-open"></i> + <i class="icon-down-open secondary-font-color"></i> </div> <select class="input-monitor" name="{{_id}}" {{isDisabled}}> {{#each values}} @@ -83,7 +91,7 @@ {{#if $eq type 'language'}} <div class="select-arrow"> - <i class="icon-down-open"></i> + <i class="icon-down-open secondary-font-color"></i> </div> <select class="input-monitor" name="{{_id}}" {{isDisabled}}> {{#each languages}} @@ -96,17 +104,18 @@ <div class="horizontal"> {{#if $eq editor 'color'}} <div class="flex-grow-1"> - <input class="input-monitor minicolors" type="text" name="{{_id}}" value="{{value}}" {{isDisabled}}/> + <input class="input-monitor colorpicker-input" type="text" name="{{_id}}" value="{{value}}" autocomplete="off" {{isDisabled}}/> + <span class="colorpicker-swatch border-component-color" style="background-color: {{value}}"></span> </div> {{/if}} {{#if $eq editor 'expression'}} <div class="flex-grow-1"> - <input class="input-monitor not-minicolors" type="text" name="{{_id}}" value="{{value}}" {{isDisabled}}/> + <input class="input-monitor" type="text" name="{{_id}}" value="{{value}}" {{isDisabled}}/> </div> {{/if}} <div class="color-editor"> <div class="select-arrow"> - <i class="icon-down-open"></i> + <i class="icon-down-open secondary-font-color"></i> </div> <select name="color-editor"> {{#each allowedTypes}} @@ -142,7 +151,7 @@ {{#if $eq type 'action'}} {{#if hasChanges section}} - <span style="line-height: 40px" class="secondary-text">{{_ "Save_to_enable_this_action"}}</span> + <span style="line-height: 40px" class="secondary-font-color">{{_ "Save_to_enable_this_action"}}</span> {{else}} <button type="button" class="button primary action" data-setting="{{_id}}" data-action="{{value}}" {{isDisabled}}>{{_ actionText}}</button> {{/if}} @@ -153,12 +162,12 @@ <div class="settings-file-preview"> <div class="preview" style="background-image:url({{value.url}}?_dc={{random}});"></div> <div class="action"> - <button type="button" class="button danger delete-asset"><i class="icon-trash"></i>{{_ 'Delete'}}</button> + <button type="button" class="button danger delete-asset"><i class="icon-trash secondary-font-color"></i>{{_ 'Delete'}}</button> </div> </div> {{else}} <div class="settings-file-preview"> - <div class="preview no-file"><i class="icon-upload"></i></div> + <div class="preview no-file background-transparent-light secondary-font-color"><i class="icon-upload secondary-font-color"></i></div> <div class="action"> <div class="button primary">{{_ 'Select_file'}} <input type="file" accept="{{assetAccept fileConstraints}}" /> @@ -173,7 +182,7 @@ {{> inputAutocomplete settings=autocompleteRoom id=_id name=_id class="search autocomplete" autocomplete="off" disabled=isDisabled.disabled}} <ul class="selected-rooms"> {{#each selectedRooms}} - <li class="remove-room" data-setting={{../_id}}>{{name}} <i class="icon-cancel"></i></li> + <li class="remove-room" data-setting={{../_id}}>{{name}} <i class="icon-cancel secondary-font-color"></i></li> {{/each}} </ul> </div> @@ -183,12 +192,30 @@ <div class="settings-description">{{{RocketChatMarkdown description}}}</div> {{/if}} {{#if alert}} - <div class="settings-alert"><i class="icon-attention"></i>{{{_ alert}}}</div> + <div class="settings-alert pending-color pending-background pending-border"><i class="icon-attention secondary-font-color"></i>{{{_ alert}}}</div> {{/if}} </div> + {{#unless $eq group._id 'Assets'}} + {{#unless isDefaultValue _id}} + <button text="{{_ 'Reset'}}" data-setting="{{_id}}" class="reset-setting button danger"> + <i class="icon-ccw secondary-font-color color-error-contrast"></i> + </button> + {{/unless}} + {{/unless}} </div> {{/each}} + {{#unless $eq group._id 'Assets'}} + <div class="input-line double-col"> + <label class="setting-label">{{_ "Reset_section_settings"}}</label> + <div class="setting-field"> + <button data-section="{{section}}" class="reset-group button danger"> + {{_ "Reset"}} + </button> + </div> + </div> + {{/unless}} + {{#if section}} {{#if sectionIsCustomOAuth section}} <div class="submit"> @@ -196,18 +223,13 @@ </div> {{/if}} {{/if}} - - {{#if hasChanges section}} - <div class="submit"> - <button class="button primary save"><i class="icon-send"></i><span>{{_ "Save_changes"}}</span></button> - </div> - {{/if}} </div> </div> {{/each}} <div class="submit"> {{#if $eq group._id 'OAuth'}} + <button class="button secondary refresh-oauth"><span>{{_ "Refresh_oauth_services"}}</span></button> <button class="button secondary add-custom-oauth"><span>{{_ "Add_custom_oauth"}}</span></button> {{/if}} {{#if $eq group._id 'Assets'}} diff --git a/packages/rocketchat-ui-admin/admin/adminFlex.coffee b/packages/rocketchat-ui-admin/admin/adminFlex.coffee index 1dface4a6acf3e503fa981538910b428294b0294..9a1a9b485474c0b8e185b739ecdc74b2d62331a3 100644 --- a/packages/rocketchat-ui-admin/admin/adminFlex.coffee +++ b/packages/rocketchat-ui-admin/admin/adminFlex.coffee @@ -1,4 +1,6 @@ Template.adminFlex.onCreated -> + @settingsFilter = new ReactiveVar('') + if not RocketChat.settings.cachedCollectionPrivate? RocketChat.settings.cachedCollectionPrivate = new RocketChat.CachedCollection({ name: 'private-settings', eventType: 'onAll' }) RocketChat.settings.collectionPrivate = RocketChat.settings.cachedCollectionPrivate.collection @@ -7,9 +9,30 @@ Template.adminFlex.onCreated -> Template.adminFlex.helpers groups: -> - return RocketChat.settings.collectionPrivate.find({type: 'group'}, { sort: { sort: 1, i18nLabel: 1 } }).fetch() + filter = Template.instance().settingsFilter.get() + + query = + type: 'group' + + if filter + filterRegex = new RegExp(_.escapeRegExp(filter), 'i') + + records = RocketChat.settings.collectionPrivate.find().fetch() + groups = [] + records = records.forEach (record) -> + if filterRegex.test(TAPi18n.__(record.i18nLabel or record._id)) + groups.push(record.group or record._id) + + groups = _.unique(groups) + if groups.length > 0 + query._id = + $in: groups + + return RocketChat.settings.collectionPrivate.find(query, { sort: { sort: 1, i18nLabel: 1 } }).fetch() + label: -> return TAPi18n.__(@i18nLabel or @_id) + adminBoxOptions: -> return RocketChat.AdminBox.getOptions() @@ -29,3 +52,6 @@ Template.adminFlex.events 'click .admin-link': -> menu.close() + + 'keyup [name=settings-search]': (e, t) -> + t.settingsFilter.set(e.target.value) diff --git a/packages/rocketchat-ui-admin/admin/adminFlex.html b/packages/rocketchat-ui-admin/admin/adminFlex.html index aad3a490ae37595bede65efbce2547f90faa2487..1eb8c60c7f7cb9bf1cd1bb647f55598e0c0a8878 100644 --- a/packages/rocketchat-ui-admin/admin/adminFlex.html +++ b/packages/rocketchat-ui-admin/admin/adminFlex.html @@ -42,6 +42,10 @@ {{_ "Settings"}} </h3> + <li> + <input type="text" name="settings-search" placeholder="{{_ 'Search'}}"> + </li> + {{#each groups}} <li> <a href="{{pathFor 'admin' group=_id}}" class="admin-link">{{label}}</a> diff --git a/packages/rocketchat-ui-admin/admin/adminInfo.html b/packages/rocketchat-ui-admin/admin/adminInfo.html index 2e8d9df21ad764e34f618b890197b50c5a48102f..b116f63ff15414c3b10b6c69b17e3b0be495f1f7 100644 --- a/packages/rocketchat-ui-admin/admin/adminInfo.html +++ b/packages/rocketchat-ui-admin/admin/adminInfo.html @@ -1,6 +1,6 @@ <template name="adminInfo"> <section class="page-container page-list"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Info"}}</span> @@ -9,186 +9,186 @@ <div class="content"> {{#if hasPermission 'view-statistics'}} <h3>{{_ "Rocket.Chat"}}</h3> - <table class="statistics-table"> - <tr> - <th>{{_ "Version"}}</th> - <td>{{statistics.version}}</td> + <table class="statistics-table secondary-background-color"> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Version"}}</th> + <td class="border-component-color">{{statistics.version}}</td> </tr> - <tr> - <th>{{_ "DB_Migration"}}</th> - <td>{{statistics.migration.version}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "DB_Migration"}}</th> + <td class="border-component-color">{{statistics.migration.version}}</td> </tr> - <tr> - <th>{{_ "DB_Migration_Date"}}</th> - <td>{{statistics.migration.lockedAt}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "DB_Migration_Date"}}</th> + <td class="border-component-color">{{statistics.migration.lockedAt}}</td> </tr> - <tr> - <th>{{_ "Installed_at"}}</th> - <td>{{statistics.installedAt}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Installed_at"}}</th> + <td class="border-component-color">{{statistics.installedAt}}</td> </tr> - <tr> - <th>{{_ "Uptime"}}</th> - <td>{{humanReadableTime statistics.process.uptime}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Uptime"}}</th> + <td class="border-component-color">{{humanReadableTime statistics.process.uptime}}</td> </tr> - <tr> - <th>{{_ "Deployment_ID"}}</th> - <td>{{statistics.uniqueId}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Deployment_ID"}}</th> + <td class="border-component-color">{{statistics.uniqueId}}</td> </tr> - <tr> - <th>{{_ "PID"}}</th> - <td>{{statistics.process.pid}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "PID"}}</th> + <td class="border-component-color">{{statistics.process.pid}}</td> </tr> - <tr> - <th>{{_ "Running_Instances"}}</th> - <td>{{statistics.instanceCount}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Running_Instances"}}</th> + <td class="border-component-color">{{statistics.instanceCount}}</td> </tr> </table> {{/if}} <h3>{{_ "Commit"}}</h3> - <table class="statistics-table"> - <tr> - <th>{{_ "Hash"}}</th> - <td>{{info.commit.hash}}</td> + <table class="statistics-table secondary-background-color"> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Hash"}}</th> + <td class="border-component-color">{{info.commit.hash}}</td> </tr> - <tr> - <th>{{_ "Date"}}</th> - <td>{{info.commit.date}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Date"}}</th> + <td class="border-component-color">{{info.commit.date}}</td> </tr> - <tr> - <th>{{_ "Branch"}}</th> - <td>{{info.commit.branch}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Branch"}}</th> + <td class="border-component-color">{{info.commit.branch}}</td> </tr> - <tr> - <th>{{_ "Tag"}}</th> - <td>{{info.commit.tag}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Tag"}}</th> + <td class="border-component-color">{{info.commit.tag}}</td> </tr> - <tr> - <th>{{_ "Author"}}</th> - <td>{{info.commit.author}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Author"}}</th> + <td class="border-component-color">{{info.commit.author}}</td> </tr> - <tr> - <th>{{_ "Subject"}}</th> - <td>{{info.commit.subject}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Subject"}}</th> + <td class="border-component-color">{{info.commit.subject}}</td> </tr> </table> {{#if hasPermission 'view-statistics'}} {{#if isReady}} <h3>{{_ "Runtime_Environment"}}</h3> - <table class="statistics-table"> - <tr> - <th>{{_ "OS_Type"}}</th> - <td>{{statistics.os.type}}</td> + <table class="statistics-table secondary-background-color"> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Type"}}</th> + <td class="border-component-color">{{statistics.os.type}}</td> </tr> - <tr> - <th>{{_ "OS_Platform"}}</th> - <td>{{statistics.os.platform}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Platform"}}</th> + <td class="border-component-color">{{statistics.os.platform}}</td> </tr> - <tr> - <th>{{_ "OS_Arch"}}</th> - <td>{{statistics.os.arch}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Arch"}}</th> + <td class="border-component-color">{{statistics.os.arch}}</td> </tr> - <tr> - <th>{{_ "OS_Release"}}</th> - <td>{{statistics.os.release}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Release"}}</th> + <td class="border-component-color">{{statistics.os.release}}</td> </tr> - <tr> - <th>{{_ "Node_version"}}</th> - <td>{{statistics.process.nodeVersion}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Node_version"}}</th> + <td class="border-component-color">{{statistics.process.nodeVersion}}</td> </tr> - <tr> - <th>{{_ "OS_Uptime"}}</th> - <td>{{humanReadableTime statistics.os.uptime}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Uptime"}}</th> + <td class="border-component-color">{{humanReadableTime statistics.os.uptime}}</td> </tr> - <tr> - <th>{{_ "OS_Loadavg"}}</th> - <td>{{numFormat statistics.os.loadavg.[0]}}, {{numFormat statistics.os.loadavg.[1]}}, {{numFormat statistics.os.loadavg.[2]}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Loadavg"}}</th> + <td class="border-component-color">{{numFormat statistics.os.loadavg.[0]}}, {{numFormat statistics.os.loadavg.[1]}}, {{numFormat statistics.os.loadavg.[2]}}</td> </tr> - <tr> - <th>{{_ "OS_Totalmem"}}</th> - <td>{{inGB statistics.os.totalmem}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Totalmem"}}</th> + <td class="border-component-color">{{inGB statistics.os.totalmem}}</td> </tr> - <tr> - <th>{{_ "OS_Freemem"}}</th> - <td>{{inGB statistics.os.freemem}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Freemem"}}</th> + <td class="border-component-color">{{inGB statistics.os.freemem}}</td> </tr> - <tr> - <th>{{_ "OS_Cpus"}}</th> - <td>{{statistics.os.cpus.length}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Cpus"}}</th> + <td class="border-component-color">{{statistics.os.cpus.length}}</td> </tr> </table> <h3>{{_ "Build_Environment"}}</h3> - <table class="statistics-table"> - <tr> - <th>{{_ "OS_Platform"}}</th> - <td>{{build.platform}}</td> + <table class="statistics-table secondary-background-color"> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Platform"}}</th> + <td class="border-component-color">{{build.platform}}</td> </tr> - <tr> - <th>{{_ "OS_Arch"}}</th> - <td>{{build.arch}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Arch"}}</th> + <td class="border-component-color">{{build.arch}}</td> </tr> - <tr> - <th>{{_ "OS_Release"}}</th> - <td>{{build.osRelease}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "OS_Release"}}</th> + <td class="border-component-color">{{build.osRelease}}</td> </tr> - <tr> - <th>{{_ "Node_version"}}</th> - <td>{{build.nodeVersion}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Node_version"}}</th> + <td class="border-component-color">{{build.nodeVersion}}</td> </tr> - <tr> - <th>{{_ "Date"}}</th> - <td>{{formatDate build.date}}</td> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Date"}}</th> + <td class="border-component-color">{{formatDate build.date}}</td> </tr> </table> <h3>{{_ "Usage"}}</h3> - <table class="statistics-table"> - <tr> - <th>{{_ "Stats_Total_Users"}}</th> - <td>{{statistics.totalUsers}}</td> - </tr> - <tr> - <th>{{_ "Stats_Active_Users"}}</th> - <td>{{statistics.activeUsers}}</td> - </tr> - <tr> - <th>{{_ "Stats_Non_Active_Users"}}</th> - <td>{{statistics.nonActiveUsers}}</td> - </tr> - <tr> - <th>{{_ "Stats_Online_Users"}}</th> - <td>{{statistics.onlineUsers}}</td> - </tr> - <tr> - <th>{{_ "Stats_Away_Users"}}</th> - <td>{{statistics.awayUsers}}</td> - </tr> - <tr> - <th>{{_ "Stats_Offline_Users"}}</th> - <td>{{statistics.offlineUsers}}</td> - </tr> - <tr> - <th>{{_ "Stats_Total_Rooms"}}</th> - <td>{{statistics.totalRooms}}</td> - </tr> - <tr> - <th>{{_ "Stats_Total_Channels"}}</th> - <td>{{statistics.totalChannels}}</td> - </tr> - <tr> - <th>{{_ "Stats_Total_Private_Groups"}}</th> - <td>{{statistics.totalPrivateGroups}}</td> - </tr> - <tr> - <th>{{_ "Stats_Total_Direct_Messages"}}</th> - <td>{{statistics.totalDirect}}</td> - </tr> - <tr> - <th>{{_ "Stats_Total_Messages"}}</th> - <td>{{statistics.totalMessages}}</td> + <table class="statistics-table secondary-background-color"> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Total_Users"}}</th> + <td class="border-component-color">{{statistics.totalUsers}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Active_Users"}}</th> + <td class="border-component-color">{{statistics.activeUsers}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Non_Active_Users"}}</th> + <td class="border-component-color">{{statistics.nonActiveUsers}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Online_Users"}}</th> + <td class="border-component-color">{{statistics.onlineUsers}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Away_Users"}}</th> + <td class="border-component-color">{{statistics.awayUsers}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Offline_Users"}}</th> + <td class="border-component-color">{{statistics.offlineUsers}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Total_Rooms"}}</th> + <td class="border-component-color">{{statistics.totalRooms}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Total_Channels"}}</th> + <td class="border-component-color">{{statistics.totalChannels}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Total_Private_Groups"}}</th> + <td class="border-component-color">{{statistics.totalPrivateGroups}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Total_Direct_Messages"}}</th> + <td class="border-component-color">{{statistics.totalDirect}}</td> + </tr> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color">{{_ "Stats_Total_Messages"}}</th> + <td class="border-component-color">{{statistics.totalMessages}}</td> </tr> </table> diff --git a/packages/rocketchat-ui-admin/admin/rooms/adminRoomInfo.html b/packages/rocketchat-ui-admin/admin/rooms/adminRoomInfo.html index 7544c1facd46d3582c0d9e991de6310c761350f6..48655d977163c4e7e0c9cefabbbed138200ab766 100644 --- a/packages/rocketchat-ui-admin/admin/rooms/adminRoomInfo.html +++ b/packages/rocketchat-ui-admin/admin/rooms/adminRoomInfo.html @@ -12,7 +12,7 @@ <label>{{_ "Name"}}</label> <div> {{#if editing 'roomName'}} - <input type="text" name="roomName" value="{{roomName}}" class="editing" /> + <input type="text" name="roomName" value="{{roomName}}" class="content-background-color editing" /> <button type="button" class="button cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button> {{else}} @@ -25,7 +25,7 @@ <label>{{_ "Topic"}}</label> <div> {{#if editing 'roomTopic'}} - <input type="text" name="roomTopic" value="{{roomTopic}}" class="editing" /> + <input type="text" name="roomTopic" value="{{roomTopic}}" class="content-background-color editing" /> <button type="button" class="button cancel">{{_ "Cancel"}}</button> <button type="button" class="button primary save">{{_ "Save"}}</button> {{else}} diff --git a/packages/rocketchat-ui-admin/admin/rooms/adminRooms.coffee b/packages/rocketchat-ui-admin/admin/rooms/adminRooms.coffee index 277b9b24eab48829433aa52ac3d435efde6846ff..ac484d8ff2314a0e4fe9bbad55b90b7c32c07e25 100644 --- a/packages/rocketchat-ui-admin/admin/rooms/adminRooms.coffee +++ b/packages/rocketchat-ui-admin/admin/rooms/adminRooms.coffee @@ -14,10 +14,10 @@ Template.adminRooms.helpers roomCount: -> return Template.instance().rooms?().count() name: -> - if @t is 'c' or @t is 'p' - return @name - else if @t is 'd' - return @usernames.join ' x ' + # if @t is 'c' or @t is 'p' + return @name + # else if @t is 'd' + # return @usernames.join ' x ' type: -> if @t is 'c' return TAPi18n.__ 'Channel' diff --git a/packages/rocketchat-ui-admin/admin/rooms/adminRooms.html b/packages/rocketchat-ui-admin/admin/rooms/adminRooms.html index 8315a3d91285b9069e0f4d21c64c369bee4c1cf1..74522f97b799dca296485b89a57fb7e838f39927 100644 --- a/packages/rocketchat-ui-admin/admin/rooms/adminRooms.html +++ b/packages/rocketchat-ui-admin/admin/rooms/adminRooms.html @@ -1,6 +1,6 @@ <template name="adminRooms"> <section class="page-container page-list"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Rooms"}}</span> @@ -13,7 +13,7 @@ <form class="search-form" role="form"> <div class="input-line search"> <input type="text" id="rooms-filter" placeholder="{{_ "Search"}}" dir="auto"> - <i class="icon-search"></i> + <i class="icon-search secondary-font-color"></i> {{#unless isReady}}<i class="icon-spin"></i>{{/unless}} </div> <label><input type="checkbox" name="room-type" value="c"> {{_ "Channels"}}</label> @@ -24,24 +24,24 @@ {{{_ "Showing_results" roomCount}}} </div> <div class="list"> - <table> + <table class="secondary-background-color"> <thead> - <tr> - <th width="30%">{{_ "Name"}}</th> - <th width="20%">{{_ "Type"}}</th> - <th width="20%">{{_ "Users"}}</th> - <th width="10%">{{_ "Msgs"}}</th> - <th width="20%">{{_ "Default"}}</th> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color" width="30%">{{_ "Name"}}</th> + <th class="content-background-color border-component-color" width="20%">{{_ "Type"}}</th> + <th class="content-background-color border-component-color" width="20%">{{_ "Users"}}</th> + <th class="content-background-color border-component-color" width="10%">{{_ "Msgs"}}</th> + <th class="content-background-color border-component-color" width="20%">{{_ "Default"}}</th> </tr> </thead> <tbody> {{#each rooms}} <tr class="room-info row-link"> - <td>{{name}}</td> - <td>{{type}}</td> - <td>{{usernames.length}}</td> - <td>{{msgs}}</td> - <td>{{default}}</td> + <td class="border-component-color">{{name}}</td> + <td class="border-component-color">{{type}}</td> + <td class="border-component-color">{{usernames.length}}</td> + <td class="border-component-color">{{msgs}}</td> + <td class="border-component-color">{{default}}</td> </tr> {{/each}} </tbody> @@ -53,7 +53,7 @@ {{/unless}} </div> </section> - <section class="flex-tab"> + <section class="flex-tab secondary-background-color"> {{> Template.dynamic template=flexTemplate data=flexData}} </section> </template> diff --git a/packages/rocketchat-ui-admin/admin/users/adminInviteUser.html b/packages/rocketchat-ui-admin/admin/users/adminInviteUser.html index 78991c1a9972c436be2ac20c0e1888aad8ee7ab0..439484d64ecddf3ef455d33f0c741ff64e0daf32 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminInviteUser.html +++ b/packages/rocketchat-ui-admin/admin/users/adminInviteUser.html @@ -5,7 +5,7 @@ <h3>{{_ "Send_invitation_email"}}</h3> <div class="input-line"> <label for="inviteEmails">{{_ "Send_invitation_email_info"}}</label> - <textarea id="inviteEmails" rows="3" style="height: auto"></textarea> + <textarea id="inviteEmails" rows="3" style="height: auto" class="content-background-color"></textarea> </div> </form> </div> diff --git a/packages/rocketchat-ui-admin/admin/users/adminUsers.html b/packages/rocketchat-ui-admin/admin/users/adminUsers.html index cab9a39673cb8927a3e8566c22a5bb8b0dd4f955..bbdbe94c55e97b76541e490983b7cd30f0ea8d02 100644 --- a/packages/rocketchat-ui-admin/admin/users/adminUsers.html +++ b/packages/rocketchat-ui-admin/admin/users/adminUsers.html @@ -1,6 +1,6 @@ <template name="adminUsers"> <section class="page-container page-list"> - <header class="fixed-title"> + <header class="fixed-title border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Users"}}</span> @@ -13,34 +13,34 @@ <form class="search-form" role="form"> <div class="input-line search"> <input type="text" id="users-filter" placeholder="{{_ "Search"}}" dir="auto"> - <i class="icon-search"></i> - {{#unless isReady}}<i class="icon-spin"></i>{{/unless}} + <i class="icon-search secondary-font-color"></i> + {{#unless isReady}}<i class="icon-spin secondary-font-color"></i>{{/unless}} </div> </form> <div class="results"> {{{_ "Showing_results" users.length}}} </div> <div class="list"> - <table> + <table class="secondary-background-color"> <thead> - <tr> - <th> </th> - <th width="34%">{{_ "Name"}}</th> - <th width="33%">{{_ "Username"}}</th> - <th width="33%">{{_ "Email"}}</th> + <tr class="admin-table-row"> + <th class="content-background-color border-component-color"> </th> + <th class="content-background-color border-component-color" width="34%">{{_ "Name"}}</th> + <th class="content-background-color border-component-color" width="33%">{{_ "Username"}}</th> + <th class="content-background-color border-component-color" width="33%">{{_ "Email"}}</th> </tr> </thead> <tbody> {{#each users}} <tr class="user-info row-link"> - <td> + <td class="border-component-color"> <div class="user-image status-{{status}}"> {{> avatar username=username}} </div> </td> - <td>{{name}}</td> - <td>{{username}}</td> - <td>{{emailAddress}}</td> + <td class="border-component-color">{{name}}</td> + <td class="border-component-color">{{username}}</td> + <td class="border-component-color">{{emailAddress}}</td> </tr> {{/each}} </tbody> @@ -52,7 +52,7 @@ {{/unless}} </div> </section> - <section class="flex-tab"> + <section class="flex-tab secondary-background-color"> {{> Template.dynamic template=flexTemplate data=flexData}} </section> </template> diff --git a/packages/rocketchat-ui-admin/publications/adminRooms.js b/packages/rocketchat-ui-admin/publications/adminRooms.js index 950b70213f57dd586eeabcb7ec758ae3c7e70e21..9d82b4bd2c733b82c581beb8aef101addc4c3d57 100644 --- a/packages/rocketchat-ui-admin/publications/adminRooms.js +++ b/packages/rocketchat-ui-admin/publications/adminRooms.js @@ -31,10 +31,13 @@ Meteor.publish('adminRooms', function(filter, types, limit) { }; filter = _.trim(filter); if (filter && types.length) { + // CACHE: can we stop using publications here? return RocketChat.models.Rooms.findByNameContainingAndTypes(filter, types, options); } else if (types.length) { + // CACHE: can we stop using publications here? return RocketChat.models.Rooms.findByTypes(types, options); } else { + // CACHE: can we stop using publications here? return RocketChat.models.Rooms.findByNameContaining(filter, options); } }); diff --git a/packages/rocketchat-ui-flextab/flex-tab/flexTabBar.coffee b/packages/rocketchat-ui-flextab/flex-tab/flexTabBar.coffee index 2838c88f81a741a239e7e0e71da064aebbddd652..c47a31ba59e4d8e4b77b2ae10f62e1e73674fa9f 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/flexTabBar.coffee +++ b/packages/rocketchat-ui-flextab/flex-tab/flexTabBar.coffee @@ -17,6 +17,7 @@ Template.flexTabBar.events RocketChat.TabBar.closeFlex() $('.flex-tab').css('max-width', '') $('.main-content').css('right', '') + RocketChat.TabBar._setTemplate '' else if not @openClick? or @openClick(e,t) if @width? diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.coffee b/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.coffee index 50f11b24641704374bd47e24a004fc97cfb72214..322a17cceb9dfb154e1545194308ad73929fd965 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.coffee +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.coffee @@ -16,23 +16,20 @@ Template.membersList.helpers roomUsers: -> onlineUsers = RoomManager.onlineUsers.get() + roomUsernames = Template.instance().users.get() room = ChatRoom.findOne(this.rid) - roomUsernames = room?.usernames or [] - roomOnlineUsernames = roomUsernames.filter((username) -> onlineUsers[username]) roomMuted = room?.muted or [] - if Template.instance().showAllUsers.get() - usernames = roomUsernames - else - usernames = roomOnlineUsernames - - users = usernames.map (username) -> - utcOffset = onlineUsers[username]?.utcOffset + totalOnline = 0 + users = roomUsernames.map (username) -> + if onlineUsers[username]? + totalOnline++ + utcOffset = onlineUsers[username].utcOffset - if utcOffset? - if utcOffset > 0 - utcOffset = "+#{utcOffset}" - utcOffset = "(UTC #{utcOffset})" + if utcOffset? + if utcOffset > 0 + utcOffset = "+#{utcOffset}" + utcOffset = "(UTC #{utcOffset})" return { username: username @@ -50,14 +47,13 @@ Template.membersList.helpers hasMore = users.length > usersLimit users = _.first(users, usersLimit) - totalUsers = roomUsernames.length totalShowing = users.length - totalOnline = roomOnlineUsernames.length ret = _id: this.rid - total: totalUsers + total: Template.instance().total.get() totalShowing: totalShowing + loading: Template.instance().loading.get() totalOnline: totalOnline users: users hasMore: hasMore @@ -133,6 +129,17 @@ Template.membersList.onCreated -> @userDetail = new ReactiveVar @showDetail = new ReactiveVar false + @users = new ReactiveVar [] + @total = new ReactiveVar + @loading = new ReactiveVar true + + Tracker.autorun => + @loading.set true + Meteor.call 'getUsersOfRoom', this.data.rid, this.showAllUsers.get(), (error, users) => + @users.set users.records + @total.set users.total + @loading.set false + @clearUserDetail = => @showDetail.set(false) setTimeout => diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.html b/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.html index 2e403adb74e056f9325a660b46e18777f8ec366c..4f1675dead84a80006c7719b6f838de1a1be47a2 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.html +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/membersList.html @@ -6,34 +6,40 @@ {{#with roomUsers}} <div class="title"> <h2>{{_ "Members_List"}}</h2> - <p> - {{{_ "Showing_online_users" total_showing=totalShowing online=totalOnline total=total}}} - <button class="see-all">{{seeAll}}</button> - </p> {{> videoButtons}} {{#if canAddUser}} <div class="control"> <div class="search-form"> <div class="input-line search"> - {{> inputAutocomplete settings=autocompleteSettingsAddUser id="user-add-search" class="search" placeholder=tAddUsers}} - <i class="icon-plus"></i> + {{> inputAutocomplete settings=autocompleteSettingsAddUser id="user-add-search" class="search content-background-color" placeholder=tAddUsers}} + <i class="icon-plus secondary-font-color"></i> </div> </div> </div> {{/if}} + <p> + {{#unless loading}} + {{{_ "Showing_online_users" total_showing=totalShowing online=totalOnline total=total}}} + <button class="see-all">{{seeAll}}</button> + {{/unless}} + </p> </div> <ul class='list clearfix lines'> - {{#each users}} - <li class='user-image user-card-room status-{{status}}'> - <button data-username="{{username}}" tabindex="0" title="{{username}}"> - {{> avatar username=username}} - <p>{{username}} {{utcOffset}}</p> - {{#if muted}} - <i class="icon-mute" title="{{_ "User_muted"}}"></i> - {{/if}} - </button> - </li> - {{/each}} + {{#if loading}} + {{> loading}} + {{else}} + {{#each users}} + <li class='user-image user-card-room status-{{status}}'> + <button data-username="{{username}}" tabindex="0" title="{{username}}"> + {{> avatar username=username}} + <p>{{username}} {{utcOffset}}</p> + {{#if muted}} + <i class="icon-mute" title="{{_ "User_muted"}}"></i> + {{/if}} + </button> + </li> + {{/each}} + {{/if}} </ul> {{#if hasMore}} <button class="button show-more-users">{{_ "Show_more"}}</button> diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/messageSearch.html b/packages/rocketchat-ui-flextab/flex-tab/tabs/messageSearch.html index 8a2862ab7f903c5ba80b961c6e1953e581606611..540b8b3ba6e3409e118708c7fcec8a88432d5aa3 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/messageSearch.html +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/messageSearch.html @@ -3,13 +3,13 @@ <div class="list-view search-messages-list"> <div class="title"> <h2>{{_ "Search_Messages"}}</h2> - <p>{{_ "You_can_search_using_RegExp_eg"}} <code class="inline">/^text$/i</code></p> + <p>{{_ "You_can_search_using_RegExp_eg"}} <code class="code-colors inline">/^text$/i</code></p> </div> <div class="control"> <form class="search-form" role="form"> <div class="input-line search"> - <input type="text" id="message-search" class="search" placeholder="{{tSearchMessages}}" autocomplete="off" /> - <i class="icon-right-open-small"></i> + <input type="text" id="message-search" class="search content-background-color" placeholder="{{tSearchMessages}}" autocomplete="off" /> + <i class="icon-search secondary-font-color"></i> </div> </form> </div> @@ -19,23 +19,19 @@ {{/unless}} {{/if}} </div> - <ul class="list clearfix"> - {{#if currentSearchTerm}} - {{#if searchResultMessages}} + {{#if currentSearchTerm}} + {{#if searchResultMessages}} + <ul class="list clearfix"> {{#each searchResultMessages}} {{#nrr nrrargs 'message' message}}{{/nrr}} {{/each}} - {{#if hasMore}} - <li class="load-more"> - {{#if ready}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> - {{/if}} + </ul> + {{#if hasMore}} + <div class="load-more"> + {{> loading}} + </div> {{/if}} {{/if}} - </ul> + {{/if}} </div> </template> diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.coffee b/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.coffee index e0196df020a9e7eb2e597c00e2e7def246a8f562..7bc48eb25b71cb896f6c971037bd190e0d9126db 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.coffee +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.coffee @@ -29,6 +29,22 @@ Template.uploadedFilesList.helpers url: -> return '/file-upload/' + @_id + '/' + @name + fixCordova: (url) -> + if Meteor.isCordova and url?[0] is '/' + url = Meteor.absoluteUrl().replace(/\/$/, '') + url + query = "rc_uid=#{Meteor.userId()}&rc_token=#{Meteor._localStorage.getItem('Meteor.loginToken')}" + if url.indexOf('?') is -1 + url = url + '?' + query + else + url = url + '&' + query + + if Meteor.settings.public.sandstorm or url.match /^(https?:)?\/\//i + return url + else if navigator.userAgent.indexOf('Electron') > -1 + return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url + else + return Meteor.absoluteUrl().replace(/\/$/, '') + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url + Template.uploadedFilesList.events 'click .room-file-item': (e, t) -> if $(e.currentTarget).siblings('.icon-picture').length diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.html b/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.html index 4d8a25c79b01c5907331318289149d221fb15b9a..22d4bb206f04d39c9e8ae85102c0b5bf87f70e14 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.html +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/uploadedFilesList.html @@ -10,25 +10,21 @@ {{#if canDelete}} <i class="icon-trash file-delete"></i> {{/if}} - <a title="{{escapedName}}" href="{{url}}" target="_blank" class="file-download" download=""> + <a title="{{escapedName}}" href="{{fixCordova url}}" target="_blank" class="file-download" download=""> <i class="icon-download file-download"></i> </a> - <a title="{{escapedName}}" href="{{url}}" target="_blank" class="room-file-item file-name {{customClassForFileType}}"> + <a title="{{escapedName}}" href="{{fixCordova url}}" target="_blank" class="room-file-item file-name {{customClassForFileType}}"> <i class="{{getFileIcon type}}"></i> <p>{{name}}</p> </a> </li> {{/each}} - {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> - {{/if}} </ul> + {{#if hasMore}} + <div class="load-more"> + {{> loading}} + </div> + {{/if}} {{#if Template.subscriptionsReady}} {{#unless hasFiles}} <h2>{{_ "Room_uploaded_file_list_empty"}}</h2> diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/userEdit.html b/packages/rocketchat-ui-flextab/flex-tab/tabs/userEdit.html index 7fd153244033bd319b581ca7962e52a0d49e9979..f05851461c57e4fc39d8ce9cb6c1240768f0978d 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/userEdit.html +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/userEdit.html @@ -1,6 +1,6 @@ <template name="userEdit"> {{#unless canEditOrAdd}} - <p>{{_ "You_are_not_authorized_to_view_this_page"}}</p> + <p class="secondary-font-color">{{_ "You_are_not_authorized_to_view_this_page"}}</p> {{else}} <div class="about clearfix"> <form class="edit-form" autocomplete="off"> diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee index a86ed4aee1cee21c911b85da222350779c41df4b..c78c5f5c600c40179a1fa8228abf08596e15b514 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.coffee @@ -111,6 +111,15 @@ Template.userInfo.helpers roles = _.union(UserRoles.findOne(uid)?.roles, RoomRoles.findOne({'u._id': uid, rid: Session.get('openedRoom') })?.roles) return RocketChat.models.Roles.find({ _id: { $in: roles }, description: { $exists: 1 } }, { fields: { description: 1 } }) + isDirect: -> + room = ChatRoom.findOne(Session.get('openedRoom')) + + return room?.t is 'd' + + isBlocker: -> + subscription = ChatSubscription.findOne({rid:Session.get('openedRoom'), 'u._id': Meteor.userId()}, { fields: { blocker: 1 } }); + return subscription.blocker + Template.userInfo.events 'click .thumb': (e) -> $(e.currentTarget).toggleClass('bigger') @@ -123,7 +132,7 @@ Template.userInfo.events if result?.rid? FlowRouter.go('direct', { username: @username }, FlowRouter.current().queryParams, -> if window.matchMedia("(max-width: 500px)").matches - RocketChat.TabBar.closeFlex()) + RocketChat.TabBar.closeFlex()) "click .flex-tab .video-remote" : (e) -> if RocketChat.TabBar.isFlexOpen() @@ -343,6 +352,26 @@ Template.userInfo.events instance.editingUser.set instance.user.get()._id + 'click .block-user': (e, instance) -> + e.stopPropagation() + e.preventDefault() + + Meteor.call 'blockUser', { rid: Session.get('openedRoom'), blocked: instance.user.get()._id }, (error, result) -> + if result + toastr.success t('User_is_blocked') + if error + handleError(error) + + 'click .unblock-user': (e, instance) -> + e.stopPropagation() + e.preventDefault() + + Meteor.call 'unblockUser', { rid: Session.get('openedRoom'), blocked: instance.user.get()._id }, (error, result) -> + if result + toastr.success t('User_is_unblocked') + if error + handleError(error) + Template.userInfo.onCreated -> @now = new ReactiveVar moment() diff --git a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html index 7d4a872bfff51ad5dc1f2e2ab1dcb9b171d752dc..4d31d1bb69e9458747f22e29e37e1418fa53d323 100644 --- a/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html +++ b/packages/rocketchat-ui-flextab/flex-tab/tabs/userInfo.html @@ -12,30 +12,30 @@ </div> <div class="info"> <h3 title="{{username}}"><i class="status-{{status}}"></i> {{username}}</h3> - <p>{{name}}</p> - <p> + <p class="secondary-font-color">{{name}}</p> + <p class="secondary-font-color"> {{#each roleTags}} <span class="role-tag" data-role="{{description}}">{{description}}</span> {{/each}} </p> - {{#if utc}}<p><i class="icon-clock"></i>{{userTime}} (UTC {{utc}})</p>{{/if}} + {{#if utc}}<p class="secondary-font-color"><i class="icon-clock"></i>{{userTime}} (UTC {{utc}})</p>{{/if}} {{#if hasPermission 'view-full-other-user-info'}} {{#if hasEmails}} - {{#each emails}} <p><i class="icon-mail"></i> {{address}}{{#if verified}} <i class="icon-ok"></i>{{/if}}</p> {{/each}} + {{#each emails}} <p class="secondary-font-color"><i class="icon-mail"></i> {{address}}{{#if verified}} <i class="icon-ok success-color"></i>{{/if}}</p> {{/each}} {{/if}} {{#if hasPhone}} - {{#each phone}} <p><i class="icon-phone"></i> {{phoneNumber}}</p> {{/each}} + {{#each phone}} <p class="secondary-font-color"><i class="icon-phone"></i> {{phoneNumber}}</p> {{/each}} {{/if}} - {{#if lastLogin}} <p><i class="icon-calendar"></i> {{_ "Created_at"}}: {{createdAt}}</p> {{/if}} - {{#if lastLogin}} <p><i class="icon-calendar"></i> {{_ "Last_login"}}: {{lastLogin}}</p> {{/if}} - {{#if services.facebook.id}} <p><i class="icon-facebook"></i><a href="{{services.facebook.link}}" target="_blank">{{services.facebook.name}}</a></p> {{/if}} - {{#if services.github.id}} <p><i class="icon-github-circled"></i><a href="https://www.github.com/{{services.github.username}}" target="_blank">{{services.github.username}}</a></p> {{/if}} - {{#if services.gitlab.id}} <p><i class="icon-gitlab"></i>{{services.gitlab.username}}</p> {{/if}} - {{#if services.google.id}} <p><i class="icon-gplus"></i><a href="https://plus.google.com/{{services.google.id}}" target="_blank">{{services.google.name}}</a></p> {{/if}} - {{#if services.linkedin.id}} <p><i class="icon-linkedin"></i><a href="{{services.linkedin.publicProfileUrl}}" target="_blank">{{linkedinUsername}}</a></p> {{/if}} - {{#if servicesMeteor.id}} <p><i class="icon-meteor"></i>{{servicesMeteor.username}}</p> {{/if}} - {{#if services.twitter.id}} <p><i class="icon-twitter"></i><a href="https://twitter.com/{{services.twitter.screenName}}" target="_blank">{{services.twitter.screenName}}</a></p> {{/if}} - {{#if services.wordpress.id}} <p><i class="icon-wordpress"></i>{{services.wordpress.user_login}}</p> {{/if}} + {{#if lastLogin}} <p class="secondary-font-color"><i class="icon-calendar"></i> {{_ "Created_at"}}: {{createdAt}}</p> {{/if}} + {{#if lastLogin}} <p class="secondary-font-color"><i class="icon-calendar"></i> {{_ "Last_login"}}: {{lastLogin}}</p> {{/if}} + {{#if services.facebook.id}} <p class="secondary-font-color"><i class="icon-facebook"></i><a href="{{services.facebook.link}}" target="_blank">{{services.facebook.name}}</a></p> {{/if}} + {{#if services.github.id}} <p class="secondary-font-color"><i class="icon-github-circled"></i><a href="https://www.github.com/{{services.github.username}}" target="_blank">{{services.github.username}}</a></p> {{/if}} + {{#if services.gitlab.id}} <p class="secondary-font-color"><i class="icon-gitlab"></i>{{services.gitlab.username}}</p> {{/if}} + {{#if services.google.id}} <p class="secondary-font-color"><i class="icon-gplus"></i><a href="https://plus.google.com/{{services.google.id}}" target="_blank">{{services.google.name}}</a></p> {{/if}} + {{#if services.linkedin.id}} <p class="secondary-font-color"><i class="icon-linkedin"></i><a href="{{services.linkedin.publicProfileUrl}}" target="_blank">{{linkedinUsername}}</a></p> {{/if}} + {{#if servicesMeteor.id}} <p class="secondary-font-color"><i class="icon-meteor"></i>{{servicesMeteor.username}}</p> {{/if}} + {{#if services.twitter.id}} <p class="secondary-font-color"><i class="icon-twitter"></i><a href="https://twitter.com/{{services.twitter.screenName}}" target="_blank">{{services.twitter.screenName}}</a></p> {{/if}} + {{#if services.wordpress.id}} <p class="secondary-font-color"><i class="icon-wordpress"></i>{{services.wordpress.user_login}}</p> {{/if}} {{/if}} </div> </div> @@ -44,23 +44,29 @@ {{#if user.active}} {{> videoButtons}} {{/if}} + {{#if isDirect}} + {{#if isBlocker}} + <button class='button button-block tertiary unblock-user'><span><i class='icon-block'></i> {{_ "Unblock_User"}}</span></button> + {{else}} + <button class='button button-block danger block-user'><span><i class='icon-block'></i> {{_ "Block_User"}}</span></button> + {{/if}} + {{/if}} {{#if showAll}} {{#if canDirectMessage user.username}} - <button class='button button-block primary pvt-msg'><span><i class='icon-chat'></i> {{_ "Conversation"}}</span></button> - {{/if}} + <button class='button button-block primary pvt-msg'><span><i class='icon-chat'></i> {{_ "Conversation"}}</span></button> {{/if}} {{#if canSetOwner}} {{#if isOwner}} <button class="button button-block danger unset-owner"><span>{{_ "Remove_as_owner"}}</span></button> {{else}} - <button class="button button-block secondary set-owner"><span>{{_ "Set_as_owner"}}</span></button> + <button class="button button-block tertiary set-owner"><span>{{_ "Set_as_owner"}}</span></button> {{/if}} {{/if}} {{#if canSetModerator}} {{#if isModerator}} <button class="button button-block danger unset-moderator"><span>{{_ "Remove_as_moderator"}}</span></button> {{else}} - <button class="button button-block secondary set-moderator"><span>{{_ "Set_as_moderator"}}</span></button> + <button class="button button-block tertiary set-moderator"><span>{{_ "Set_as_moderator"}}</span></button> {{/if}} {{/if}} {{#if canMuteUser}} diff --git a/packages/rocketchat-ui-login/login/form.coffee b/packages/rocketchat-ui-login/login/form.coffee index 6f35258fec2c44504d8e42a062fc4296b6161b34..53abe369367725fa6c631c364962d6b54c583806 100644 --- a/packages/rocketchat-ui-login/login/form.coffee +++ b/packages/rocketchat-ui-login/login/form.coffee @@ -47,22 +47,6 @@ Template.loginForm.helpers hasOnePassword: -> return OnePassword?.findLoginForUrl? && device?.platform?.toLocaleLowerCase() is 'ios' - customFields: -> - if not Template.instance().customFields.get() - return [] - - customFieldsArray = [] - for key, value of Template.instance().customFields.get() - if value.hideFromForm is true - continue - - customFieldsArray.push - fieldName: key, - field: value - - return customFieldsArray - - Template.loginForm.events 'submit #login-card': (event, instance) -> event.preventDefault() @@ -155,13 +139,6 @@ Template.loginForm.events OnePassword.findLoginForUrl(succesCallback, errorCallback, Meteor.absoluteUrl()) - 'focus .input-text input': (event) -> - $(event.currentTarget).parents('.input-text').addClass('focus') - - 'blur .input-text input': (event) -> - if event.currentTarget.value is '' - $(event.currentTarget).parents('.input-text').removeClass('focus') - Template.loginForm.onCreated -> instance = @ diff --git a/packages/rocketchat-ui-login/login/form.html b/packages/rocketchat-ui-login/login/form.html index fd86236712577da1c54d8372f008888d118674e9..c761e8f19c5bec18409cf770d16d2955cd739f4e 100644 --- a/packages/rocketchat-ui-login/login/form.html +++ b/packages/rocketchat-ui-login/login/form.html @@ -1,10 +1,10 @@ <template name="loginForm"> {{#if state 'sandstorm'}} - <div class="alert alert-danger"> + <div class="alert error-color error-background error-border"> You must login to Sandstorm (on the top right) in order to access this chat. </div> {{else}} - <form id="login-card" method='/' novalidate> + <form id="login-card" class="content-background-color color-primary-font-color" method='/' novalidate> {{#if state 'wait-activation'}} <header> <h2>{{{_ "Registration_Succeeded"}}}</h2> @@ -12,85 +12,75 @@ <p>{{{_ "Please_wait_activation"}}}</p> </header> {{else}} - {{ > loginServices }} + {{> loginServices }} {{#if needsValidateEmail}} - <div class="alert alert-danger"> + <div class="alert error-color error-background error-border"> {{_ "You_need_confirm_email"}} </div> {{/if}} {{#if showFormLogin}} <div class="fields"> {{#if state 'login'}} - <div class='input-text active'> + <div class="input-line"> <label for="emailOrUsername">{{emailOrUsernamePlaceholder}}</label> - <input type="text" name='emailOrUsername' id="emailOrUsername" autocapitalize="off" autocorrect="off" /> - {{#if hasOnePassword}} - <div class="one-passsword"></div> - {{/if}} - <div class="input-error"></div> + <div> + <input type="text" name='emailOrUsername' id="emailOrUsername" autocapitalize="off" autocorrect="off" /> + {{#if hasOnePassword}} + <div class="one-passsword"></div> + {{/if}} + <div class="input-error"></div> + </div> </div> - <div class='input-text active'> + <div class="input-line"> <label for="pass">{{passwordPlaceholder}}</label> - <input type="password" name='pass' id="pass" /> - <div class="input-error"></div> + <div> + <input type="password" name='pass' id="pass" /> + <div class="input-error"></div> + </div> </div> {{/if}} {{#if state 'register'}} - <div class='input-text active'> + <div class="input-line"> <label for="name">{{namePlaceholder}}</label> - <input type="text" name='name' id="name" dir="auto" /> - <div class="input-error"></div> + <div> + <input type="text" name='name' id="name" dir="auto" /> + <div class="input-error"></div> + </div> </div> - <div class='input-text active'> + <div class="input-line"> <label for="email">{{_ "Email"}}</label> - <input type="email" name='email' id="email" /> - <div class="input-error"></div> + <div> + <input type="email" name='email' id="email" /> + <div class="input-error"></div> + </div> </div> - {{#each customFields}} - {{#if $eq field.type 'select'}} - <div class='input-text active focus'> - <label for="{{fieldName}}">{{_ fieldName}}</label> - <div class="select-arrow"> - <i class="icon-down-open"></i> - </div> - <select name="{{fieldName}}" data-customfield="true"> - {{#each field.options}} - {{#if $eq . ../field.defaultValue}} - <option value="{{.}}" selected>{{_ .}}</option> - {{else}} - <option value="{{.}}">{{_ .}}</option> - {{/if}} - {{/each}} - </select> - <div class="input-error"></div> - </div> - {{/if}} - {{#if $eq field.type 'text'}} - <div class='input-text active'> - <label for="{{fieldName}}">{{_ fieldName}}</label> - <input type="text" name="{{fieldName}}" id="{{fieldName}}" data-customfield="true" value="{{field.defaultValue}}" maxlength="{{field.maxLength}}" /> - <div class="input-error"></div> - </div> - {{/if}} - {{/each}} - <div class='input-text active'> + + {{> customFieldsForm hideFromForm=true}} + + <div class="input-line"> <label for="pass">{{passwordPlaceholder}}</label> - <input type="password" name='pass' id="pass" /> - <div class="input-error"></div> + <div> + <input type="password" name='pass' id="pass" /> + <div class="input-error"></div> + </div> </div> {{#if requirePasswordConfirmation}} - <div class='input-text active'> + <div class="input-line"> <label for="confirm-pass">{{_ "Confirm_password"}}</label> - <input type="password" name='confirm-pass' id="confirm-pass" /> - <div class="input-error"></div> + <div> + <input type="password" name='confirm-pass' id="confirm-pass" /> + <div class="input-error"></div> + </div> </div> {{/if}} {{/if}} {{#if state 'forgot-password' 'email-verification'}} - <div class='input-text active'> + <div class="input-line"> <label for="email">{{_ "Email"}}</label> - <input type="email" name='email' id="email" /> - <div class="input-error"></div> + <div> + <input type="email" name='email' id="email" /> + <div class="input-error"></div> + </div> </div> {{/if}} </div> @@ -126,7 +116,7 @@ <div class='login-terms'> {{{loginTerms}}} <div class="powered-by"> - Powered by <a href="https://rocket.chat">Open Source Chat Platform Rocket.Chat</a>. + Powered by <a class="color-tertiary-font-color" href="https://rocket.chat">Open Source Chat Platform Rocket.Chat</a>. </div> </div> {{/if}} diff --git a/packages/rocketchat-ui-login/login/layout.html b/packages/rocketchat-ui-login/login/layout.html index a097d5834bb2a9f91fd6892e1a06221dd2e17524..056958c7a019802d863a419880ad392c79fb8a66 100644 --- a/packages/rocketchat-ui-login/login/layout.html +++ b/packages/rocketchat-ui-login/login/layout.html @@ -1,9 +1,9 @@ <template name="loginLayout"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> - {{ > loginHeader }} + {{> loginHeader }} {{> Template.dynamic template=center}} - {{ > loginFooter }} + {{> loginFooter }} </div> </section> </template> diff --git a/packages/rocketchat-ui-login/reset-password/resetPassword.html b/packages/rocketchat-ui-login/reset-password/resetPassword.html index 8592692eb7532b06ada3286046af2fbce03be70e..cb35991720aab3ee82fade1d6bd8bdbd5dc8888d 100644 --- a/packages/rocketchat-ui-login/reset-password/resetPassword.html +++ b/packages/rocketchat-ui-login/reset-password/resetPassword.html @@ -1,5 +1,5 @@ <template name="resetPassword"> - <form id="login-card" action="/"> + <form id="login-card" class="content-background-color color-primary-font-color" action="/"> <div class="fields"> <header> {{#if requirePasswordChange}} diff --git a/packages/rocketchat-ui-login/username/layout.html b/packages/rocketchat-ui-login/username/layout.html index 7302211d5d537631daccbea46c4949cd1be63adb..d2e6407055288ba35a1ecefa7ad2315713dee882 100644 --- a/packages/rocketchat-ui-login/username/layout.html +++ b/packages/rocketchat-ui-login/username/layout.html @@ -1,5 +1,5 @@ <template name="usernameLayout"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> {{> Template.dynamic template=render}} </div> diff --git a/packages/rocketchat-ui-login/username/username.coffee b/packages/rocketchat-ui-login/username/username.coffee index 48686fa7459cf5c74687b46809f684a778782fc3..225d190b79cb582754b71028550a5aa00fa5fe15 100644 --- a/packages/rocketchat-ui-login/username/username.coffee +++ b/packages/rocketchat-ui-login/username/username.coffee @@ -52,6 +52,3 @@ Template.username.events RocketChat.Button.reset(button) instance.username.set(username) RocketChat.callbacks.run('usernameSet') - - if not err? - Meteor.call 'joinDefaultChannels' diff --git a/packages/rocketchat-ui-login/username/username.html b/packages/rocketchat-ui-login/username/username.html index 4f12d49f0d30636fc0807c594f1c0937c02e63de..1f64b55e25c66b325e41d9a78b233bea906a1a43 100644 --- a/packages/rocketchat-ui-login/username/username.html +++ b/packages/rocketchat-ui-login/username/username.html @@ -1,23 +1,23 @@ <template name="username"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> - <form id="login-card" method='/'> + <form id="login-card" class="content-background-color color-primary-font-color" method='/'> <header> <h2>{{_ "Username_title"}}</h2> <p>{{_ "Username_description"}}</p> </header> {{#if username.error}} - <div class="alert alert-danger"> + <div class="alert error-color error-background error-border"> {{{_ "error-field-unavailable" field=username.username}}} </div> {{/if}} {{#if username.invalid}} - <div class="alert alert-danger"> + <div class="alert error-color error-background error-border"> {{{_ "Username_invalid" username.username}}} </div> {{/if}} {{#if username.empty}} - <div class="alert alert-danger"> + <div class="alert error-color error-background error-border"> {{{_ "Username_cant_be_empty"}}} </div> {{/if}} diff --git a/packages/rocketchat-ui-master/master/error.html b/packages/rocketchat-ui-master/master/error.html index 4290413f22fad872fffab3571336116722b2058e..07060aac8b95f5740579a8b6e77fe979d8ff7cb5 100644 --- a/packages/rocketchat-ui-master/master/error.html +++ b/packages/rocketchat-ui-master/master/error.html @@ -1,5 +1,5 @@ <template name="error"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> <header> <a class="logo" href="/"> diff --git a/packages/rocketchat-ui-master/master/loading.html b/packages/rocketchat-ui-master/master/loading.html index d6c4bae4c36ae6ae24b0218a1cd1c8ad7d9c2cd2..8cfdfef28ffca04f0279ef9fbae6b2f2feeb6d89 100644 --- a/packages/rocketchat-ui-master/master/loading.html +++ b/packages/rocketchat-ui-master/master/loading.html @@ -1,5 +1,5 @@ <template name="loading"> - <div class="loading"> + <div class="loading-animation"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> diff --git a/packages/rocketchat-ui-master/master/logoLayout.html b/packages/rocketchat-ui-master/master/logoLayout.html index c7decca4f52c297799e74cdbae85ab589289497a..f59261572c31a83e9f702f9e01375196bfe10b44 100644 --- a/packages/rocketchat-ui-master/master/logoLayout.html +++ b/packages/rocketchat-ui-master/master/logoLayout.html @@ -1,5 +1,5 @@ <template name="logoLayout"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> <header> <a class="logo" href="/"> diff --git a/packages/rocketchat-ui-master/master/main.coffee b/packages/rocketchat-ui-master/master/main.coffee index 96e72c2c0d5b0eb28ea9f394ad8d6be5a3d0b3da..2e80fe1425be45a1f958c0109925f1a578ee672a 100644 --- a/packages/rocketchat-ui-master/master/main.coffee +++ b/packages/rocketchat-ui-master/master/main.coffee @@ -79,68 +79,6 @@ Template.body.onRendered -> j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl f.parentNode.insertBefore j, f - Tracker.autorun (c) -> - if RocketChat.settings.get 'Meta_language' - c.stop() - - Meta.set - name: 'http-equiv' - property: 'content-language' - content: RocketChat.settings.get 'Meta_language' - Meta.set - name: 'name' - property: 'language' - content: RocketChat.settings.get 'Meta_language' - - Tracker.autorun (c) -> - if RocketChat.settings.get 'Meta_fb_app_id' - c.stop() - - Meta.set - name: 'property' - property: 'fb:app_id' - content: RocketChat.settings.get 'Meta_fb_app_id' - - Tracker.autorun (c) -> - if RocketChat.settings.get 'Meta_robots' - c.stop() - - Meta.set - name: 'name' - property: 'robots' - content: RocketChat.settings.get 'Meta_robots' - - Tracker.autorun (c) -> - if RocketChat.settings.get 'Meta_google-site-verification' - c.stop() - - Meta.set - name: 'name' - property: 'google-site-verification' - content: RocketChat.settings.get 'Meta_google-site-verification' - - Tracker.autorun (c) -> - if RocketChat.settings.get 'Meta_msvalidate01' - c.stop() - - Meta.set - name: 'name' - property: 'msvalidate.01' - content: RocketChat.settings.get 'Meta_msvalidate01' - - Tracker.autorun (c) -> - c.stop() - - Meta.set - name: 'name' - property: 'application-name' - content: RocketChat.settings.get 'Site_Name' - - Meta.set - name: 'name' - property: 'apple-mobile-web-app-title' - content: RocketChat.settings.get 'Site_Name' - if Meteor.isCordova $(document.body).addClass 'is-cordova' @@ -199,12 +137,10 @@ Template.main.helpers embeddedVersion: -> return 'embedded-view' if RocketChat.Layout.isEmbedded() - Template.main.events "click .burger": -> console.log 'room click .burger' if window.rocketDebug - chatContainer = $("#rocket-chat") menu.toggle() 'touchstart': (e, t) -> @@ -215,17 +151,18 @@ Template.main.events t.touchstartY = undefined t.movestarted = false t.blockmove = false + t.isRtl = isRtl localStorage.getItem "userLanguage" if $(e.currentTarget).closest('.main-content').length > 0 t.touchstartX = e.originalEvent.touches[0].clientX t.touchstartY = e.originalEvent.touches[0].clientY - t.mainContent = $('.main-content') + t.mainContent = $('.main-content, .flex-tab-bar') t.wrapper = $('.messages-box > .wrapper') 'touchmove': (e, t) -> if t.touchstartX? touch = e.originalEvent.touches[0] - diffX = t.touchstartX - touch.clientX - diffY = t.touchstartY - touch.clientY + diffX = touch.clientX - t.touchstartX + diffY = touch.clientY - t.touchstartY absX = Math.abs(diffX) absY = Math.abs(diffY) @@ -235,37 +172,58 @@ Template.main.events if t.blockmove isnt true and (t.movestarted is true or absX > 5) t.movestarted = true - if menu.isOpen() - t.left = 260 - diffX + if t.isRtl + if menu.isOpen() + t.diff = -260 + diffX + else + t.diff = diffX + + if t.diff < -260 + t.diff = -260 + if t.diff > 0 + t.diff = 0 else - t.left = -diffX + if menu.isOpen() + t.diff = 260 + diffX + else + t.diff = diffX - if t.left > 260 - t.left = 260 - if t.left < 0 - t.left = 0 + if t.diff > 260 + t.diff = 260 + if t.diff < 0 + t.diff = 0 t.mainContent.addClass('notransition') - t.mainContent.css('transform', 'translate('+t.left+'px)') + t.mainContent.css('transform', 'translate(' + t.diff + 'px)') t.wrapper.css('overflow', 'hidden') 'touchend': (e, t) -> if t.movestarted is true t.mainContent.removeClass('notransition') - t.mainContent.css('transform', ''); t.wrapper.css('overflow', '') - if menu.isOpen() - if t.left >= 200 - menu.open() + if t.isRtl + if menu.isOpen() + if t.diff >= -200 + menu.close() + else + menu.open() else - menu.close() + if t.diff <= -60 + menu.open() + else + menu.close() else - if t.left >= 60 - menu.open() + if menu.isOpen() + if t.diff >= 200 + menu.open() + else + menu.close() else - menu.close() - + if t.diff >= 60 + menu.open() + else + menu.close() Template.main.onRendered -> diff --git a/packages/rocketchat-ui-master/master/main.html b/packages/rocketchat-ui-master/master/main.html index ff577bb694aa257915303aeab505d38ee6d34fc6..754aadcb59603bbc658712280c43d80ee106b62f 100644 --- a/packages/rocketchat-ui-master/master/main.html +++ b/packages/rocketchat-ui-master/master/main.html @@ -2,41 +2,25 @@ <meta charset="utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="expires" content="-1" /> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="fragment" content="!" /> <meta name="distribution" content="global" /> <meta name="rating" content="general" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> - <meta name="msapplication-TileColor" content="#04436a"> - <meta name="msapplication-TileImage" content="images/logo/mstile-144x144.png?v=3"> - <meta name="msapplication-config" content="images/logo/browserconfig.xml?v=3"> - <meta name="theme-color" content="#04436a"> - <link rel="manifest" href="images/logo/manifest.json?v=3"> - <link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/nocfbnnmjnndkbipkabodnheejiegccf"> - <link rel="icon" sizes="any" type="image/svg+xml" href="assets/favicon.svg?v=3"> - <link rel="icon" sizes="256x256" type="image/png" href="assets/favicon_256.png?v=3"> - <link rel="icon" sizes="192x192" type="image/png" href="assets/favicon_192.png?v=3"> - <link rel="icon" sizes="128x128" type="image/png" href="assets/favicon_128.png?v=3"> - <link rel="icon" sizes="96x96" type="image/png" href="assets/favicon_96.png?v=3"> - <link rel="icon" sizes="64x64" type="image/png" href="assets/favicon_64.png?v=3"> - <!-- - <link rel="icon" sizes="48x48" type="image/png" href="/images/logo/favicon-48x48.png?v=3"> - <link rel="icon" sizes="32x32" type="image/png" href="/images/logo/favicon-32x32.png?v=3"> - <link rel="icon" sizes="16x16" type="image/png" href="/images/logo/favicon-16x16.png?v=3"> - --> - <link rel="shortcut icon" sizes="16x16 32x32 48x48" type="image/x-icon" href="assets/favicon_ico.ico?v=3" /> - <link rel="apple-touch-icon" sizes="57x57" href="images/logo/apple-touch-icon-57x57.png?v=3"> - <link rel="apple-touch-icon" sizes="60x60" href="images/logo/apple-touch-icon-60x60.png?v=3"> - <link rel="apple-touch-icon" sizes="72x72" href="images/logo/apple-touch-icon-72x72.png?v=3"> - <link rel="apple-touch-icon" sizes="76x76" href="images/logo/apple-touch-icon-76x76.png?v=3"> - <link rel="apple-touch-icon" sizes="114x114" href="images/logo/apple-touch-icon-114x114.png?v=3"> - <link rel="apple-touch-icon" sizes="120x120" href="images/logo/apple-touch-icon-120x120.png?v=3"> - <link rel="apple-touch-icon" sizes="144x144" href="images/logo/apple-touch-icon-144x144.png?v=3"> - <link rel="apple-touch-icon" sizes="152x152" href="images/logo/apple-touch-icon-152x152.png?v=3"> - <link rel="apple-touch-icon" sizes="180x180" href="images/logo/apple-touch-icon-180x180.png?v=3"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="msapplication-TileImage" content="images/logo/mstile-144x144.png?v=3" /> + <meta name="msapplication-config" content="images/logo/browserconfig.xml?v=3" /> + <link rel="manifest" href="images/logo/manifest.json?v=3" /> + <link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/nocfbnnmjnndkbipkabodnheejiegccf" /> + <link rel="icon" sizes="any" type="image/svg+xml" href="assets/favicon.svg?v=3" /> + <link rel="icon" sizes="32x32" type="image/png" href="/images/logo/favicon-32x32.png?v=3" /> + <link rel="icon" sizes="16x16" type="image/png" href="/images/logo/favicon-16x16.png?v=3" /> + <link rel="mask-icon" href="/images/logo/safari-pinned-tab.svg" color="#04436a"> + <link rel="apple-touch-icon" sizes="180x180" href="images/logo/apple-touch-icon-180x180.png?v=3" /> </head> -<body> +<body class="global-font-family color-primary-font-color"> </body> <template name="main"> @@ -63,16 +47,16 @@ {{> spotlight}} {{> videoCall overlay=true}} <div id="user-card-popover"></div> - <div id="rocket-chat" class="{{embeddedVersion}} menu-nav menu-closed"> + <div id="rocket-chat" class="{{embeddedVersion}} menu-nav"> <div class="connection-status"> {{> status}} </div> {{#unless modal}} - <div class="flex-tab-bar" role="toolbar"> + <div class="flex-tab-bar content-background-color" role="toolbar"> {{> flexTabBar}} </div> {{/unless}} - <div class="main-content {{flexOpened}} {{flexOpenedRTC1}} {{flexOpenedRTC2}} {{#if modal}}main-modal{{/if}}"> + <div class="main-content content-background-color {{flexOpened}} {{flexOpenedRTC1}} {{flexOpenedRTC2}} {{#if modal}}main-modal{{/if}}"> {{> Template.dynamic template=center}} </div> {{#unless modal}} diff --git a/packages/rocketchat-ui-master/server/inject.js b/packages/rocketchat-ui-master/server/inject.js index 6f4dca356aa38399c0eca9d82ffa163c3d4bde80..98b8b585dea84694da7af1781cda3618d392fc47 100644 --- a/packages/rocketchat-ui-master/server/inject.js +++ b/packages/rocketchat-ui-master/server/inject.js @@ -1,8 +1,8 @@ /* globals Inject */ -Inject.rawBody('page-loading', ` +Inject.rawHead('page-loading', ` <style> -.loading { +.loading-animation { top: 0; right: 0; bottom: 0; @@ -13,60 +13,70 @@ Inject.rawBody('page-loading', ` justify-content: center; text-align: center; } -.loading > div { +.loading-animation > div { width: 10px; height: 10px; margin: 2px; border-radius: 100%; display: inline-block; + background-color: rgba(255,255,255,0.6); -webkit-animation: loading-bouncedelay 1.4s infinite ease-in-out both; animation: loading-bouncedelay 1.4s infinite ease-in-out both; } -.loading .bounce1 { +.loading-animation .bounce1 { -webkit-animation-delay: -0.32s; animation-delay: -0.32s; } -.loading .bounce2 { +.loading-animation .bounce2 { -webkit-animation-delay: -0.16s; animation-delay: -0.16s; } @-webkit-keyframes loading-bouncedelay { - 0%, 80%, 100% { -webkit-transform: scale(0) } + 0%, + 80%, + 100% { -webkit-transform: scale(0) } 40% { -webkit-transform: scale(1.0) } } @keyframes loading-bouncedelay { - 0%, 80%, 100% { transform: scale(0); } + 0%, + 80%, + 100% { transform: scale(0); } 40% { transform: scale(1.0); } } -</style> +</style>`); + +Inject.rawBody('page-loading-div', ` <div id="initial-page-loading" class="page-loading"> - <div class="loading"> + <div class="loading-animation"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> </div> </div>`); +if (process.env.DISABLE_ANIMATION || process.env.TEST_MODE === 'true') { + Inject.rawHead('disable-animation', ` + <style> + body, body * { + animation: none !important; + transition: none !important; + } + </style> + <script> + window.DISABLE_ANIMATION = true; + </script> + `); +} -RocketChat.settings.get('theme-color-primary-background-color', function(key, value) { - if (value) { - Inject.rawHead('theme-color-primary-background-color', `<style>body { background-color: ${value};}</style>`); - } else { - Inject.rawHead('theme-color-primary-background-color', '<style>body { background-color: #04436a;}</style>'); - } +RocketChat.settings.get('theme-color-primary-background-color', (key, value = '#04436a') => { + Inject.rawHead(key, `<style>body { background-color: ${value};}</style>` + + `<meta name="msapplication-TileColor" content="${value}" />` + + `<meta name="theme-color" content="${value}" />`); }); -RocketChat.settings.get('theme-color-tertiary-background-color', function(key, value) { +RocketChat.settings.get('Accounts_ForgetUserSessionOnWindowClose', (key, value) => { if (value) { - Inject.rawHead('theme-color-tertiary-background-color', `<style>.loading > div { background-color: ${value};}</style>`); - } else { - Inject.rawHead('theme-color-tertiary-background-color', '<style>.loading > div { background-color: #cccccc;}</style>'); - } -}); - -RocketChat.settings.get('Accounts_ForgetUserSessionOnWindowClose', function(key, value) { - if (value) { - Inject.rawModHtml('Accounts_ForgetUserSessionOnWindowClose', function(html) { + Inject.rawModHtml(key, (html) => { const script = ` <script> if (Meteor._localStorage._data === undefined && window.sessionStorage) { @@ -74,42 +84,58 @@ RocketChat.settings.get('Accounts_ForgetUserSessionOnWindowClose', function(key, } </script> `; - return html.replace(/<\/body>/, script+'\n</body>'); + return html.replace(/<\/body>/, script + '\n</body>'); }); } else { - Inject.rawModHtml('Accounts_ForgetUserSessionOnWindowClose', function(html) { + Inject.rawModHtml(key, (html) => { return html; }); } }); -RocketChat.settings.get('Site_Url', function() { - Meteor.defer(function() { - let baseUrl; - if (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX && __meteor_runtime_config__.ROOT_URL_PATH_PREFIX.trim() !== '') { - baseUrl = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX; - } else { - baseUrl = '/'; - } - if (/\/$/.test(baseUrl) === false) { - baseUrl += '/'; - } - Inject.rawHead('base', `<base href="${baseUrl}">`); - }); +RocketChat.settings.get('Site_Name', (key, value = 'Rocket.Chat') => { + Inject.rawHead(key, + `<title>${value}</title>` + + `<meta name="application-name" content="${value}">` + + `<meta name="apple-mobile-web-app-title" content="${value}">`); }); -RocketChat.settings.get('Site_Name', function(key, value) { - if (value) { - Inject.rawHead('title', `<title>${value}</title>`); - } else { - Inject.rawHead('title', '<title>Rocket.Chat</title>'); - } +RocketChat.settings.get('Meta_language', (key, value = '') => { + Inject.rawHead(key, + `<meta http-equiv="content-language" content="${value}">` + + `<meta name="language" content="${value}">`); }); -RocketChat.settings.get('GoogleSiteVerification_id', function(key, value) { - if (value) { - Inject.rawHead('google-site-verification', `<meta name="google-site-verification" content="${value}" />`); +RocketChat.settings.get('Meta_robots', (key, value = '') => { + Inject.rawHead(key, `<meta name="robots" content="${value}">`); +}); + +RocketChat.settings.get('Meta_msvalidate01', (key, value = '') => { + Inject.rawHead(key, `<meta name="msvalidate.01" content="${value}">`); +}); + +RocketChat.settings.get('Meta_google-site-verification', (key, value = '') => { + Inject.rawHead(key, `<meta name="google-site-verification" content="${value}" />`); +}); + +RocketChat.settings.get('Meta_fb_app_id', (key, value = '') => { + Inject.rawHead(key, `<meta property="fb:app_id" content="${value}">`); +}); + +RocketChat.settings.get('Meta_custom', (key, value = '') => { + Inject.rawHead(key, value); +}); + +Meteor.defer(() => { + let baseUrl; + if (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX && __meteor_runtime_config__.ROOT_URL_PATH_PREFIX.trim() !== '') { + baseUrl = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX; } else { - Inject.rawHead('google-site-verification', ''); + baseUrl = '/'; + } + if (/\/$/.test(baseUrl) === false) { + baseUrl += '/'; } + Inject.rawHead('base', `<base href="${baseUrl}">`); }); + diff --git a/packages/rocketchat-ui-message/message/message.coffee b/packages/rocketchat-ui-message/message/message.coffee index df3f6cb50fffb74e086b4256f3860388b0726624..76590ba3e3400eb510f0f66446c7ade179fe1b7a 100644 --- a/packages/rocketchat-ui-message/message/message.coffee +++ b/packages/rocketchat-ui-message/message/message.coffee @@ -32,8 +32,11 @@ Template.message.helpers return 'temp' body: -> return Template.instance().body - system: -> + system: (returnClass) -> if RocketChat.MessageTypes.isSystemMessage(this) + if returnClass + return 'color-info-font-color' + return 'system' edited: -> return Template.instance().wasEdited @@ -128,7 +131,6 @@ Template.message.helpers hideReactions: -> return 'hidden' if _.isEmpty(@reactions) - actionLinks: -> # remove 'method_id' and 'params' properties return _.map(@actionLinks, (actionLink, key) -> _.extend({ id: key }, _.omit(actionLink, 'method_id', 'params'))) @@ -230,4 +232,4 @@ Template.message.onViewRendered = (context) -> else if templateInstance?.firstNode && templateInstance?.atBottom is false newMessage = templateInstance?.find(".new-message") - newMessage?.className = "new-message" + newMessage?.className = "new-message background-primary-action-color" diff --git a/packages/rocketchat-ui-message/message/message.html b/packages/rocketchat-ui-message/message/message.html index a5be3b79903fe2859f4eab06ead942f87b468ae3..cb42e24f478d17ce76992f10259c39f94c5b3789 100644 --- a/packages/rocketchat-ui-message/message/message.html +++ b/packages/rocketchat-ui-message/message/message.html @@ -1,5 +1,5 @@ <template name="message"> - <li id="{{_id}}" class="message {{isSequential}} {{system}} {{t}} {{own}} {{isTemp}} {{chatops}} {{customClass}}" data-username="{{u.username}}" data-groupable="{{isGroupable}}" data-date="{{date}}" data-timestamp="{{timestamp}}"> + <li id="{{_id}}" class="message background-transparent-dark-hover {{isSequential}} {{system}} {{t}} {{own}} {{isTemp}} {{chatops}} {{customClass}}" data-username="{{u.username}}" data-groupable="{{isGroupable}}" data-date="{{date}}" data-timestamp="{{timestamp}}"> <div class="day-divider"> <span>{{date}}</span> </div> @@ -25,16 +25,16 @@ {{/if}} {{/if}} {{#if alias}} - <button type="button" class="user user-card-message" data-username="{{u.username}}" tabindex="1">{{alias}} <span class="message-alias">@{{u.username}}</span></button> + <button type="button" class="user user-card-message color-primary-font-color" data-username="{{u.username}}" tabindex="1">{{alias}} <span class="message-alias border-component-color color-info-font-color">@{{u.username}}</span></button> {{else}} - <button type="button" class="user user-card-message" data-username="{{u.username}}" tabindex="1">{{u.username}}</button> + <button type="button" class="user user-card-message color-primary-font-color" data-username="{{u.username}}" tabindex="1">{{u.username}}</button> {{/if}} - <span class="info"> + <span class="info border-component-color color-info-font-color"> {{#each roleTags}} - <span class="role-tag" data-role="{{description}}">{{description}}</span> + <span class="role-tag background-info-font-color" data-role="{{description}}">{{description}}</span> {{/each}} {{#if isBot}} - <span class="is-bot">BOT</span> + <span class="is-bot background-info-font-color">BOT</span> {{/if}} <span class="time" title='{{date}} {{time}}'>{{time}}</span> {{#if edited}} @@ -50,7 +50,7 @@ <i class="icon-cog message-cog" aria-label="{{_ "Actions"}}"></i> </div> </span> - <div class="body" dir="auto"> + <div class="body color-primary-font-color {{system true}}" dir="auto"> {{{body}}} {{#if hasOembed}} {{#each urls}} @@ -63,7 +63,7 @@ </div> <ul class="actionLinks {{hideActionLinks}}"> {{#each actionLink in actionLinks}} - <li> + <li class="color-primary-action-color"> <span class="action-link" data-actionlink="{{actionLink.id}}"> {{#if actionLink.icon}} <i class="{{actionLink.icon}}"></i> diff --git a/packages/rocketchat-ui-message/message/messageBox.coffee b/packages/rocketchat-ui-message/message/messageBox.coffee index 869cbf1fc53ba5a8df1e33025cb33c9c2bf422b8..e3f31cb39cb3875d59d89855e74c036fe953e0fd 100644 --- a/packages/rocketchat-ui-message/message/messageBox.coffee +++ b/packages/rocketchat-ui-message/message/messageBox.coffee @@ -40,10 +40,17 @@ Template.messageBox.helpers roomData = Session.get('roomData' + this._id) if roomData?.t is 'd' - if ChatSubscription.findOne({ rid: this._id }, { fields: { archived: 1 } })?.archived + subscription = ChatSubscription.findOne({ rid: this._id }, { fields: { archived: 1, blocked: 1, blocker: 1 } }) + if subscription and (subscription.archived or subscription.blocked or subscription.blocker) return false return true + isBlockedOrBlocker: -> + roomData = Session.get('roomData' + this._id) + if roomData?.t is 'd' + subscription = ChatSubscription.findOne({ rid: this._id }, { fields: { blocked: 1, blocker: 1 } }) + if subscription and (subscription.blocked or subscription.blocker) + return true getPopupConfig: -> template = Template.instance() @@ -105,7 +112,7 @@ Template.messageBox.helpers return RocketChat.roomTypes.getNotSubscribedTpl @_id showSandstorm: -> - return Meteor.settings.public.sandstorm + return Meteor.settings.public.sandstorm && !Meteor.isCordova Template.messageBox.events @@ -191,7 +198,7 @@ Template.messageBox.events fileUpload filesToUpload - 'click .message-form .geo-location': (event, instance) -> + 'click .message-form .message-buttons.location': (event, instance) -> roomId = @_id position = RocketChat.Geolocation.get() diff --git a/packages/rocketchat-ui-message/message/messageBox.html b/packages/rocketchat-ui-message/message/messageBox.html index 80a3ac1d8378bc82706788b57b0786d8096ba1e7..65a36bf0e109781b7eac0577f0ae14b807191f8a 100644 --- a/packages/rocketchat-ui-message/message/messageBox.html +++ b/packages/rocketchat-ui-message/message/messageBox.html @@ -1,11 +1,11 @@ <template name="messageBox"> {{#if subscribed}} - <form class="message-form" method="post" action="/"> + <form class="message-form secondary-font-color" method="post" action="/"> {{> messagePopupConfig getPopupConfig}} {{#if allowedToSend}} - <div class="message-input"> - <div class="input-message-container"> - <textarea dir="auto" name="msg" maxlength="{{maxMessageLength}}" class="input-message autogrow-short" placeholder="{{_ 'Message'}}"></textarea> + <div class="message-input border-component-color"> + <div class="input-message-container content-background-color"> + <textarea dir="auto" name="msg" maxlength="{{maxMessageLength}}" class="message-form-text input-message autogrow-short" placeholder="{{_ 'Message'}}"></textarea> <div class="inner-left-toolbar"> <i class="emoji-picker-icon icon-people"></i> @@ -59,7 +59,11 @@ </div> {{else}} <div class="stream-info"> - {{_ "room_is_read_only"}} + {{#if isBlockedOrBlocker}} + {{_ "room_is_blocked"}} + {{else}} + {{_ "room_is_read_only"}} + {{/if}} </div> {{/if}} <div class="stream-info"> @@ -89,8 +93,8 @@ <span>~<strike>{{_ "strike"}}</strike>~</span> {{/if}} {{#if showMarkdownCode}} - <code class="inline">`{{_ "inline_code"}}`</code> - <code class="inline"><span class="hidden-br"><br></span>```<span class="hidden-br"><br></span><i class="icon-level-down"></i>{{_ "multi"}}<span class="hidden-br"><br></span><i class="icon-level-down"></i>{{_ "line"}}<span class="hidden-br"><br></span><i class="icon-level-down"></i>```</code> + <code class="code-colors inline">`{{_ "inline_code"}}`</code> + <code class="code-colors inline"><span class="hidden-br"><br></span>```<span class="hidden-br"><br></span><i class="icon-level-down"></i>{{_ "multi"}}<span class="hidden-br"><br></span><i class="icon-level-down"></i>{{_ "line"}}<span class="hidden-br"><br></span><i class="icon-level-down"></i>```</code> {{/if}} {{#if katexSyntax}} <span><a href="https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX" target="_blank">{{katexSyntax}}</a></span> diff --git a/packages/rocketchat-ui-message/message/messageDropdown.html b/packages/rocketchat-ui-message/message/messageDropdown.html index 93468e4bd65a7e6a8da086006cfd678b1e236725..aeef21c9abe01d9ba9f863897f77e827cd3cb21d 100644 --- a/packages/rocketchat-ui-message/message/messageDropdown.html +++ b/packages/rocketchat-ui-message/message/messageDropdown.html @@ -1,7 +1,7 @@ <template name="messageDropdown"> <div class="message-dropdown"> <ul> - <li class="message-dropdown-close"><i class=" icon-angle-left" aria-label="{{_ "Close"}}"></i></li> + <li class="message-dropdown-close secondary-background-color border-component-color"><i class=" icon-angle-left" aria-label="{{_ "Close"}}"></i></li> {{#if actions.length}} {{#each actions}} <li class="{{id}} {{classes}} message-action" title="{{_ i18nLabel}}" data-id="{{id}}"><i class="{{icon}}" aria-label="{{_ i18nLabel}}"></i></li> diff --git a/packages/rocketchat-ui-message/message/popup/messagePopup.html b/packages/rocketchat-ui-message/message/popup/messagePopup.html index 685ae84704424ba3fda7e1636cac18c4f75382a5..c81aff00d73f075504dc1c2945d45a11f5e3e99c 100644 --- a/packages/rocketchat-ui-message/message/popup/messagePopup.html +++ b/packages/rocketchat-ui-message/message/popup/messagePopup.html @@ -1,14 +1,13 @@ <template name="messagePopup"> {{#if isOpen}} <div class="message-popup-position"> - <div class="message-popup {{cls}}"> + <div class="message-popup content-background-color {{cls}}"> {{#if title}} - <div class="message-popup-title"> + <div class="message-popup-title secondary-background-color"> {{title}} </div> {{/if}} <div class="message-popup-items"> - {{> loading}} {{#each data}} <div class="popup-item" data-id="{{_id}}"> {{> Template.dynamic template=../template}} diff --git a/packages/rocketchat-ui-message/message/popup/messagePopupConfig.coffee b/packages/rocketchat-ui-message/message/popup/messagePopupConfig.coffee index cc5252b0ab23de21c1a05c3b8bcb152249759fb7..3cf38b4ffc6700a6ae9656eb1f679695cb19dc99 100644 --- a/packages/rocketchat-ui-message/message/popup/messagePopupConfig.coffee +++ b/packages/rocketchat-ui-message/message/popup/messagePopupConfig.coffee @@ -82,21 +82,21 @@ Template.messagePopupConfig.helpers status: item.status sort: 1 - # Get users of room - if items.length < 5 and filter?.trim() isnt '' - messageUsers = _.pluck(items, 'username') - Tracker.nonreactive -> - roomUsernames = RocketChat.models.Rooms.findOne(Session.get('openedRoom')).usernames - for roomUsername in roomUsernames - if messageUsers.indexOf(roomUsername) is -1 and exp.test(roomUsername) - items.push - _id: roomUsername - username: roomUsername - status: Session.get('user_' + roomUsername + '_status') or 'offline' - sort: 2 - - if items.length >= 5 - break + # # Get users of room + # if items.length < 5 and filter?.trim() isnt '' + # messageUsers = _.pluck(items, 'username') + # Tracker.nonreactive -> + # roomUsernames = RocketChat.models.Rooms.findOne(Session.get('openedRoom')).usernames + # for roomUsername in roomUsernames + # if messageUsers.indexOf(roomUsername) is -1 and exp.test(roomUsername) + # items.push + # _id: roomUsername + # username: roomUsername + # status: Session.get('user_' + roomUsername + '_status') or 'offline' + # sort: 2 + + # if items.length >= 5 + # break # Get users from db if items.length < 5 and filter?.trim() isnt '' diff --git a/packages/rocketchat-ui-message/message/popup/messagePopupUser.html b/packages/rocketchat-ui-message/message/popup/messagePopupUser.html index 48d84d6129d68255d765a6b2b9be3a224c22e08a..c90a04375d3de1a2647ed158467b871fa7f0274a 100644 --- a/packages/rocketchat-ui-message/message/popup/messagePopupUser.html +++ b/packages/rocketchat-ui-message/message/popup/messagePopupUser.html @@ -1,7 +1,7 @@ <template name="messagePopupUser"> {{#unless system}} - <div class="popup-user-status popup-user-status-{{status}}"></div> + <div class="popup-user-status border-transparent-dark popup-user-status-{{status}}"></div> <div class="popup-user-avatar" style="background-image:url({{avatarUrlFromUsername username}});"></div> {{/unless}} <strong>{{username}}</strong> {{name}} -</template> \ No newline at end of file +</template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/accountBox.coffee b/packages/rocketchat-ui-sidenav/side-nav/accountBox.coffee index 7d5e89e70cb561dbdec203b43f48bd7bef9fb571..b0f2bf2741b2ef312c5a2c1ed738e7d8cda7c6d7 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/accountBox.coffee +++ b/packages/rocketchat-ui-sidenav/side-nav/accountBox.coffee @@ -59,6 +59,9 @@ Template.accountBox.events AccountBox.openFlex() 'click .account-box-item': -> + if @href + FlowRouter.go @href + if @sideNav? SideNav.setFlex @sideNav SideNav.openFlex() diff --git a/packages/rocketchat-ui-sidenav/side-nav/accountBox.html b/packages/rocketchat-ui-sidenav/side-nav/accountBox.html index 5f79fea9a0b45eca8a018f36751a68739ab7d4e6..61282239834df5aaf8e83d8607dceb66e5ba96b4 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/accountBox.html +++ b/packages/rocketchat-ui-sidenav/side-nav/accountBox.html @@ -1,7 +1,7 @@ <template name="accountBox"> <div class="account-box" aria-label="{{_ "Account"}}" role="region"> {{#with myUserInfo}} - <div class="info status-{{status}}"> + <div class="info status-{{status}} primary-background-color"> {{#if username}} <div class="thumb" data-status='{{visualStatus}}'> {{> avatar username=username}} @@ -11,7 +11,7 @@ </div> {{/if}} </div> - <nav class="options animated-hidden"> + <nav class="options primary-background-color animated-hidden"> <div class="wrapper"> <button data-status="online" class="status online"><span>{{_ "Online"}}</span></button> <button data-status="away" class="status away"><span>{{_ "Away" context="male"}}</span></button> @@ -19,7 +19,7 @@ <button data-status="offline" class="status offline"><span>{{_ "Invisible"}}</span></button> <button id="account" class='account-link'><i class="icon-sliders"></i><span>{{_ "My_Account"}}</span></button> {{#each registeredMenus}} - <a href="{{pathFor href}}" class="account-box-item"><i class="{{icon}}"></i><span>{{name}}</span></a> + <button class="account-box-item"><i class="{{icon}}"></i><span>{{name}}</span></button> {{/each}} {{#if showAdminOption }} <button id="admin" class='account-link'><i class="icon-wrench"></i><span>{{_ "Administration"}}</span></button> diff --git a/packages/rocketchat-ui-sidenav/side-nav/channels.html b/packages/rocketchat-ui-sidenav/side-nav/channels.html index 746fc7bb51d0ace134dbbf617ef8408aae5515d4..9658c595dfce84b51bde22c561915df0feb4acd2 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/channels.html +++ b/packages/rocketchat-ui-sidenav/side-nav/channels.html @@ -1,5 +1,5 @@ <template name="channels"> - <h3 class="add-room {{isActive}}"> + <h3 class="add-room background-transparent-darker-hover {{isActive}}"> {{_ "Channels"}} <span class="room-count-small">({{rooms.count}})</span> {{#if canCreate}} <i class="icon-plus" aria-label="{{_ "Create channel"}}" role="button"></i> @@ -12,5 +12,5 @@ <p class="empty">{{_ "No_channels_yet" }}</p> {{/each}} </ul> - <button class="more more-channels">{{_ "More_channels"}}...</button> + <button class="more more-channels background-transparent-darker-hover">{{_ "More_channels"}}...</button> </template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.coffee b/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.coffee index fd7697f138cb255e629d2e96a2671f9493330600..99b126be34929f0f5fdba0d388ee1216cee79d33 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.coffee +++ b/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.coffee @@ -9,7 +9,8 @@ Template.chatRoomItem.helpers return this.unread userStatus: -> - return 'status-' + (Session.get('user_' + this.name + '_status') or 'offline') if this.t is 'd' + userStatus = RocketChat.roomTypes.getUserStatus(this.t, this.rid); + return 'status-' + (userStatus or 'offline') name: -> return this.name diff --git a/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.html b/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.html index c3bf57b7711440368f31cba18776f4eb25d8ed94..92c8f8c63abddec275d84a04eee0a13ed6354a77 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.html +++ b/packages/rocketchat-ui-sidenav/side-nav/chatRoomItem.html @@ -1,5 +1,5 @@ <template name="chatRoomItem"> - <li class="link-room-{{rid}} {{active}} {{#if unread}}has-unread{{/if}} {{#if alert}}has-alert{{/if}}"> + <li class="link-room-{{rid}} background-transparent-darker-hover {{active}} {{#if unread}}has-unread{{/if}} {{#if alert}}has-alert{{/if}}"> <a class="open-room" href="{{route}}" title="{{name}}"> {{#if unread}} <span class="unread">{{unread}}</span> diff --git a/packages/rocketchat-ui-sidenav/side-nav/combined.html b/packages/rocketchat-ui-sidenav/side-nav/combined.html index 25a19a812d5eb000950c418f5c0b1e63b4ec08e8..912120f588a2424d341371fe2e736c1ab1f9fb4c 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/combined.html +++ b/packages/rocketchat-ui-sidenav/side-nav/combined.html @@ -1,5 +1,5 @@ <template name="combined"> - <h3 class="add-room {{isActive}}"> + <h3 class="add-room background-transparent-darker-hover {{isActive}}"> {{_ "Channels"}} <span class="room-count-small">({{rooms.count}})</span> {{#if canCreate}} <i class="icon-plus" aria-label="{{_ "Create channel"}}" role="button"></i> @@ -12,5 +12,5 @@ <p class="empty">{{_ "No_channels_yet" }}</p> {{/each}} </ul> - <button class="more more-channels">{{_ "More_channels"}}...</button> + <button class="more more-channels background-transparent-darker-hover">{{_ "More_channels"}}...</button> </template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html b/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html index d188757768f49eb507351c1b16bf836e2dd477eb..346125416a1f9fa5e05a125199c79ca462b8e225 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/createChannelFlex.html @@ -1,31 +1,31 @@ <template name="createChannelFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Channels"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <h4>{{_ "Create_new" }}</h4> <div class="input-line"> - <label for="channel-name">{{_ "Name"}}</label> + <label class="color-tertiary-font-color" for="channel-name">{{_ "Name"}}</label> <input type="text" id="channel-name" class="required" dir="auto" placeholder="{{_ 'Enter_name_here'}}"> </div> <div class="input-line"> - <label for="channel-ro">{{_ "Read_only_channel"}}</label> + <label class="color-tertiary-font-color" for="channel-ro">{{_ "Read_only_channel"}}</label> <input type="checkbox" id="channel-ro"> </div> <div class="input-line"> - <label for="channel-members">{{_ "Select_users"}}</label> + <label class="color-tertiary-font-color" for="channel-members">{{_ "Select_users"}}</label> {{> inputAutocomplete settings=autocompleteSettings id="channel-members" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} <ul class="selected-users"> {{#each selectedUsers}} - <li>{{.}} <i class="icon-cancel remove-room-member"></i></li> + <li class="background-transparent-darker">{{.}} <i class="icon-cancel remove-room-member secondary-font-color"></i></li> {{/each}} </ul> </div> {{#if error.fields}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{#each error.fields}} <p>{{_ "The_field_is_required" .}}</p> @@ -33,19 +33,19 @@ </div> {{/if}} {{#if error.invalid}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Invalid_room_name" roomName}}} </div> {{/if}} {{#if error.duplicate}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Duplicate_channel_name" roomName}}} </div> {{/if}} {{#if error.archivedduplicate}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Duplicate_archived_channel_name" roomName}}} </div> @@ -56,7 +56,7 @@ </div> </div> </div> - <footer> + <footer class="primary-background-color"> <div> <button class="button all">{{_ "All_channels"}}</button> </div> diff --git a/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html b/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html index acb8bdb1349c7ae18830c43fb3c0d668fe905706..1b68e172105e7f0c7bcf37a2066c84eaeded733c 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html @@ -1,35 +1,41 @@ <template name="createCombinedFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Channels"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <h4>{{_ "Create_new" }}</h4> - <div class="input-line"> - <label for="channel-name">{{_ "Name"}}</label> + <div class="input-line no-icon"> + <span>{{_ "Name"}}</span> <input type="text" id="channel-name" class="required" dir="auto" placeholder="{{_ 'Enter_name_here'}}"> </div> - <div class="input-line"> - <label for="channel-type">{{_ "Private_Group"}}</label> - <input type="checkbox" id="channel-type" {{privateSwitchDisabled}} {{privateSwitchChecked}}> + <div class="input-line toggle"> + <span>{{_ "Private"}}</span> + <div class="input checkbox toggle"> + <input type="checkbox" id="channel-type" {{privateSwitchDisabled}} {{privateSwitchChecked}}> + <label class="color-tertiary-font-color" for="channel-type"></label> + </div> </div> - <div class="input-line"> - <label for="channel-ro">{{_ "Read_only_channel"}}</label> - <input type="checkbox" id="channel-ro"> + <div class="input-line toggle"> + <span>{{_ "Read_only_channel"}}</span> + <div class="input checkbox toggle"> + <input type="checkbox" id="channel-ro"> + <label class="color-tertiary-font-color" for="channel-ro"></label> + </div> </div> - <div class="input-line"> - <label for="channel-members">{{_ "Select_users"}}</label> + <div class="input-line no-icon"> + <label class="color-tertiary-font-color" for="channel-members">{{_ "Select_users"}}</label> {{> inputAutocomplete settings=autocompleteSettings id="channel-members" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} <ul class="selected-users"> {{#each selectedUsers}} - <li>{{.}} <i class="icon-cancel remove-room-member"></i></li> + <li class="background-transparent-darker">{{.}} <i class="icon-cancel remove-room-member"></i></li> {{/each}} </ul> </div> {{#if error.fields}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{#each error.fields}} <p>{{_ "The_field_is_required" .}}</p> @@ -37,19 +43,19 @@ </div> {{/if}} {{#if error.invalid}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Invalid_room_name" roomName}}} </div> {{/if}} {{#if error.duplicate}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Duplicate_channel_name" roomName}}} </div> {{/if}} {{#if error.archivedduplicate}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Duplicate_archived_channel_name" roomName}}} </div> @@ -60,7 +66,7 @@ </div> </div> </div> - <footer> + <footer class="primary-background-color"> <div> <button class="button all">{{_ "All_channels"}}</button> </div> diff --git a/packages/rocketchat-ui-sidenav/side-nav/directMessages.html b/packages/rocketchat-ui-sidenav/side-nav/directMessages.html index 9bcf597e859458999b927bc8f6fb44d691489d76..2e8fddcc8972bd85e89dc43ac59726c48e9e24ad 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/directMessages.html +++ b/packages/rocketchat-ui-sidenav/side-nav/directMessages.html @@ -1,5 +1,5 @@ <template name="directMessages"> - <h3 class="add-room {{isActive}}"> + <h3 class="add-room background-transparent-darker-hover {{isActive}}"> {{_ "Direct_Messages"}} <span class="room-count-small">({{rooms.count}})</span> {{#if canCreate}} <i class="icon-plus"></i> @@ -11,6 +11,6 @@ {{else}} <p class="empty">{{_ "No_direct_messages_yet" }}</p> {{/each}} - <button class="more more-direct-messages">{{_ "More_direct_messages"}}...</button> + <button class="more more-direct-messages background-transparent-darker-hover">{{_ "More_direct_messages"}}...</button> </ul> </template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/directMessagesFlex.html b/packages/rocketchat-ui-sidenav/side-nav/directMessagesFlex.html index 78dd378cac3a851d4e5b625097de9bfd425da75b..43b66dbbd715d499589fbee76283fa6665e202b2 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/directMessagesFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/directMessagesFlex.html @@ -1,18 +1,18 @@ <template name="directMessagesFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Direct_Messages"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <h4>{{_ "Create_new"}}</h4> - <div class="input-line"> - <label for="who">{{_ "Select_user" }}</label> + <div class="input-line no-icon"> + <label class="color-tertiary-font-color" for="who">{{_ "Select_user" }}</label> {{> inputAutocomplete settings=autocompleteSettings id="who" class="required search" autocomplete="off" placeholder=(_ "Search_by_username")}} </div> {{#if error}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{#each error}} <p>{{_ "The_field_is_required" error}}</p> diff --git a/packages/rocketchat-ui-sidenav/side-nav/listChannelsFlex.html b/packages/rocketchat-ui-sidenav/side-nav/listChannelsFlex.html index f4eef9e163454c673acb49173be0051ba5a337e1..8df15568b591b858fa4789cbc6069c6cc190f3a1 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/listChannelsFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/listChannelsFlex.html @@ -1,10 +1,10 @@ <template name="listChannelsFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Channels"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <div class="flex-control"> <div class="search"> @@ -14,7 +14,7 @@ <option value="joined" selected="{{showSelected 'joined'}}">{{_ "Joined"}}</option> <option value="all" selected="{{showSelected 'all'}}">{{_ "All"}}</option> </select> - <i class="icon-comment"></i> + <i class="icon-comment secondary-font-color"></i> </div> <div class="input-line search"> <select class="c-select" id="sort-channels"> @@ -25,46 +25,42 @@ <option value="name" selected="{{sortSubscriptionsSelected 'name'}}">{{_ "Name"}}</option> <option value="ls" selected="{{sortSubscriptionsSelected 'ls'}}">{{_ "Last_seen"}}</option> </select> - <i class="icon-sort-alt-up"></i> + <i class="icon-sort-alt-up secondary-font-color"></i> </div> <div class="input-line search"> <input type="text" id="channel-search" class="search" placeholder="{{_ "Search_Channels"}}" autocomplete="off" /> - <i class="icon-right-open-small"></i> + <i class="icon-right-open-small secondary-font-color"></i> </div> </form> </div> </div> <h4>{{_ "Channels_list"}}</h4> <ul> - {{#each channel}} - <li> - <a href="{{pathFor 'channel' name=name}}" class="channel-link"> - <i class="icon-hash"></i> - {{name}} - <span class='opt fixed'> - {{#if member}} - <i class="icon-eye" title="{{_ "Open"}}" aria-label="{{_ "Open"}}"></i> - {{/if}} - {{#if hidden}} - <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> - {{/if}} - </span> - </a> - </li> - {{/each}} + {{#each channel}} + <li> + <a href="{{pathFor 'channel' name=name}}" class="channel-link"> + <i class="icon-hash"></i> + {{name}} + <span class='opt fixed'> + {{#if member}} + <i class="icon-eye" title="{{_ "Open"}}" aria-label="{{_ "Open"}}"></i> + {{/if}} + {{#if hidden}} + <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> + {{/if}} + </span> + </a> + </li> + {{/each}} + </ul> {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> + <div class="load-more"> + {{> loading}} + </div> {{/if}} - </ul> </div> </div> - <footer> + <footer class="primary-background-color"> <div> {{#if hasPermission 'create-c'}} <button class="button primary create">{{_ "Create_new"}}</button> diff --git a/packages/rocketchat-ui-sidenav/side-nav/listCombinedFlex.html b/packages/rocketchat-ui-sidenav/side-nav/listCombinedFlex.html index 34225805643d453f5ed5a3001351e75ebb4ef4c9..371cd9ff32bcc54abb2776dcdcd15ff4022ab4af 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/listCombinedFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/listCombinedFlex.html @@ -1,10 +1,10 @@ <template name="listCombinedFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Channels"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <div class="flex-control"> <div class="search"> @@ -14,7 +14,7 @@ <option value="joined" selected="{{showSelected 'joined'}}">{{_ "Joined"}}</option> <option value="all" selected="{{showSelected 'all'}}">{{_ "All"}}</option> </select> - <i class="icon-comment"></i> + <i class="icon-comment secondary-font-color"></i> </div> <div class="input-line search"> <select class="c-select" id="sort-channels"> @@ -25,7 +25,7 @@ <option value="name" selected="{{sortSubscriptionsSelected 'name'}}">{{_ "Name"}}</option> <option value="ls" selected="{{sortSubscriptionsSelected 'ls'}}">{{_ "Last_seen"}}</option> </select> - <i class="icon-sort-alt-up"></i> + <i class="icon-sort-alt-up secondary-font-color"></i> </div> <div class="input-line search"> <select class="c-select" id="channel-type"> @@ -33,46 +33,42 @@ <option value="public" selected="{{channelTypeSelected 'public'}}">{{_ "Public"}}</option> <option value="private" selected="{{channelTypeSelected 'private'}}">{{_ "Private"}}</option> </select> - <i class="icon-lock"></i> + <i class="icon-lock secondary-font-color"></i> </div> <div class="input-line search"> - <input type="text" id="channel-search" class="search" placeholder="{{_ "Search_Channels"}}" autocomplete="off" /> - <i class="icon-right-open-small"></i> + <input type="text" id="channel-search" class="search" placeholder="{{_ "Search"}}" autocomplete="off" /> + <i class="icon-search secondary-font-color"></i> </div> </form> </div> </div> <h4>{{_ "List_of_Channels"}}</h4> <ul> - {{#each channel}} - <li> - <a href="{{pathFor url name=name}}" class="channel-link"> - <i class="{{roomIcon}}"></i> - {{name}} - <span class='opt fixed'> - {{#if member}} - <i class="icon-eye" title="{{_ "Open"}}" aria-label="{{_ "Open"}}"></i> - {{/if}} - {{#if hidden}} - <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> - {{/if}} - </span> - </a> - </li> - {{/each}} + {{#each channel}} + <li> + <a href="{{pathFor url name=name}}" class="channel-link"> + <i class="{{roomIcon}}"></i> + {{name}} + <span class='opt fixed'> + {{#if member}} + <i class="icon-eye" title="{{_ "Open"}}" aria-label="{{_ "Open"}}"></i> + {{/if}} + {{#if hidden}} + <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> + {{/if}} + </span> + </a> + </li> + {{/each}} + </ul> {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> + <div class="load-more"> + {{> loading}} + </div> {{/if}} - </ul> </div> </div> - <footer> + <footer class="primary-background-color"> <div> {{#if canCreate}} <button class="button primary create">{{_ "Create_new"}}</button> diff --git a/packages/rocketchat-ui-sidenav/side-nav/listDirectMessagesFlex.html b/packages/rocketchat-ui-sidenav/side-nav/listDirectMessagesFlex.html index 50fdf94ed23024169ad802a0a398af27882e8f22..c252bea71e441627e1303e26da8f8e26d28f9046 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/listDirectMessagesFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/listDirectMessagesFlex.html @@ -1,53 +1,49 @@ <template name="listDirectMessagesFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Direct_Messages"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <div class="flex-control"> <div class="search"> <form class="search-form" role="form"> <div class="input-line search"> - <input type="text" id="channel-search" class="search" placeholder="{{_ "Search_Direct_Messages"}}" autocomplete="off" /> - <i class="icon-right-open-small"></i> + <input type="text" id="channel-search" class="search" placeholder="{{_ "Search"}}" autocomplete="off" /> + <i class="icon-search secondary-font-color"></i> </div> <div class="input-line search"> <select class="c-select" id="sort"> <option value="name" selected="{{sortSelected 'name'}}">{{_ "Name"}}</option> <option value="ls" selected="{{sortSelected 'ls'}}">{{_ "Last_seen"}}</option> </select> - <i class="icon-sort-alt-up"></i> + <i class="icon-sort-alt-up secondary-font-color"></i> </div> </form> </div> </div> <h4>{{_ "List_of_Direct_Messages"}}</h4> <ul> - {{#each rooms}} - <li> - <a href="{{pathFor 'direct' username=name}}" class="channel-link"> - <i class="icon-at {{userStatus}}"></i> - {{name}} - <span class='opt fixed'> - {{#if hidden}} - <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> - {{/if}} - </span> - </a> - </li> - {{/each}} + {{#each rooms}} + <li> + <a href="{{pathFor 'direct' username=name}}" class="channel-link"> + <i class="icon-at {{userStatus}}"></i> + {{name}} + <span class='opt fixed'> + {{#if hidden}} + <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> + {{/if}} + </span> + </a> + </li> + {{/each}} + </ul> {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> + <div class="load-more"> + {{> loading}} + </div> {{/if}} - </ul> </div> </div> </template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.html b/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.html index 6a43dcf50df9432245c4563f190798bddc27e533..f664e26c66693869886b603749333a3e468e2e66 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/listPrivateGroupsFlex.html @@ -1,10 +1,10 @@ <template name="listPrivateGroupsFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Private_Groups"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <div class="flex-control"> <div class="search"> @@ -14,43 +14,39 @@ <option value="name" selected="{{sortSelected 'name'}}">{{_ "Name"}}</option> <option value="ls" selected="{{sortSelected 'ls'}}">{{_ "Last_seen"}}</option> </select> - <i class="icon-sort-alt-up"></i> + <i class="icon-sort-alt-up secondary-font-color"></i> </div> <div class="input-line search"> <input type="text" id="channel-search" class="search" placeholder="{{_ "Search_Private_Groups"}}" autocomplete="off" /> - <i class="icon-right-open-small"></i> + <i class="icon-right-open-small secondary-font-color"></i> </div> </form> </div> </div> <h4>{{_ "Private_Groups_list"}}</h4> <ul> - {{#each groups}} - <li> - <a href="{{pathFor 'group' name=name}}" class="channel-link"> - <i class="icon-lock"></i> - {{name}} - <span class='opt fixed'> - {{#if hidden}} - <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> - {{/if}} - </span> - </a> - </li> - {{/each}} + {{#each groups}} + <li> + <a href="{{pathFor 'group' name=name}}" class="channel-link"> + <i class="icon-lock"></i> + {{name}} + <span class='opt fixed'> + {{#if hidden}} + <i class="icon-eye-off" title="{{_ "Hidden"}}" aria-label="{{_ "Hidden"}}"></i> + {{/if}} + </span> + </a> + </li> + {{/each}} + </ul> {{#if hasMore}} - <li class="load-more"> - {{#if Template.subscriptionsReady}} - <button>{{_ "Has_more"}}...</button> - {{else}} - <div class="load-more-loading">{{_ "Loading..."}}</div> - {{/if}} - </li> + <div class="load-more"> + {{> loading}} + </div> {{/if}} - </ul> </div> </div> - <footer> + <footer class="primary-background-color"> <div> <button class="button primary create">{{_ "Create_new"}}</button> </div> diff --git a/packages/rocketchat-ui-sidenav/side-nav/privateGroups.html b/packages/rocketchat-ui-sidenav/side-nav/privateGroups.html index 9238e93da07229daa7b19a79730210d8d89af920..f23c44a824f9612ea07df696382c03544873fc6d 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/privateGroups.html +++ b/packages/rocketchat-ui-sidenav/side-nav/privateGroups.html @@ -1,5 +1,5 @@ <template name="privateGroups"> - <h3 class="add-room {{isActive}}"> + <h3 class="add-room background-transparent-darker-hover {{isActive}}"> {{_ "Private_Groups"}} <span class="room-count-small">({{rooms.count}})</span> {{#if canCreate}} <i class="icon-plus"></i> @@ -13,6 +13,6 @@ {{/each}} </ul> {{#if $gt total totalOpen}} - <button class="more more-groups">{{_ "More_groups"}}...</button> + <button class="more more-groups background-transparent-darker-hover">{{_ "More_groups"}}...</button> {{/if}} </template> diff --git a/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html b/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html index a434b28ce8b977218ae55fd6d1ea8f5665bddefb..b44af2de8eb56b7629e86588078b232f72e4fd23 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/privateGroupsFlex.html @@ -1,31 +1,31 @@ <template name="privateGroupsFlex"> - <header> + <header class="primary-background-color"> <div> <h4>{{_ "Private_Groups"}}</h4> </div> </header> - <div class="content"> + <div class="content primary-background-color"> <div class="wrapper"> <h4>{{_ "Create_new"}}</h4> <div class="input-line"> - <label for="pvt-group-name">{{_ "Name"}}</label> + <label class="color-tertiary-font-color" for="pvt-group-name">{{_ "Name"}}</label> <input type="text" id="pvt-group-name" class="required" dir="auto" placeholder="{{_ 'Enter_name_here'}}"> </div> <div class="input-line"> - <label for="channel-ro">{{_ "Read_only_group"}}</label> + <label class="color-tertiary-font-color" for="channel-ro">{{_ "Read_only_group"}}</label> <input type="checkbox" id="channel-ro"> </div> <div class="input-line"> - <label for="pvt-group-members">{{_ "Select_users" }}</label> + <label class="color-tertiary-font-color" for="pvt-group-members">{{_ "Select_users" }}</label> {{> inputAutocomplete settings=autocompleteSettings id="pvt-group-members" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} <ul class="selected-users"> {{#each selectedUsers}} - <li>{{.}} <i class="icon-cancel remove-room-member"></i></li> + <li class="background-transparent-darker">{{.}} <i class="icon-cancel remove-room-member"></i></li> {{/each}} </ul> </div> {{#if error.fields}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{#each error.fields}} <p>{{_ "The_field_is_required" .}}</p> @@ -33,19 +33,19 @@ </div> {{/if}} {{#if error.invalid}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Invalid_room_name" groupName}}} </div> {{/if}} {{#if error.duplicate}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Duplicate_private_group_name" groupName}}} </div> {{/if}} {{#if error.archivedduplicate}} - <div class="input-error"> + <div class="input-error error-color"> <strong>{{_ "Oops!"}}</strong> {{{_ "Duplicate_archived_private_group_name" groupName}}} </div> diff --git a/packages/rocketchat-ui-sidenav/side-nav/sideNav.html b/packages/rocketchat-ui-sidenav/side-nav/sideNav.html index b728c3433827de557d4020068fc02a8718baec01..4d15c91a242b900bdb16382aea6ab6e69b1a77f8 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/sideNav.html +++ b/packages/rocketchat-ui-sidenav/side-nav/sideNav.html @@ -1,15 +1,15 @@ <template name="sideNav"> - <aside class="side-nav" role="navigation"> + <aside class="side-nav primary-background-color color-tertiary-font-color" role="navigation"> <header class="header"> {{> accountBox }} </header> {{#if currentUser}} - <div class="unread-rooms top-unread-rooms hidden"> + <div class="unread-rooms background-primary-action-color color-primary-action-contrast top-unread-rooms hidden"> {{_ "More_unreads"}} <i class="icon-up-big"></i> </div> <div class="rooms-list" aria-label="{{_ "Channels"}}" role="region"> <div class="wrapper"> - {{ > unreadRooms }} + {{> unreadRooms }} {{#each roomType}} {{#if canShowRoomType}} @@ -24,10 +24,10 @@ {{/if}} </div> </div> - <div class="unread-rooms bottom-unread-rooms hidden"> + <div class="unread-rooms background-primary-action-color color-primary-action-contrast bottom-unread-rooms hidden"> {{_ "More_unreads"}} <i class="icon-down-big"></i> </div> - <div class="flex-nav animated-hidden"> + <div class="flex-nav primary-background-color animated-hidden"> <section> {{> Template.dynamic template=flexTemplate data=flexData}} </section> diff --git a/packages/rocketchat-ui-sidenav/side-nav/userStatus.html b/packages/rocketchat-ui-sidenav/side-nav/userStatus.html index 66dc5a7e632c925f63496c57f81e04c778579bd0..b92728ef1379afe08788f2149420f974e356a4af 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/userStatus.html +++ b/packages/rocketchat-ui-sidenav/side-nav/userStatus.html @@ -1,7 +1,7 @@ <template name="userStatus"> <div class="account-box"> {{#with myUserInfo}} - <div class="info status-{{status}}"> + <div class="info status-{{status}} primary-background-color"> {{#if username}} <div class="thumb" data-status='{{visualStatus}}'> {{> avatar username=username}} @@ -11,7 +11,7 @@ </div> {{/if}} </div> - <nav class="options animated-hidden"> + <nav class="options primary-background-color animated-hidden"> <div class="wrapper"> <button data-status="online" class="status online"><span>{{_ "Online"}}</span></button> <button data-status="away" class="status away"><span>{{_ "Away" context="male"}}</span></button> diff --git a/packages/rocketchat-ui-vrecord/client/vrecord.html b/packages/rocketchat-ui-vrecord/client/vrecord.html index 3b4046a4e4c19476e6a38b7c5565f749c71f4eba..881a941a01d4a35e11d90f416429acaa5cd8f539 100644 --- a/packages/rocketchat-ui-vrecord/client/vrecord.html +++ b/packages/rocketchat-ui-vrecord/client/vrecord.html @@ -1,16 +1,16 @@ <template name="vrecDialog"> - <div class="vrec-dialog"> + <div class="vrec-dialog secondary-background-color"> <div class="video-container"> <video width="320" height="240" src=""></video> </div> <div class="buttons"> <ul> - <li class="left-aligned"> + <li class="left-aligned border-secondary-background-color"> <button class="button primary record {{recordDisabled}}" {{recordDisabled}}> <i class="{{recordIcon}}"></i> </button> </li> - <li class="right-aligned"> + <li class="right-aligned border-secondary-background-color"> <button class="button cancel">{{_ "Cancel"}}</button> <button class="button primary ok {{okDisabled}}" {{okDisabled}}>{{_ "Ok"}}</button> </li> diff --git a/packages/rocketchat-ui-vrecord/client/vrecord.less b/packages/rocketchat-ui-vrecord/client/vrecord.less index 71a8892c6395b8517a5b3c1f7b8b7912a9695a4a..5bfd1d2f73d8857f686df2aadfb64a1662d58b25 100644 --- a/packages/rocketchat-ui-vrecord/client/vrecord.less +++ b/packages/rocketchat-ui-vrecord/client/vrecord.less @@ -2,9 +2,10 @@ opacity: 0; visibility: hidden; position: absolute; - background-color: @secondary-background-color; border-radius: 5px; - box-shadow: 0px 1px 1px 0 rgba(0,0,0,0.2), 0 2px 10px 0 rgba(0,0,0,.16); + box-shadow: + 0 1px 1px 0 rgba(0, 0, 0, 0.2), + 0 2px 10px 0 rgba(0, 0, 0, 0.16); &.show { opacity: 1; @@ -25,20 +26,21 @@ display: table-cell; margin: 0 2px; padding: 6px 0; - - border-bottom: 2px solid @secondary-background-color; + border-bottom-width: 2px; &.right-aligned { text-align: right; } + &.left-aligned { text-align: left; } } } } + .video-container { - padding: 5px 5px 5px 5px; + padding: 5px; } } diff --git a/packages/rocketchat-ui-vrecord/loadStylesheet.coffee b/packages/rocketchat-ui-vrecord/loadStylesheet.coffee deleted file mode 100644 index 62be80a50dd00c377a17c606e42654566eb0a6ea..0000000000000000000000000000000000000000 --- a/packages/rocketchat-ui-vrecord/loadStylesheet.coffee +++ /dev/null @@ -1,2 +0,0 @@ -RocketChat.theme.addPackageAsset -> - return Assets.getText 'client/vrecord.less' diff --git a/packages/rocketchat-ui-vrecord/package.js b/packages/rocketchat-ui-vrecord/package.js index eb8101001eef70059a28801ec63eaa4831fd43d7..309ac94c2904cb99ced38d2fb10866e1b2ed9cd6 100644 --- a/packages/rocketchat-ui-vrecord/package.js +++ b/packages/rocketchat-ui-vrecord/package.js @@ -18,12 +18,11 @@ Package.onUse(function(api) { ]); - api.addAssets('client/vrecord.less', 'server'); + api.addFiles('client/vrecord.less', 'client'); api.addFiles('client/vrecord.html', 'client'); api.addFiles('client/vrecord.coffee', 'client'); api.addFiles('client/VRecDialog.coffee', 'client'); api.addFiles('server/settings.coffee', 'server'); - api.addFiles('loadStylesheet.coffee', 'server'); }); diff --git a/packages/rocketchat-ui/lib/RoomManager.coffee b/packages/rocketchat-ui/lib/RoomManager.coffee index e5d0eb489d7328d5791ea970cdf61babe81a3f69..99296113cb0e0bda88cb1433f3587d870669f1f4 100644 --- a/packages/rocketchat-ui/lib/RoomManager.coffee +++ b/packages/rocketchat-ui/lib/RoomManager.coffee @@ -57,10 +57,6 @@ Tracker.autorun -> close = (typeName) -> if openedRooms[typeName] - if openedRooms[typeName].sub? - for sub in openedRooms[typeName].sub - sub.stop() - if openedRooms[typeName].rid? msgStream.removeAllListeners openedRooms[typeName].rid RocketChat.Notifications.unRoom openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream @@ -87,14 +83,10 @@ Tracker.autorun -> unless user?.username return - record.sub = [ - Meteor.subscribe 'room', typeName - ] - if record.ready is true return - ready = record.sub[0].ready() and CachedChatSubscription.ready.get() is true + ready = CachedChatRoom.ready.get() and CachedChatSubscription.ready.get() is true if ready is true type = typeName.substr(0, 1) @@ -232,9 +224,9 @@ Tracker.autorun -> topOffset = $(item).offset().top + scrollTop percent = 100 / totalHeight * topOffset if $(item).hasClass('mention-link-all') - ticksBar.append('<div class="tick tick-all" style="top: '+percent+'%;"></div>') + ticksBar.append('<div class="tick background-attention-color" style="top: '+percent+'%;"></div>') else - ticksBar.append('<div class="tick" style="top: '+percent+'%;"></div>') + ticksBar.append('<div class="tick background-primary-action-color" style="top: '+percent+'%;"></div>') open: open close: close @@ -252,3 +244,4 @@ Tracker.autorun -> RocketChat.callbacks.add 'afterLogoutCleanUp', -> RoomManager.closeAllRooms() +, RocketChat.callbacks.priority.MEDIUM, 'roommanager-after-logout-cleanup' diff --git a/packages/rocketchat-ui/lib/codeMirror/codeMirror.js b/packages/rocketchat-ui/lib/codeMirror/codeMirror.js new file mode 100644 index 0000000000000000000000000000000000000000..61991d3d72c14aa378b0cac5cea253226f63a085 --- /dev/null +++ b/packages/rocketchat-ui/lib/codeMirror/codeMirror.js @@ -0,0 +1,160 @@ +import './codeMirrorComponent.html'; +import './codeMirrorComponent.js'; + +import 'codemirror/addon/fold/brace-fold.js'; +import 'codemirror/addon/fold/comment-fold.js'; +import 'codemirror/addon/fold/foldcode.js'; +import 'codemirror/addon/fold/foldgutter.css'; +import 'codemirror/addon/fold/foldgutter.js'; +import 'codemirror/addon/fold/indent-fold.js'; +import 'codemirror/addon/fold/markdown-fold.js'; +import 'codemirror/addon/fold/xml-fold.js'; + +// lints +// import 'codemirror/addon/lint/jsonlint.js'; +// import 'codemirror/addon/lint/jshint.js'; +// import 'codemirror/addon/lint/csslint.js'; +// import 'codemirror/addon/lint/css-lint.js'; +// import 'codemirror/addon/lint/html-lint.js'; +// import 'codemirror/addon/lint/javascript-lint.js'; +// import 'codemirror/addon/lint/json-lint.js'; +// import 'codemirror/addon/lint/yaml-lint.js'; +// import 'codemirror/addon/lint/lint.css'; +// import 'codemirror/addon/lint/lint.js'; + +// active line mode +import 'codemirror/addon/selection/active-line.js'; + +// search/replace +import 'codemirror/addon/search/search.js'; +import 'codemirror/addon/search/searchcursor.js'; +import 'codemirror/addon/dialog/dialog.js'; +import 'codemirror/addon/dialog/dialog.css'; + +// overlay: required by `gfm.js` +import 'codemirror/addon/mode/overlay.js'; + +// markdown list continuation; nice complement for gfm +import 'codemirror/addon/edit/continuelist.js'; + +// modes +import 'codemirror/mode/apl/apl.js'; +import 'codemirror/mode/asterisk/asterisk.js'; +import 'codemirror/mode/clike/clike.js'; +import 'codemirror/mode/clojure/clojure.js'; +import 'codemirror/mode/cobol/cobol.js'; +import 'codemirror/mode/commonlisp/commonlisp.js'; +import 'codemirror/mode/coffeescript/coffeescript.js'; +import 'codemirror/mode/css/css.js'; +import 'codemirror/mode/cypher/cypher.js'; +import 'codemirror/mode/d/d.js'; +import 'codemirror/mode/diff/diff.js'; +import 'codemirror/mode/django/django.js'; +import 'codemirror/mode/dockerfile/dockerfile.js'; +import 'codemirror/mode/dtd/dtd.js'; +import 'codemirror/mode/dylan/dylan.js'; +import 'codemirror/mode/ecl/ecl.js'; +import 'codemirror/mode/eiffel/eiffel.js'; +import 'codemirror/mode/erlang/erlang.js'; +import 'codemirror/mode/fortran/fortran.js'; +import 'codemirror/mode/gas/gas.js'; +import 'codemirror/mode/gfm/gfm.js'; +import 'codemirror/mode/gherkin/gherkin.js'; +import 'codemirror/mode/go/go.js'; +import 'codemirror/mode/groovy/groovy.js'; +import 'codemirror/mode/haml/haml.js'; +import 'codemirror/mode/haskell/haskell.js'; +import 'codemirror/mode/haxe/haxe.js'; +import 'codemirror/mode/htmlembedded/htmlembedded.js'; +import 'codemirror/mode/htmlmixed/htmlmixed.js'; +import 'codemirror/mode/http/http.js'; +import 'codemirror/mode/idl/idl.js'; +import 'codemirror/mode/javascript/javascript.js'; +import 'codemirror/mode/jinja2/jinja2.js'; +import 'codemirror/mode/julia/julia.js'; +import 'codemirror/mode/livescript/livescript.js'; +import 'codemirror/mode/lua/lua.js'; +import 'codemirror/mode/markdown/markdown.js'; +import 'codemirror/mode/mirc/mirc.js'; +import 'codemirror/mode/mllike/mllike.js'; +import 'codemirror/mode/modelica/modelica.js'; +import 'codemirror/mode/nginx/nginx.js'; +import 'codemirror/mode/ntriples/ntriples.js'; +import 'codemirror/mode/octave/octave.js'; +import 'codemirror/mode/pascal/pascal.js'; +import 'codemirror/mode/pegjs/pegjs.js'; +import 'codemirror/mode/perl/perl.js'; +import 'codemirror/mode/php/php.js'; +import 'codemirror/mode/pig/pig.js'; +import 'codemirror/mode/properties/properties.js'; +import 'codemirror/mode/puppet/puppet.js'; +import 'codemirror/mode/python/python.js'; +import 'codemirror/mode/q/q.js'; +import 'codemirror/mode/r/r.js'; +import 'codemirror/mode/rpm/rpm.js'; +import 'codemirror/mode/rst/rst.js'; +import 'codemirror/mode/ruby/ruby.js'; +import 'codemirror/mode/rust/rust.js'; +import 'codemirror/mode/sass/sass.js'; +import 'codemirror/mode/scheme/scheme.js'; +import 'codemirror/mode/shell/shell.js'; +import 'codemirror/mode/sieve/sieve.js'; +import 'codemirror/mode/slim/slim.js'; +import 'codemirror/mode/smalltalk/smalltalk.js'; +import 'codemirror/mode/smarty/smarty.js'; +import 'codemirror/mode/solr/solr.js'; +import 'codemirror/mode/sparql/sparql.js'; +import 'codemirror/mode/sql/sql.js'; +import 'codemirror/mode/stex/stex.js'; +import 'codemirror/mode/tcl/tcl.js'; +import 'codemirror/mode/textile/textile.js'; +import 'codemirror/mode/tiddlywiki/tiddlywiki.js'; +import 'codemirror/mode/tiki/tiki.js'; +import 'codemirror/mode/toml/toml.js'; +import 'codemirror/mode/tornado/tornado.js'; +import 'codemirror/mode/turtle/turtle.js'; +import 'codemirror/mode/vb/vb.js'; +import 'codemirror/mode/vbscript/vbscript.js'; +import 'codemirror/mode/velocity/velocity.js'; +import 'codemirror/mode/verilog/verilog.js'; +import 'codemirror/mode/xml/xml.js'; +import 'codemirror/mode/xquery/xquery.js'; +import 'codemirror/mode/yaml/yaml.js'; +import 'codemirror/mode/z80/z80.js'; + +// themes +// import 'codemirror/theme/3024-day.css'; +// import 'codemirror/theme/3024-night.css'; +// import 'codemirror/theme/ambiance-mobile.css'; +// import 'codemirror/theme/ambiance.css'; +// import 'codemirror/theme/base16-dark.css'; +// import 'codemirror/theme/base16-light.css'; +// import 'codemirror/theme/blackboard.css'; +// import 'codemirror/theme/cobalt.css'; +// import 'codemirror/theme/eclipse.css'; +// import 'codemirror/theme/elegant.css'; +// import 'codemirror/theme/erlang-dark.css'; +// import 'codemirror/theme/lesser-dark.css'; +// import 'codemirror/theme/mbo.css'; +// import 'codemirror/theme/mdn-like.css'; +// import 'codemirror/theme/midnight.css'; +// import 'codemirror/theme/monokai.css'; +// import 'codemirror/theme/neat.css'; +// import 'codemirror/theme/neo.css'; +// import 'codemirror/theme/night.css'; +// import 'codemirror/theme/paraiso-dark.css'; +// import 'codemirror/theme/paraiso-light.css'; +// import 'codemirror/theme/pastel-on-dark.css'; +// import 'codemirror/theme/rubyblue.css'; +// import 'codemirror/theme/solarized.css'; +// import 'codemirror/theme/the-matrix.css'; +// import 'codemirror/theme/tomorrow-night-eighties.css'; +// import 'codemirror/theme/twilight.css'; +// import 'codemirror/theme/vibrant-ink.css'; +// import 'codemirror/theme/xq-dark.css'; +// import 'codemirror/theme/xq-light.css'; + +// key bindings +// import 'codemirror/keymap/emacs.js'; +import 'codemirror/keymap/sublime.js'; +// import 'codemirror/keymap/vim.js'; diff --git a/packages/rocketchat-ui/lib/codeMirror/codeMirrorComponent.html b/packages/rocketchat-ui/lib/codeMirror/codeMirrorComponent.html new file mode 100644 index 0000000000000000000000000000000000000000..143889e8e198e059c67a55fd3cb6748dcbd41c23 --- /dev/null +++ b/packages/rocketchat-ui/lib/codeMirror/codeMirrorComponent.html @@ -0,0 +1,3 @@ +<template name="CodeMirror"> + <textarea id="{{editorId}}" name="{{editorName}}" style="display: none">{{code}}</textarea> +</template> diff --git a/packages/rocketchat-ui/lib/codeMirror/codeMirrorComponent.js b/packages/rocketchat-ui/lib/codeMirror/codeMirrorComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..3508c89cc565a7499d5abe2ea82c879745494c4a --- /dev/null +++ b/packages/rocketchat-ui/lib/codeMirror/codeMirrorComponent.js @@ -0,0 +1,50 @@ +/* global CodeMirrors */ +CodeMirrors = {}; + +import 'codemirror/lib/codemirror.css'; +import CodeMirror from 'codemirror/lib/codemirror.js'; + +Template.CodeMirror.rendered = function() { + var options = this.data.options || { lineNumbers: true }; + var textarea = this.find('textarea'); + var editor = CodeMirror.fromTextArea(textarea, options); + + CodeMirrors[this.data.id || 'code-mirror-textarea'] = editor; + + var self = this; + editor.on('change', function(doc) { + var val = doc.getValue(); + textarea.value = val; + if (self.data.reactiveVar) { + Session.set(self.data.reactiveVar, val); + } + }); + + if (this.data.reactiveVar) { + this.autorun(function() { + var val = Session.get(self.data.reactiveVar) || ''; + if (val !== editor.getValue()) { + editor.setValue(val); + } + }); + } + + Meteor.defer(function() { + editor.refresh(); + }); +}; + +Template.CodeMirror.destroyed = function() { + delete CodeMirrors[this.data.id || 'code-mirror-textarea']; + this.$('#' + (this.data.id || 'code-mirror-textarea')).next('.CodeMirror').remove(); +}; + +Template.CodeMirror.helpers({ + editorId() { + return this.id || 'code-mirror-textarea'; + }, + + editorName() { + return this.name || 'code-mirror-textarea'; + } +}); diff --git a/packages/rocketchat-ui/lib/collections.coffee b/packages/rocketchat-ui/lib/collections.coffee index 7ee8ca9ab69ff3fadeb768fcb3a3aa4f31c08a9e..3f32ee66eea03ea67d799a7c4cf691a5977bf738 100644 --- a/packages/rocketchat-ui/lib/collections.coffee +++ b/packages/rocketchat-ui/lib/collections.coffee @@ -1,5 +1,6 @@ @ChatMessage = new Meteor.Collection null -@ChatRoom = new Meteor.Collection 'rocketchat_room' +@CachedChatRoom = new RocketChat.CachedCollection({ name: 'rooms', initOnLogin: true }) +@ChatRoom = CachedChatRoom.collection @CachedChatSubscription = new RocketChat.CachedCollection({ name: 'subscriptions', initOnLogin: true }) @ChatSubscription = CachedChatSubscription.collection diff --git a/packages/rocketchat-ui/lib/cordova/keyboard-fix.coffee b/packages/rocketchat-ui/lib/cordova/keyboard-fix.coffee index 9b45da2a5e6b4776264c70effb57fd0d17da4aec..119946b249905fe86bc1efae6ff72eef949dc3c9 100644 --- a/packages/rocketchat-ui/lib/cordova/keyboard-fix.coffee +++ b/packages/rocketchat-ui/lib/cordova/keyboard-fix.coffee @@ -8,7 +8,6 @@ if Meteor.isCordova if device?.platform.toLowerCase() isnt 'android' if Meteor.userId()? $('.main-content').css 'height', window.innerHeight - $('.mobile-message-menu').css 'height', window.innerHeight $('.sweet-alert').css 'transform', "translateY(-#{(document.height - window.innerHeight)/2}px)" $('.sweet-alert').css '-webkit-transform', "translateY(-#{(document.height - window.innerHeight)/2}px)" else @@ -19,9 +18,8 @@ if Meteor.isCordova if device?.platform.toLowerCase() isnt 'android' if Meteor.userId()? $('.main-content').css 'height', window.innerHeight - $('.mobile-message-menu').css 'height', window.innerHeight $('.sweet-alert').css 'transform', '' $('.sweet-alert').css '-webkit-transform', '' else $(document.body).css 'height', window.innerHeight - $(document.body).css 'overflow', 'visible' \ No newline at end of file + $(document.body).css 'overflow', 'visible' diff --git a/packages/rocketchat-ui/lib/fileUpload.coffee b/packages/rocketchat-ui/lib/fileUpload.coffee index e23f8762497033810bfca2722e39a5d316276ce9..014b88d2d5f0d1a304cc97a37892acb33be39252 100644 --- a/packages/rocketchat-ui/lib/fileUpload.coffee +++ b/packages/rocketchat-ui/lib/fileUpload.coffee @@ -49,7 +49,10 @@ readAsArrayBuffer = (file, callback) -> Your browser does not support the audio element. </audio> </div> - <div class='upload-preview-title'>#{Handlebars._escape(file.name)}</div> + <div class='upload-preview-title'> + <input id='file-name' style='display: inherit;' value='#{Handlebars._escape(file.name)}' placeholder='#{t("Upload_file_name")}'> + <input id='file-description' style='display: inherit;' value='' placeholder='#{t("Upload_file_description")}'> + </div> """ else if file.type is 'video' text = """ @@ -59,14 +62,20 @@ readAsArrayBuffer = (file, callback) -> Your browser does not support the video element. </video> </div> - <div class='upload-preview-title'>#{Handlebars._escape(file.name)}</div> + <div class='upload-preview-title'> + <input id='file-name' style='display: inherit;' value='#{Handlebars._escape(file.name)}' placeholder='#{t("Upload_file_name")}'> + <input id='file-description' style='display: inherit;' value='' placeholder='#{t("Upload_file_description")}'> + </div> """ else text = """ <div class='upload-preview'> <div class='upload-preview-file' style='background-image: url(#{fileContent})'></div> </div> - <div class='upload-preview-title'>#{Handlebars._escape(file.name)}</div> + <div class='upload-preview-title'> + <input id='file-name' style='display: inherit;' value='#{Handlebars._escape(file.name)}' placeholder='#{t("Upload_file_name")}'> + <input id='file-description' style='display: inherit;' value='' placeholder='#{t("Upload_file_description")}'> + </div> """ swal @@ -78,15 +87,15 @@ readAsArrayBuffer = (file, callback) -> html: true , (isConfirm) -> consume() - if isConfirm isnt true return record = - name: file.name or file.file.name + name: document.getElementById('file-name').value or file.name or file.file.name size: file.file.size type: file.file.type rid: roomId + description: document.getElementById('file-description').value upload = fileUploadHandler record, file.file diff --git a/packages/rocketchat-ui/lib/getAvatarUrlFromUsername.coffee b/packages/rocketchat-ui/lib/getAvatarUrlFromUsername.coffee index 392e2ebfe4aa040af9ed6c79ec699016ade1727c..90761f6b720d0bc8f03577bbc913eb2827be1e93 100644 --- a/packages/rocketchat-ui/lib/getAvatarUrlFromUsername.coffee +++ b/packages/rocketchat-ui/lib/getAvatarUrlFromUsername.coffee @@ -1,10 +1,19 @@ @getAvatarUrlFromUsername = (username) -> key = "avatar_random_#{username}" random = Session?.keys[key] or 0 + if not username? return - if Meteor.isCordova - path = Meteor.absoluteUrl().replace /\/$/, '' + + cdnPrefix = (RocketChat.settings.get('CDN_PREFIX') or '').trim().replace(/\/$/, '') + pathPrefix = (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX or '').trim().replace(/\/$/, '') + + if cdnPrefix + path = cdnPrefix + pathPrefix + else if Meteor.isCordova + # Meteor.absoluteUrl alread has path prefix + path = Meteor.absoluteUrl().replace(/\/$/, '') else - path = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; - "#{path}/avatar/#{encodeURIComponent(username)}.jpg?_dc=#{random}" + path = pathPrefix + + return "#{path}/avatar/#{encodeURIComponent(username)}?_dc=#{random}" diff --git a/packages/rocketchat-ui/lib/iframeCommands.js b/packages/rocketchat-ui/lib/iframeCommands.js index 4e74ca1ff5a064a86a164280f7e9ae616bdb7a4c..5ff57ff1807318c76f87ab6b3102b919715672da 100644 --- a/packages/rocketchat-ui/lib/iframeCommands.js +++ b/packages/rocketchat-ui/lib/iframeCommands.js @@ -19,12 +19,17 @@ const commands = { }, event.origin); }; - if (typeof data.service === 'string') { + const siteUrl = Meteor.settings.Site_Url + '/'; + if (typeof data.redirectUrl !== 'string' || !data.redirectUrl.startsWith(siteUrl)) { + data.redirectUrl = null; + } + + if (typeof data.service === 'string' && window.ServiceConfiguration) { const customOauth = ServiceConfiguration.configurations.findOne({service: data.service}); if (customOauth) { const customLoginWith = Meteor['loginWith' + _.capitalize(customOauth.service, true)]; - const customRedirectUri = window.OAuth._redirectUri(customOauth.service, customOauth); + const customRedirectUri = data.redirectUrl || siteUrl; customLoginWith.call(Meteor, {'redirectUrl': customRedirectUri}, customOAuthCallback); } } diff --git a/packages/rocketchat-theme/assets/stylesheets/swipebox.min.css b/packages/rocketchat-ui/lib/jquery.swipebox.min.css similarity index 100% rename from packages/rocketchat-theme/assets/stylesheets/swipebox.min.css rename to packages/rocketchat-ui/lib/jquery.swipebox.min.css diff --git a/packages/rocketchat-ui/lib/menu.coffee b/packages/rocketchat-ui/lib/menu.coffee index f2ad61eb7bdda1ee9a388c70153cfb8e9671263d..c94d8de8c39d6cfecc69c465f4c6cf816171f313 100644 --- a/packages/rocketchat-ui/lib/menu.coffee +++ b/packages/rocketchat-ui/lib/menu.coffee @@ -1,18 +1,23 @@ @menu = new class init: -> - @container = $("#rocket-chat") + @mainContent = $('.main-content, .flex-tab-bar') @list = $('.rooms-list') + Session.set("isMenuOpen", false) + isOpen: -> - return @container?.hasClass("menu-opened") is true + return Session.get("isMenuOpen") open: -> - if not @isOpen() - @container?.removeClass("menu-closed").addClass("menu-opened") + Session.set("isMenuOpen", true) + if isRtl localStorage.getItem "userLanguage" + @mainContent?.css('transform', 'translateX(-260px)') + else + @mainContent?.css('transform', 'translateX(260px)') close: -> - if @isOpen() - @container?.removeClass("menu-opened").addClass("menu-closed") + Session.set("isMenuOpen", false) + @mainContent?.css('transform', 'translateX(0)') toggle: -> if @isOpen() diff --git a/packages/rocketchat-ui/lib/recorderjs/audioRecorder.coffee b/packages/rocketchat-ui/lib/recorderjs/audioRecorder.coffee index 013af2a24e52a488fc445c298c93d3d63b4840ec..e51dbeff7a2b6ba9655cea1147753ade205fee34 100644 --- a/packages/rocketchat-ui/lib/recorderjs/audioRecorder.coffee +++ b/packages/rocketchat-ui/lib/recorderjs/audioRecorder.coffee @@ -4,7 +4,7 @@ navigator.getUserMedia = navigator.getUserMedia or navigator.webkitGetUserMedia window.URL = window.URL or window.webkitURL - @audio_context = new AudioContext + window.audioContext = new AudioContext ok = (stream) => @startUserMedia(stream) @@ -18,7 +18,7 @@ startUserMedia: (stream) -> @stream = stream - input = @audio_context.createMediaStreamSource(stream) + input = window.audioContext.createMediaStreamSource(stream) @recorder = new Recorder(input, {workerPath: '/recorderWorker.js'}) @recorder.record() @@ -32,9 +32,10 @@ @recorder.clear() - delete @audio_context + window.audioContext.close() + delete window.audioContext delete @recorder delete @stream getBlob: (cb) -> - @recorder.exportWAV cb \ No newline at end of file + @recorder.exportWAV cb diff --git a/packages/rocketchat-ui/package.js b/packages/rocketchat-ui/package.js index 72f8da9b1a6b0ac3330fdc1c4a5b9493bd66c1f0..3b868ee1e7709c3f76cc592065ddc5b583d37810 100644 --- a/packages/rocketchat-ui/package.js +++ b/packages/rocketchat-ui/package.js @@ -40,6 +40,7 @@ Package.onUse(function(api) { api.addFiles('lib/fileUpload.coffee', 'client'); api.addFiles('lib/fireEvent.js', 'client'); api.addFiles('lib/iframeCommands.js', 'client'); + api.addFiles('lib/jquery.swipebox.min.css', 'client'); api.addFiles('lib/jquery.swipebox.min.js', 'client'); api.addFiles('lib/jquery.swipebox.init.js', 'client'); api.addFiles('lib/menu.coffee', 'client'); @@ -56,6 +57,8 @@ Package.onUse(function(api) { api.addFiles('lib/tapi18n.coffee', 'client'); api.addFiles('lib/textarea-autogrow.js', 'client'); + api.addFiles('lib/codeMirror/codeMirror.js', 'client'); + // LIB CORDOVA api.addFiles('lib/cordova/facebook-login.coffee', 'client'); api.addFiles('lib/cordova/keyboard-fix.coffee', 'client'); diff --git a/packages/rocketchat-ui/views/404/roomNotFound.html b/packages/rocketchat-ui/views/404/roomNotFound.html index b3c028106873fbe190d092de915e15ecb622ea16..9d653a0cce26d77d3863fc532a63112117bc91ad 100644 --- a/packages/rocketchat-ui/views/404/roomNotFound.html +++ b/packages/rocketchat-ui/views/404/roomNotFound.html @@ -1,12 +1,12 @@ <template name="roomNotFound"> - <section class="page-container page-list"> - <header class="fixed-title"> + <section class="page-container page-list content-background-color"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "Room_not_found"}}</span> </h2> </header> - <div class="content room-not-found"> + <div class="content room-not-found error-color"> <i class="icon-attention"></i> <div> {{#with data}} diff --git a/packages/rocketchat-ui/views/app/burger.coffee b/packages/rocketchat-ui/views/app/burger.coffee index 6af03ca0dbde668839a27d2ee309af32d5e40701..58ca516db8820a852c787a3ebbc707e2a9b2963f 100644 --- a/packages/rocketchat-ui/views/app/burger.coffee +++ b/packages/rocketchat-ui/views/app/burger.coffee @@ -1,3 +1,5 @@ Template.burger.helpers unread: -> - return Session.get 'unread' \ No newline at end of file + return Session.get 'unread' + isMenuOpen: -> + if Session.equals('isMenuOpen', true) then 'menu-opened' diff --git a/packages/rocketchat-ui/views/app/burger.html b/packages/rocketchat-ui/views/app/burger.html index 8cfe49e46a8beb34d31eb32a89015b3a9bf9c3f0..2ae6d163307dff5b35196ee6f5b7fdbf254eeb08 100644 --- a/packages/rocketchat-ui/views/app/burger.html +++ b/packages/rocketchat-ui/views/app/burger.html @@ -1,12 +1,12 @@ <template name="burger"> - <div class="burger"> + <div class="burger {{isMenuOpen}}"> <i></i> <i></i> <i></i> {{#if unread}} - <div class="unread-burger-alert"> + <div class="unread-burger-alert color-error-contrast background-error-color"> {{unread}} </div> {{/if}} </div> -</template> \ No newline at end of file +</template> diff --git a/packages/rocketchat-ui/views/app/home.html b/packages/rocketchat-ui/views/app/home.html index 2983174eb66df61b4f413c27b5068012d16aef9c..f9463714cae06f078863972e3ea46048b6383f28 100644 --- a/packages/rocketchat-ui/views/app/home.html +++ b/packages/rocketchat-ui/views/app/home.html @@ -1,6 +1,6 @@ <template name="home"> - <section class="page-container page-home page-static"> - <header class="fixed-title"> + <section class="page-container page-home page-static content-background-color"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> <span class="room-title">{{title}}</span> diff --git a/packages/rocketchat-ui/views/app/pageContainer.html b/packages/rocketchat-ui/views/app/pageContainer.html index 0508293eb397ae845c9479f5f35b8d9ce6b0b0f3..666501273d9621f23237436651491a17a411b938 100644 --- a/packages/rocketchat-ui/views/app/pageContainer.html +++ b/packages/rocketchat-ui/views/app/pageContainer.html @@ -1,6 +1,6 @@ <template name="pageContainer"> - <section class="page-container page-home page-static page-list"> - <header class="fixed-title"> + <section class="page-container page-home page-static page-list content-background-color"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> <span class="page-title"> diff --git a/packages/rocketchat-ui/views/app/pageSettingsContainer.html b/packages/rocketchat-ui/views/app/pageSettingsContainer.html index e37b35c19920719564f04525394c526d33ed3c00..46bfee36ebb01a66a9d565910170f53762afd634 100644 --- a/packages/rocketchat-ui/views/app/pageSettingsContainer.html +++ b/packages/rocketchat-ui/views/app/pageSettingsContainer.html @@ -1,6 +1,6 @@ <template name="pageSettingsContainer"> - <section class="page-container page-home page-static page-settings"> - <header class="fixed-title"> + <section class="page-container page-home page-static page-settings content-background-color"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> <span class="page-title">{{pageTitle}}</span> diff --git a/packages/rocketchat-ui/views/app/privateHistory.html b/packages/rocketchat-ui/views/app/privateHistory.html index 15eaf15df7aba6d4945f12d58d83f2f0796cd2f7..5544350792f9a83286208e1901c8ffa85c6252dd 100644 --- a/packages/rocketchat-ui/views/app/privateHistory.html +++ b/packages/rocketchat-ui/views/app/privateHistory.html @@ -1,6 +1,6 @@ <template name="privateHistory"> - <section class="page-container page-list"> - <header class="fixed-title"> + <section class="page-container page-list content-background-color"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> <span class="room-title">{{_ "History"}}</span> @@ -10,7 +10,7 @@ <form class="search-form" role="form"> <div class="input-line search"> <input type="text" id="history-filter" placeholder="{{_ "Search"}}" dir="auto"> - <i class="icon-search"></i> + <i class="icon-search secondary-font-color"></i> </div> </form> <div class="results"> diff --git a/packages/rocketchat-ui/views/app/room.coffee b/packages/rocketchat-ui/views/app/room.coffee index 14863c92045dd2ec05f1710d08a3a5342bdc6780..69bb49fc23454c5c6fb34da7ba1044610f7d79eb 100644 --- a/packages/rocketchat-ui/views/app/room.coffee +++ b/packages/rocketchat-ui/views/app/room.coffee @@ -18,7 +18,7 @@ Template.room.helpers favorite: -> sub = ChatSubscription.findOne { rid: this._id }, { fields: { f: 1 } } - return 'icon-star favorite-room' if sub?.f? and sub.f and favoritesEnabled() + return 'icon-star favorite-room pending-color' if sub?.f? and sub.f and favoritesEnabled() return 'icon-star-empty' favoriteLabel: -> @@ -93,14 +93,7 @@ Template.room.helpers userStatus: -> roomData = Session.get('roomData' + this._id) - - return {} unless roomData - - if roomData.t in ['d', 'l'] - subscription = RocketChat.models.Subscriptions.findOne({rid: this._id}); - return Session.get('user_' + subscription.name + '_status') || 'offline' - else - return 'offline' + return RocketChat.roomTypes.getUserStatus(roomData.t, this._id) or 'offline' flexOpened: -> return 'opened' if RocketChat.TabBar.isFlexOpen() @@ -127,7 +120,10 @@ Template.room.helpers return moment(this.since).calendar(null, {sameDay: 'LT'}) flexTemplate: -> - return RocketChat.TabBar.getTemplate() + if Session.get('openedRoom') is this._id + return RocketChat.TabBar.getTemplate() + + return '' flexData: -> return _.extend { @@ -579,13 +575,13 @@ Template.room.onRendered -> template.isAtBottom = (scrollThreshold) -> if not scrollThreshold? then scrollThreshold = 0 if wrapper.scrollTop + scrollThreshold >= wrapper.scrollHeight - wrapper.clientHeight - newMessage.className = "new-message not" + newMessage.className = "new-message background-primary-action-color not" return true return false template.sendToBottom = -> wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight - newMessage.className = "new-message not" + newMessage.className = "new-message background-primary-action-color not" template.checkIfScrollIsAtBottom = -> template.atBottom = template.isAtBottom(100) diff --git a/packages/rocketchat-ui/views/app/room.html b/packages/rocketchat-ui/views/app/room.html index ba3500d5e008ff9996e612757cc932bb5dec508a..a79f89aa5183c2d4e35ab4864868bbad75b294e5 100644 --- a/packages/rocketchat-ui/views/app/room.html +++ b/packages/rocketchat-ui/views/app/room.html @@ -1,13 +1,13 @@ <template name="room"> <div class="dropzone"> - <div class="dropzone-overlay"> - <div> + <div class="dropzone-overlay background-transparent-darkest color-content-background-color"> + <div class="background-transparent-darkest"> {{_ "Drop_to_upload_file"}} </div> </div> - <section class="messages-container {{adminClass}}" id="{{windowId}}" aria-label="{{_ "Channel"}}"> + <section class="messages-container border-component-color {{adminClass}}" id="{{windowId}}" aria-label="{{_ "Channel"}}"> {{#unless embeddedVersion}} - <header class="fixed-title"> + <header class="fixed-title content-background-color border-component-color"> {{> burger}} <h2> {{#if showToggleFavorite}} @@ -23,7 +23,7 @@ {{#with unreadData}} {{#if since}} {{#if count}} - <div class="unread-bar"> + <div class="unread-bar color-primary-action-color background-component-color"> <button class="jump-to"> <span class="jump-to-large">{{_ "Jump_to_first_unread"}}</span> <span class="jump-to-small">{{_ "Jump"}}</span> @@ -42,7 +42,7 @@ {{/if}} {{/with}} {{#each uploading}} - <div class="upload-progress {{#if error}}upload-error{{/if}}"> + <div class="upload-progress color-primary-action-color background-component-color {{#if error}}error-background error-border{{/if}}"> {{#if error}} <div class="upload-progress-text"> {{error}} @@ -64,15 +64,15 @@ </div> <div class="messages-box {{#if selectable}}selectable{{/if}} {{viewMode}}"> <div class="ticks-bar"></div> - <button class="new-message not"> + <button class="new-message background-primary-action-color color-primary-action-contrast not"> <i class="icon-down-big"></i> {{_ "New_messages"}} </button> - <div class="jump-recent {{#unless hasMoreNext}}not{{/unless}}"> + <div class="jump-recent background-component-color {{#unless hasMoreNext}}not{{/unless}}"> <button>{{_ "Jump_to_recent_messages"}} <i class="icon-level-down"></i></button> </div> {{#unless canPreview}} - <div class="content room-not-found"> + <div class="content room-not-found error-color"> <div> {{_ "You_must_join_to_view_messages_in_this_channel"}} </div> @@ -83,14 +83,10 @@ {{#if canPreview}} {{#if hasMore}} <li class="load-more"> - {{#if isLoading}} - <div class="load-more-loading">{{_ "Loading_more_from_history"}}...</div> - {{else}} - <button>{{_ "Has_more"}}...</button> - {{/if}} + {{> loading}} </li> {{else}} - <li class="start"> + <li class="start color-info-font-color"> {{_ "Start_of_conversation"}} </li> {{/if}} @@ -100,21 +96,17 @@ {{/each}} {{#if hasMoreNext}} <li class="load-more"> - {{#if isLoading}} - <div class="load-more-loading">{{_ "Loading_more_from_history"}}...</div> - {{else}} - <button>{{_ "Has_more"}}...</button> - {{/if}} + {{> loading}} </li> {{/if}} </ul> </div> </div> - <footer class="footer"> + <footer class="footer border-component-color"> {{> messageBox}} </footer> </section> - <section class="flex-tab"> + <section class="flex-tab secondary-background-color"> {{> Template.dynamic template=flexTemplate data=flexData}} </section> </div> diff --git a/packages/rocketchat-ui/views/app/spotlight/spotlight.coffee b/packages/rocketchat-ui/views/app/spotlight/spotlight.coffee index e1a51bab1f308e7160b2e83ee3d2d25ad46c8985..5e43e46d9b80968ff9173a55585fc00203fc82bf 100644 --- a/packages/rocketchat-ui/views/app/spotlight/spotlight.coffee +++ b/packages/rocketchat-ui/views/app/spotlight/spotlight.coffee @@ -26,7 +26,7 @@ getFromServer = (filter, records, cb) => for room in results.rooms server.push({ _id: room._id - t: 'c', + t: room.t, name: room.name }) @@ -57,7 +57,7 @@ Template.spotlight.helpers getValue: (_id, collection, records, firstPartValue) -> doc = _.findWhere(records, {_id: _id}) - FlowRouter.go(RocketChat.roomTypes.getRouteLink(doc.t, doc), null, FlowRouter.current().queryParams) + RocketChat.roomTypes.openRouteLink(doc.t, doc, FlowRouter.current().queryParams) spotlight.hide() diff --git a/packages/rocketchat-ui/views/app/spotlight/spotlight.html b/packages/rocketchat-ui/views/app/spotlight/spotlight.html index 5eacde1e2feb56ffe8c2a5dfaf76fa245f6283fd..f291a1bbf9eb2b8dceb585499cfad05cee2ae663 100644 --- a/packages/rocketchat-ui/views/app/spotlight/spotlight.html +++ b/packages/rocketchat-ui/views/app/spotlight/spotlight.html @@ -1,7 +1,7 @@ <template name="spotlight"> - <div class="spotlight hidden"> - <div class="spotlight-input"> - <i class="icon-search"></i> + <div class="spotlight hidden background-transparent-darker"> + <div class="spotlight-input secondary-background-color secondary-font-color"> + <i class="icon-search secondary-font-color"></i> <input type="text" name="spotlight"> {{> messagePopup popupConfig}} </div> diff --git a/packages/rocketchat-ui/views/app/spotlight/spotlightTemplate.js b/packages/rocketchat-ui/views/app/spotlight/spotlightTemplate.js index a0c0276e88a424cff880fe40a26800ff4b4a0b53..631931f6eb687ad6b912eac2f5ed7cc893300062 100644 --- a/packages/rocketchat-ui/views/app/spotlight/spotlightTemplate.js +++ b/packages/rocketchat-ui/views/app/spotlight/spotlightTemplate.js @@ -4,9 +4,10 @@ Template.spotlightTemplate.helpers({ }, userStatus() { - if (this.t === 'd' || this.t === 'l') { + if (this.t === 'd') { return 'status-' + (Session.get(`user_${this.name}_status`) || 'offline'); + } else { + return 'status-' + (RocketChat.roomTypes.getUserStatus(this.t, this.rid || this._id) || 'offline'); } - return 'status-offline'; } }); diff --git a/packages/rocketchat-ui/views/app/videoCall/videoCall.html b/packages/rocketchat-ui/views/app/videoCall/videoCall.html index ce641e2ab9b35055923b866659659404c5e443b4..1fe1594f2be1ce205fafc5b8005cd6f00b7f193e 100644 --- a/packages/rocketchat-ui/views/app/videoCall/videoCall.html +++ b/packages/rocketchat-ui/views/app/videoCall/videoCall.html @@ -1,17 +1,17 @@ <template name="videoCall"> {{#if videoAvaliable}} {{#if videoActive}} - <div class="webrtc-video {{#if overlay}}webrtc-video-overlay{{/if}}"> - <div class="main-video"> - <video src="{{mainVideoUrl}}" autoplay muted="true" class="{{#if $eq mainVideoUrl selfVideoUrl}}video-flip{{/if}}"></video> - <div>{{mainVideoUsername}}</div> + <div class="webrtc-video {{#if overlay}}webrtc-video-overlay background-transparent-darker{{/if}}"> + <div class="main-video background-transparent-darker"> + <video src="{{mainVideoUrl}}" autoplay muted="true" class="webrtc-video-element {{#if $eq mainVideoUrl selfVideoUrl}}video-flip{{/if}}"></video> + <div class="background-transparent-darker">{{mainVideoUsername}}</div> </div> <div class="videos"> {{#if selfVideoUrl}} - <div class="video-item" data-username="$self"> - <video src="{{selfVideoUrl}}" autoplay muted="true" class="{{#unless screenShareEnabled}}video-flip{{/unless}}"></video> + <div class="video-item background-transparent-darker" data-username="$self"> + <video src="{{selfVideoUrl}}" autoplay muted="true" class="webrtc-video-element {{#unless screenShareEnabled}}video-flip{{/unless}}"></video> {{#unless audioAndVideoEnabled}} - <div class="video-muted-overlay"> + <div class="video-muted-overlay background-transparent-darker"> {{#unless audioEnabled}} <button><i class="icon-mute"></i></button> {{/unless}} @@ -20,13 +20,13 @@ {{/unless}} </div> {{/unless}} - <div>{{_ "you"}}</div> + <div class="background-transparent-darker">{{_ "you"}}</div> </div> {{/if}} {{#each remoteVideoItems}} - <div class="video-item {{#unless connected}}state-overlay{{/unless}}" data-state-text="{{stateText}}" data-username="{{id}}"> - <video src="{{url}}" autoplay></video> - <div>{{usernameByUserId id}}</div> + <div class="video-item background-transparent-darker {{#unless connected}}state-overlay background-transparent-darker-before{{/unless}}" data-state-text="{{stateText}}" data-username="{{id}}"> + <video src="{{url}}" autoplay class="webrtc-video-element"></video> + <div class="background-transparent-darker">{{usernameByUserId id}}</div> </div> {{/each}} </div> diff --git a/packages/rocketchat-ui/views/cmsPage.html b/packages/rocketchat-ui/views/cmsPage.html index 9cd5c7fbddb278b8cb545316f5f0a08e65eca019..30e92787934f0d35b96c99fdc08821aa3b8526b1 100644 --- a/packages/rocketchat-ui/views/cmsPage.html +++ b/packages/rocketchat-ui/views/cmsPage.html @@ -1,5 +1,5 @@ <template name="cmsPage"> - <div class="cms-page"> + <div class="cms-page content-background-color"> <div class="cms-page-close"> <a href="/login" class="button primary"><i class="icon-cancel"></i></a> </div> diff --git a/packages/rocketchat-ui/views/fxos.html b/packages/rocketchat-ui/views/fxos.html index 23c767884d16662c87cc927b643acc3a882a82a0..c12762c3ef9c1ce289b650e7097b4a60c49c0b8f 100644 --- a/packages/rocketchat-ui/views/fxos.html +++ b/packages/rocketchat-ui/views/fxos.html @@ -1,12 +1,12 @@ <template name="fxOsInstallPrompt"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> <header> <a class="logo" href="/"> <img src="images/logo/logo.svg?v=3" /> </a> </header> - <div class="cms-page"> + <div class="cms-page content-background-color"> <h1>{{_ "Install_FxOs"}}</h1> <p>{{_ "Install_FxOs_follow_instructions"}}</p> </div> @@ -15,14 +15,14 @@ </template> <template name="fxOsInstallDone"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> <header> <a class="logo" href="/"> <img src="images/logo/logo.svg?v=3" /> </a> </header> - <div class="cms-page"> + <div class="cms-page content-background-color"> <h1>{{_ "Install_FxOs"}}</h1> <p>{{_ "Install_FxOs_done"}}</p> </div> @@ -31,14 +31,14 @@ </template> <template name="fxOsInstallError"> - <section class="full-page"> + <section class="full-page color-tertiary-font-color"> <div class="wrapper"> <header> <a class="logo" href="/"> <img src="images/logo/logo.svg?v=3" /> </a> </header> - <div class="cms-page"> + <div class="cms-page content-background-color"> <h1>{{_ "Install_FxOs"}}</h1> <p>{{_ "Install_FxOs_error"}}</p> <p>{{installError}}</p> diff --git a/packages/rocketchat-videobridge/client/stylesheets/video.less b/packages/rocketchat-videobridge/client/stylesheets/video.less index 5b9638601eef2dd8151c268f674d04027f6d85ed..f9f00d45035edffedf19924b14e763b3e005e61c 100644 --- a/packages/rocketchat-videobridge/client/stylesheets/video.less +++ b/packages/rocketchat-videobridge/client/stylesheets/video.less @@ -1,18 +1,18 @@ .flex-tab { - .video-chat { - ul { - li { - margin-bottom: 20px; - } - } - } + .video-chat { + ul { + li { + margin-bottom: 20px; + } + } + } } .video-chat { - .main-video { - iframe { - width: 100%; - min-height: 299px; - } - } + .main-video { + iframe { + width: 100%; + min-height: 299px; + } + } } diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.html b/packages/rocketchat-videobridge/client/views/videoFlexTab.html index a3595d17085b1bebe76a53bc36f2a5873ddd5dcf..71230ec318c411df2fd9fb39b35e371491559383 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.html +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.html @@ -1,10 +1,18 @@ <template name="videoFlexTab"> - - <div class="content"> - <div class="video-chat"> - <div class="main-video"> - <div id="videoContainer"></div> - </div> - </div> - </div> + <div class="content"> + {{#if openInNewWindow}} + <div class="list-view"> + <div class="title"> + <h2>{{_ "Video_Conference"}}</h2> + </div> + <p>{{_ "Opened_in_a_new_window"}}</p> + </div> + {{else}} + <div class="video-chat"> + <div class="main-video"> + <div id="videoContainer"></div> + </div> + </div> + {{/if}} + </div> </template> diff --git a/packages/rocketchat-videobridge/client/views/videoFlexTab.js b/packages/rocketchat-videobridge/client/views/videoFlexTab.js index 6e379d0ebcb8ed601fb3c2237dc274f1528cc0f7..42f1027846d0fe3211018a19d1059d2795fe52c9 100644 --- a/packages/rocketchat-videobridge/client/views/videoFlexTab.js +++ b/packages/rocketchat-videobridge/client/views/videoFlexTab.js @@ -2,7 +2,9 @@ /* eslint new-cap: [2, {"capIsNewExceptions": ["MD5"]}] */ Template.videoFlexTab.helpers({ - + openInNewWindow() { + return RocketChat.settings.get('Jitsi_Open_New_Window'); + } }); Template.videoFlexTab.onCreated(function() { @@ -20,83 +22,91 @@ Template.videoFlexTab.onCreated(function() { let jitsiRoomActive = null; + const closePanel = () => { + // Reset things. Should probably be handled better in closeFlex() + $('.flex-tab').css('max-width', ''); + $('.main-content').css('right', ''); + + RocketChat.TabBar.closeFlex(); + + RocketChat.TabBar.updateButton('video', { class: '' }); + }; + this.timeout = null; this.autorun(() => { if (RocketChat.settings.get('Jitsi_Enabled')) { - if (RocketChat.TabBar.getTemplate() === 'videoFlexTab') { - if (RocketChat.TabBar.isFlexOpen()) { - let roomId = Session.get('openedRoom'); - - let domain = RocketChat.settings.get('Jitsi_Domain'); - let jitsiRoom = 'RocketChat' + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString(); - let noSsl = RocketChat.settings.get('Jitsi_SSL') ? false : true; - console.debug('Jitsi Room ID:', jitsiRoom, 'If from direct message or private group don\'t share. Or you will be letting the invitee join any future conversation.'); + if (RocketChat.TabBar.isFlexOpen() && RocketChat.TabBar.getTemplate() === 'videoFlexTab') { + let roomId = Session.get('openedRoom'); - if (jitsiRoomActive !== null && jitsiRoomActive !== jitsiRoom) { - jitsiRoomActive = null; + let domain = RocketChat.settings.get('Jitsi_Domain'); + let jitsiRoom = RocketChat.settings.get('Jitsi_URL_Room_Prefix') + CryptoJS.MD5(RocketChat.settings.get('uniqueID') + roomId).toString(); + let noSsl = RocketChat.settings.get('Jitsi_SSL') ? false : true; - console.log('Room Changed... Close panel!'); - // Reset things. Should probably be handled better in closeFlex() - $('.flex-tab').css('max-width', ''); - $('.main-content').css('right', ''); + if (jitsiRoomActive !== null && jitsiRoomActive !== jitsiRoom) { + jitsiRoomActive = null; - RocketChat.TabBar.closeFlex(); + closePanel(); - RocketChat.TabBar.updateButton('video', { class: '' }); + // Clean up and stop updating timeout. + Meteor.defer(() => this.api && this.api.dispose()); + if (timeOut) { + clearInterval(timeOut); + } + } else { + jitsiRoomActive = jitsiRoom; - // Clean up and stop updating timeout. - if (timeOut) { - Meteor.defer(() => { - this.api.dispose(); - }); - clearInterval(timeOut); - } - } else { - jitsiRoomActive = jitsiRoom; + RocketChat.TabBar.updateButton('video', { class: 'red' }); - RocketChat.TabBar.updateButton('video', { class: 'red' }); + if (RocketChat.settings.get('Jitsi_Open_New_Window')) { + Meteor.call('jitsi:updateTimeout', roomId); - // Lets make sure its loaded before we try to show it. - if (typeof JitsiMeetExternalAPI !== 'undefined') { + timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); - // Keep it from showing duplicates when re-evaluated on variable change. - if (!$('[id^=jitsiConference]').length) { - this.api = new JitsiMeetExternalAPI(domain, jitsiRoom, width, height, document.getElementById('videoContainer'), configOverwrite, interfaceConfigOverwrite, noSsl); + const newWindow = window.open((noSsl ? 'http://' : 'https://') + domain + '/' + jitsiRoom, jitsiRoom); + newWindow.focus(); - /* - * Hack to send after frame is loaded. - * postMessage converts to events in the jitsi meet iframe. - * For some reason those aren't working right. - */ - Meteor.setTimeout(() => { - this.api.executeCommand('displayName', [Meteor.user().name]); - }, 5000); + let closeInterval = setInterval(() => { + if (newWindow.closed !== false) { + closePanel(); + clearInterval(closeInterval); + clearInterval(timeOut); + } + }, 300); - Meteor.call('jitsi:updateTimeout', roomId); + // Lets make sure its loaded before we try to show it. + } else if (typeof JitsiMeetExternalAPI !== 'undefined') { - timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); - } + // Keep it from showing duplicates when re-evaluated on variable change. + if (!$('[id^=jitsiConference]').length) { + this.api = new JitsiMeetExternalAPI(domain, jitsiRoom, width, height, document.getElementById('videoContainer'), configOverwrite, interfaceConfigOverwrite, noSsl); - // Execute any commands that might be reactive. Like name changing. - if (this.api) { + /* + * Hack to send after frame is loaded. + * postMessage converts to events in the jitsi meet iframe. + * For some reason those aren't working right. + */ + Meteor.setTimeout(() => { this.api.executeCommand('displayName', [Meteor.user().name]); - } - } - } + }, 5000); - } else { - RocketChat.TabBar.updateButton('video', { class: '' }); + Meteor.call('jitsi:updateTimeout', roomId); - // Clean up and stop updating timeout. - if (timeOut) { - Meteor.defer(() => { - this.api.dispose(); - }); - clearInterval(timeOut); + timeOut = Meteor.setInterval(() => Meteor.call('jitsi:updateTimeout', roomId), 10*1000); + } + + // Execute any commands that might be reactive. Like name changing. + this.api && this.api.executeCommand('displayName', [Meteor.user().name]); } } + } else { + RocketChat.TabBar.updateButton('video', { class: '' }); + + // Clean up and stop updating timeout. + if (timeOut) { + Meteor.defer(() => this.api && this.api.dispose()); + clearInterval(timeOut); + } } } }); - }); diff --git a/packages/rocketchat-videobridge/server/settings.js b/packages/rocketchat-videobridge/server/settings.js index c325c5dc219e6e8edaeffbb2538021b3a8d2e50f..a08f09b62d13e7005fb218baf3b3b3727ec52c97 100644 --- a/packages/rocketchat-videobridge/server/settings.js +++ b/packages/rocketchat-videobridge/server/settings.js @@ -17,6 +17,16 @@ Meteor.startup(function() { public: true }); + this.add('Jitsi_URL_Room_Prefix', 'RocketChat', { + type: 'string', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true + }, + i18nLabel: 'URL_room_prefix', + public: true + }); + this.add('Jitsi_SSL', true, { type: 'boolean', enableQuery: { @@ -27,6 +37,16 @@ Meteor.startup(function() { public: true }); + this.add('Jitsi_Open_New_Window', false, { + type: 'boolean', + enableQuery: { + _id: 'Jitsi_Enabled', + value: true + }, + i18nLabel: 'Always_open_in_new_window', + public: true + }); + this.add('Jitsi_Enable_Channels', false, { type: 'boolean', enableQuery: { diff --git a/packages/rocketchat-webrtc/WebRTCClass.coffee b/packages/rocketchat-webrtc/WebRTCClass.coffee index e7c1f3fbb6ca158173dacae5d767c754402fa170..4ac810fac618b818e5446b1288cfc68cd7c09a70 100644 --- a/packages/rocketchat-webrtc/WebRTCClass.coffee +++ b/packages/rocketchat-webrtc/WebRTCClass.coffee @@ -160,7 +160,7 @@ class WebRTCClass @screenShareAvailable = @navigator in ['chrome', 'firefox'] @media = - video: true + video: false audio: true @transport = new @transportClass @ @@ -228,10 +228,10 @@ class WebRTCClass if @active isnt true or @monitor is true or @remoteMonitoring is true then return remoteConnections = [] - for id, peerConnections of @peerConnections + for id, peerConnection of @peerConnections remoteConnections.push id: id - media: peerConnections.remoteMedia + media: peerConnection.remoteMedia @transport.sendStatus media: @media @@ -340,6 +340,8 @@ class WebRTCClass stream.addTrack(peer.stream.getAudioTracks()[0]) stream.volume = volume + this.audioContext = audioContext + onSuccess(stream) navigator.getUserMedia media, onSuccessLocal, onError @@ -461,7 +463,8 @@ class WebRTCClass stopAllPeerConnections: -> for id, peerConnection of @peerConnections - @stopPeerConnection id + @stopPeerConnection id + window.audioContext?.close() setAudioEnabled: (enabled=true) -> if @localStream? @@ -592,7 +595,7 @@ class WebRTCClass title = "Group audio call from #{subscription.name}" swal - title: "<i class='icon-#{icon} alert-icon'></i>#{title}" + title: "<i class='icon-#{icon} alert-icon success-color'></i>#{title}" text: "Do you want to accept?" html: true showCancelButton: true diff --git a/public/images/logo/android-chrome-144x144.png b/public/images/logo/android-chrome-144x144.png deleted file mode 100644 index fe9b2a8a988b3d38509f700fe0e34c6a2e2aea0a..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-chrome-144x144.png and /dev/null differ diff --git a/public/images/logo/android-chrome-36x36.png b/public/images/logo/android-chrome-36x36.png deleted file mode 100644 index c5cade5e3024b429ebeb5dd0eda19bb522712e86..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-chrome-36x36.png and /dev/null differ diff --git a/public/images/logo/android-chrome-48x48.png b/public/images/logo/android-chrome-48x48.png deleted file mode 100644 index ae7d50d9f5f2281d8086298cbb8ca1d5a508087e..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-chrome-48x48.png and /dev/null differ diff --git a/public/images/logo/android-chrome-72x72.png b/public/images/logo/android-chrome-72x72.png deleted file mode 100644 index 17ab1f42d7552fa02363da29b2fa2a16ffbebd1f..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-chrome-72x72.png and /dev/null differ diff --git a/public/images/logo/android-chrome-96x96.png b/public/images/logo/android-chrome-96x96.png deleted file mode 100644 index f98245a8abef2561c41b9068854dcabc1057dbc8..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-chrome-96x96.png and /dev/null differ diff --git a/public/images/logo/android-hdpi.png b/public/images/logo/android-hdpi.png deleted file mode 100644 index cf331a1ad66f57e3bf50d0a163a807f17b74480d..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-hdpi.png and /dev/null differ diff --git a/public/images/logo/android-mdpi.png b/public/images/logo/android-mdpi.png deleted file mode 100644 index 5f9e164a90b657ed7f1b095b2528cf071f29cb44..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-mdpi.png and /dev/null differ diff --git a/public/images/logo/android-xhdpi.png b/public/images/logo/android-xhdpi.png deleted file mode 100644 index 36fb209536b883171c3770402a33c3b218341afc..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-xhdpi.png and /dev/null differ diff --git a/public/images/logo/android-xxhdpi.png b/public/images/logo/android-xxhdpi.png deleted file mode 100644 index 5dbee688043451ee114a5d0775a6eab7344b9d2c..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-xxhdpi.png and /dev/null differ diff --git a/public/images/logo/android-xxxhdpi.png b/public/images/logo/android-xxxhdpi.png deleted file mode 100644 index 5de255141fe405860ca98fb45b02d52980ded29b..0000000000000000000000000000000000000000 Binary files a/public/images/logo/android-xxxhdpi.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-1024x1024.png b/public/images/logo/apple-touch-icon-1024x1024.png deleted file mode 100644 index 107dcda3db9653ef1608843b69cca1091627edee..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-1024x1024.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-114x114.png b/public/images/logo/apple-touch-icon-114x114.png deleted file mode 100644 index a5513bec2d0dd2693a7fb4e061eac110aaef2eab..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-114x114.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-120x120.png b/public/images/logo/apple-touch-icon-120x120.png deleted file mode 100644 index 98a86b1d6c2cef75bca6c365efcfdd9fde8ad711..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-120x120.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-144x144.png b/public/images/logo/apple-touch-icon-144x144.png deleted file mode 100644 index adf2a0a18d02d82dd989fb0661d367fcdcfd95ee..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-144x144.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-152x152.png b/public/images/logo/apple-touch-icon-152x152.png deleted file mode 100644 index 026c0a7b2c3765532ef356dbe86dc7a49561fa75..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-152x152.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-172x172.png b/public/images/logo/apple-touch-icon-172x172.png deleted file mode 100644 index 26e1f2e62d92c5cf596aadbd88af4f4292479226..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-172x172.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-180x180.png b/public/images/logo/apple-touch-icon-180x180.png deleted file mode 100644 index bb4688bc622d6d6909e063c44aecd4690e628730..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-180x180.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-196x196.png b/public/images/logo/apple-touch-icon-196x196.png deleted file mode 100644 index 3522df38db74cb72cf2c5a5b1cc6e44e87fffe0b..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-196x196.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-29x29.png b/public/images/logo/apple-touch-icon-29x29.png deleted file mode 100644 index b8f76bff8a032178395a863d0ac9db795bf98f8e..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-29x29.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-40x40.png b/public/images/logo/apple-touch-icon-40x40.png deleted file mode 100644 index eedb40059b192f7e1c1db20b5410a33781149dc3..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-40x40.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-48x48.png b/public/images/logo/apple-touch-icon-48x48.png deleted file mode 100644 index 3bf6d17cbb544f5a433835292571611eac27c310..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-48x48.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-55x55.png b/public/images/logo/apple-touch-icon-55x55.png deleted file mode 100644 index 5f3c8428e366c55deb4e3e64cdcd7611712b99b4..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-55x55.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-57x57.png b/public/images/logo/apple-touch-icon-57x57.png deleted file mode 100644 index 706de25b5b7e940bfb1460938cad14aa96122fe2..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-57x57.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-58x58.png b/public/images/logo/apple-touch-icon-58x58.png deleted file mode 100644 index 2d10e689d7efbbe91df2cfaae5cfa1ddc43403c7..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-58x58.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-60x60.png b/public/images/logo/apple-touch-icon-60x60.png deleted file mode 100644 index 9efb96495ff371ed5a42ba2fb44f66eb21303bc3..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-60x60.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-72x72.png b/public/images/logo/apple-touch-icon-72x72.png deleted file mode 100644 index af17998cc3acb6db525049ee050e7372a0b17033..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-72x72.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-76x76.png b/public/images/logo/apple-touch-icon-76x76.png deleted file mode 100644 index 9c84cc658f6d7c8f7ae6d8b61e5d92cc5ccd74d7..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-76x76.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-80x80.png b/public/images/logo/apple-touch-icon-80x80.png deleted file mode 100644 index 8d815759b6ee959d9a11b9fc0384ac867007889e..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-80x80.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-87x87.png b/public/images/logo/apple-touch-icon-87x87.png deleted file mode 100644 index ff9274db491dd9cd68a2dfe4a7fb55f1de0d3f76..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-87x87.png and /dev/null differ diff --git a/public/images/logo/apple-touch-icon-88x88.png b/public/images/logo/apple-touch-icon-88x88.png deleted file mode 100644 index 7620721e2bb977873b81b4a62df73fe540c3ec9a..0000000000000000000000000000000000000000 Binary files a/public/images/logo/apple-touch-icon-88x88.png and /dev/null differ diff --git a/public/images/logo/browserconfig.xml b/public/images/logo/browserconfig.xml index 8f51ba5fdcd557518e0680c02a6f902386e7f1e0..fe60a23024c474687d774e9dde9216fd4e60b70e 100644 --- a/public/images/logo/browserconfig.xml +++ b/public/images/logo/browserconfig.xml @@ -1,12 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <browserconfig> - <msapplication> - <tile> - <square70x70logo src="/images/logo/mstile-70x70.png?v=3"/> - <square150x150logo src="/images/logo/mstile-150x150.png?v=3"/> - <square310x310logo src="/images/logo/mstile-310x310.png?v=3"/> - <wide310x150logo src="/images/logo/mstile-310x150.png?v=3"/> - <TileColor>#04436a</TileColor> - </tile> - </msapplication> + <msapplication> + <tile> + <square150x150logo src="/images/logo/mstile-150x150.png?v=3"/> + <square310x310logo src="/images/logo/mstile-310x310.png?v=3"/> + <wide310x150logo src="/images/logo/mstile-310x150.png?v=3"/> + <TileColor>#04436a</TileColor> + </tile> + </msapplication> </browserconfig> diff --git a/public/images/logo/favicon-128x128.png b/public/images/logo/favicon-128x128.png deleted file mode 100644 index 713aa256fd775d56790dbca9bda2eafb8ccd9204..0000000000000000000000000000000000000000 Binary files a/public/images/logo/favicon-128x128.png and /dev/null differ diff --git a/public/images/logo/favicon-256x256.png b/public/images/logo/favicon-256x256.png deleted file mode 100644 index 912112d2c9794607a75a6b0a76187da730810f8e..0000000000000000000000000000000000000000 Binary files a/public/images/logo/favicon-256x256.png and /dev/null differ diff --git a/public/images/logo/favicon-48x48.png b/public/images/logo/favicon-48x48.png deleted file mode 100644 index e03c317cb2263ae66185d08ff876c79236150a10..0000000000000000000000000000000000000000 Binary files a/public/images/logo/favicon-48x48.png and /dev/null differ diff --git a/public/images/logo/favicon-64x64.png b/public/images/logo/favicon-64x64.png deleted file mode 100644 index e218cb72aeb3d82c7800e2149baeb632be9f0139..0000000000000000000000000000000000000000 Binary files a/public/images/logo/favicon-64x64.png and /dev/null differ diff --git a/public/images/logo/favicon-96x96.png b/public/images/logo/favicon-96x96.png deleted file mode 100644 index d09d598190debcccb9741a61d9d36c14f226177d..0000000000000000000000000000000000000000 Binary files a/public/images/logo/favicon-96x96.png and /dev/null differ diff --git a/public/images/logo/icon-loader.svg b/public/images/logo/icon-loader.svg deleted file mode 100644 index 3564228a97114ef583db20f6b3ab4e229057e963..0000000000000000000000000000000000000000 --- a/public/images/logo/icon-loader.svg +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg class="rocket-loader" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve"> -<path class="outer" fill="#C1282D" d="M185.094,100.547c0-8.516-2.547-16.683-7.574-24.274c-4.513-6.815-10.835-12.848-18.792-17.93 - c-15.359-9.812-35.55-15.216-56.846-15.216c-7.114,0-14.125,0.601-20.926,1.792c-4.22-3.949-9.159-7.502-14.385-10.312 - c-27.923-13.532-51.079-0.318-51.079-0.318S37.021,51.974,33.52,67.478c-9.632,9.554-14.852,21.075-14.852,33.07 - c0,0.038,0.002,0.076,0.002,0.115c0,0.038-0.002,0.076-0.002,0.114c0,11.995,5.22,23.516,14.852,33.071 - c3.501,15.503-18.028,33.188-18.028,33.188s23.156,13.214,51.079-0.317c5.227-2.811,10.166-6.364,14.385-10.313 - c6.801,1.19,13.812,1.792,20.925,1.792c21.297,0,41.486-5.404,56.847-15.216c7.956-5.083,14.278-11.115,18.792-17.931 - c5.027-7.592,7.574-15.758,7.574-24.274c0-0.038-0.002-0.076-0.002-0.114C185.092,100.624,185.094,100.586,185.094,100.547z"/> -<path class="inner" fill="#FFFFFF" d="M101.882,55.167c39.433,0,71.401,20.419,71.401,45.609c0,25.188-31.968,45.609-71.401,45.609 - c-8.781,0-17.189-1.016-24.958-2.866c-7.896,9.498-25.265,22.704-42.138,18.435c5.489-5.896,13.62-15.856,11.879-32.262 - c-10.113-7.87-16.185-17.94-16.185-28.916C30.479,75.587,62.448,55.167,101.882,55.167"/> -<g> - <circle fill="#C1282D" cx="101.882" cy="102.245" r="9.484"/> - <circle fill="#C1282D" cx="134.86" cy="102.245" r="9.484"/> - <circle fill="#C1282D" cx="68.902" cy="102.245" r="9.484"/> -</g> -<g> - <path fill="#CDCCCC" d="M101.882,140.385c-8.781,0-17.189-0.88-24.958-2.484c-6.972,7.269-21.331,17.04-36.212,16.681 - c-1.959,2.972-4.09,5.401-5.926,7.374c16.873,4.269,34.243-8.937,42.138-18.436c7.769,1.852,16.178,2.867,24.958,2.867 - c39.117,0,70.882-20.096,71.39-45.006C172.764,122.97,140.999,140.385,101.882,140.385z"/> -</g> -</svg> diff --git a/public/images/logo/loading.gif b/public/images/logo/loading.gif deleted file mode 100644 index bacd16c39e3bc5d1dfbc119508973afbe1c1578b..0000000000000000000000000000000000000000 Binary files a/public/images/logo/loading.gif and /dev/null differ diff --git a/public/images/logo/manifest.json b/public/images/logo/manifest.json index 30898278e784dc11e8b219136a21b1c58334e4ff..31c4bb75839071bdc3464a806ca1d6bc710b36cf 100644 --- a/public/images/logo/manifest.json +++ b/public/images/logo/manifest.json @@ -1,41 +1,15 @@ { "name": "Rocket.Chat", "icons": [ - { - "src": "\/images\/logo\/android-chrome-36x36.png?v=3", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "\/images\/logo\/android-chrome-48x48.png?v=3", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "\/images\/logo\/android-chrome-72x72.png?v=3", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "\/images\/logo\/android-chrome-96x96.png?v=3", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "\/images\/logo\/android-chrome-144x144.png?v=3", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, { "src": "\/images\/logo\/android-chrome-192x192.png?v=3", "sizes": "192x192", - "type": "image\/png", - "density": "4.0" + "type": "image\/png" + }, + { + "src": "\/images\/logo\/512x512.png?v=3", + "sizes": "512x512", + "type": "image\/png" } ], "start_url": "https:\/\/rocket.chat\/home", diff --git a/public/images/logo/mstile-70x70.png b/public/images/logo/mstile-70x70.png deleted file mode 100644 index c42cacee61b484e1f553ba81973d5c72ecc189b7..0000000000000000000000000000000000000000 Binary files a/public/images/logo/mstile-70x70.png and /dev/null differ diff --git a/public/images/logo/safari-pinned-tab.svg b/public/images/logo/safari-pinned-tab.svg new file mode 100644 index 0000000000000000000000000000000000000000..05a9c740658b8a7f537fb9cc5913288317c60740 --- /dev/null +++ b/public/images/logo/safari-pinned-tab.svg @@ -0,0 +1,28 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.11, written by Peter Selinger 2001-2013 +</metadata> +<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M758 4695 c-2 -1 -43 -5 -93 -8 -91 -6 -217 -26 -297 -47 -23 -7 -50 +-14 -58 -15 -44 -10 -223 -79 -257 -100 -1 -1 55 -60 126 -131 284 -288 418 +-537 413 -768 -2 -77 1 -72 -96 -182 -166 -192 -292 -437 -331 -649 -20 -109 +-25 -288 -12 -405 28 -241 151 -503 334 -714 111 -128 104 -115 105 -213 0 +-49 -5 -99 -10 -113 -5 -14 -10 -29 -11 -35 -1 -5 -8 -32 -17 -60 -54 -165 +-199 -371 -400 -569 l-105 -103 61 -27 c118 -53 233 -87 405 -120 134 -26 387 +-26 545 -1 200 32 441 115 575 199 122 76 286 197 337 248 13 13 28 18 46 14 +66 -12 133 -21 242 -32 41 -3 89 -8 105 -10 37 -5 433 -4 495 0 41 3 69 6 210 +22 25 3 81 11 125 19 44 8 94 16 110 19 17 3 62 12 100 21 39 9 80 18 91 21 +35 7 345 110 389 129 187 81 401 199 525 291 122 90 302 258 348 326 9 13 19 +25 23 28 22 17 130 190 170 275 89 189 122 337 122 545 0 212 -36 376 -124 +556 -209 431 -652 781 -1241 980 -103 35 -302 88 -368 99 -19 3 -42 8 -50 10 +-16 4 -30 7 -145 25 -303 48 -822 48 -1068 -1 -73 -14 -89 -11 -128 24 -71 62 +-92 79 -172 136 -206 145 -406 232 -651 281 -87 18 -101 19 -221 25 -60 4 +-118 8 -127 9 -9 2 -18 3 -20 1z"/> +</g> +</svg> diff --git a/public/manifest.webapp b/public/manifest.webapp index 30949605959370c8ab0a367d4f90876bd083b6b3..6a76a1684dab68a5fe38ccf1f18a5453c1be3603 100644 --- a/public/manifest.webapp +++ b/public/manifest.webapp @@ -3,7 +3,7 @@ "description": "The Ultimate Open Source WebChat Platform", "launch_path": "/", "icons": { - "128": "/images/logo/favicon-128x128.png", + "192": "/images/logo/android-chrome-192x192.png", "512": "/images/logo/512x512.png" }, "developer": { diff --git a/server/lib/accounts.coffee b/server/lib/accounts.coffee index 36bb63af6db973532c01ebb323572e89f53d52ee..acfe73bf263bfe770bcebc1c3245afef0b8b9698 100644 --- a/server/lib/accounts.coffee +++ b/server/lib/accounts.coffee @@ -81,6 +81,11 @@ Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc, options, _id = insertUserDoc.call(Accounts, options, user) + # Add user to default channels + if user.username? and options.joinDefaultChannels isnt false and user.joinDefaultChannels isnt false + Meteor.runAsUser _id, -> + Meteor.call('joinDefaultChannels', options.joinDefaultChannelsSilenced) + if roles.length is 0 # when inserting first user give them admin privileges otherwise make a regular user hasAdmin = RocketChat.models.Users.findOne({ roles: 'admin' }, {fields: {_id: 1}}) diff --git a/server/methods/addRoomModerator.coffee b/server/methods/addRoomModerator.coffee index 963a75e1b2365af31539080512a5387b469ee588..ed54a154a04e5f03a07eec6e6fe8edb8a17c6cf2 100644 --- a/server/methods/addRoomModerator.coffee +++ b/server/methods/addRoomModerator.coffee @@ -10,13 +10,20 @@ Meteor.methods unless RocketChat.authz.hasPermission Meteor.userId(), 'set-moderator', rid throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'addRoomModerator' } - subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, userId + user = RocketChat.models.Users.findOneById userId + + unless user?.username + throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'addRoomModerator' } + + subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, user._id unless subscription? - throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'addRoomModerator' } + throw new Meteor.Error 'error-user-not-in-room', 'User is not in this room', { method: 'addRoomModerator' } + + if 'moderator' in (subscription.roles or []) + throw new Meteor.Error 'error-user-already-moderator', 'User is already a moderator', { method: 'addRoomModerator' } RocketChat.models.Subscriptions.addRoleById(subscription._id, 'moderator') - user = RocketChat.models.Users.findOneById userId fromUser = RocketChat.models.Users.findOneById Meteor.userId() RocketChat.models.Messages.createSubscriptionRoleAddedWithRoomIdAndUser rid, user, u: diff --git a/server/methods/addRoomOwner.coffee b/server/methods/addRoomOwner.coffee index 7d051bcbaffd7279ab82c07dfbe5691fb4821087..6e6c8b937d1aec671d5f1a43212add790c7013fb 100644 --- a/server/methods/addRoomOwner.coffee +++ b/server/methods/addRoomOwner.coffee @@ -10,13 +10,20 @@ Meteor.methods unless RocketChat.authz.hasPermission Meteor.userId(), 'set-owner', rid throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'addRoomOwner' } - subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, userId + user = RocketChat.models.Users.findOneById userId + + unless user?.username + throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'addRoomOwner' } + + subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, user._id unless subscription? - throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'addRoomOwner' } + throw new Meteor.Error 'error-user-not-in-room', 'User is not in this room', { method: 'addRoomOwner' } + + if 'owner' in (subscription.roles or []) + throw new Meteor.Error 'error-user-already-owner', 'User is already an owner', { method: 'addRoomOwner' } RocketChat.models.Subscriptions.addRoleById(subscription._id, 'owner') - user = RocketChat.models.Users.findOneById userId fromUser = RocketChat.models.Users.findOneById Meteor.userId() RocketChat.models.Messages.createSubscriptionRoleAddedWithRoomIdAndUser rid, user, u: diff --git a/server/methods/groupsList.js b/server/methods/groupsList.js index 37a5fe771f62caf02a1d372ebe0b2654750d6c3f..0094a81adc608daa1b2b0adedb76f7b0ce8a0a26 100644 --- a/server/methods/groupsList.js +++ b/server/methods/groupsList.js @@ -1,7 +1,7 @@ Meteor.methods({ groupsList: function(nameFilter, limit, sort) { - check(nameFilter, String); + check(nameFilter, Match.Optional(String)); check(limit, Match.Optional(Number)); check(sort, Match.Optional(String)); diff --git a/server/methods/messageSearch.js b/server/methods/messageSearch.js index b1009be1a0720f97c118cd54d8cbcc1649353bfe..4cea4d84126aef48fa522dc5dc2f179a64ae7089 100644 --- a/server/methods/messageSearch.js +++ b/server/methods/messageSearch.js @@ -148,7 +148,7 @@ Meteor.methods({ text = text.replace(/after:(\d{1,2})[\/\.-](\d{1,2})[\/\.-](\d{4})/g, filterAfterDate); text = text.replace(/on:(\d{1,2})[\/\.-](\d{1,2})[\/\.-](\d{4})/g, filterOnDate); // Sort order - text = text.replace(/(?:order|sort):(asc|ascend|ascending|desc|descend|descening)/g, sortByTimestamp); + text = text.replace(/(?:order|sort):(asc|ascend|ascending|desc|descend|descending)/g, sortByTimestamp); // Query in message text text = text.trim().replace(/\s\s/g, ' '); diff --git a/server/methods/removeRoomModerator.coffee b/server/methods/removeRoomModerator.coffee index c46b57aca4628b3c4d6387cd9760e384bad23f30..b375c8f731ceb596e7dc8b8de706a4de5d860ac0 100644 --- a/server/methods/removeRoomModerator.coffee +++ b/server/methods/removeRoomModerator.coffee @@ -10,13 +10,20 @@ Meteor.methods unless RocketChat.authz.hasPermission Meteor.userId(), 'set-moderator', rid throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'removeRoomModerator' } - subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, userId + user = RocketChat.models.Users.findOneById userId + + unless user?.username + throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'removeRoomModerator' } + + subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, user._id unless subscription? throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'removeRoomModerator' } + if 'moderator' not in (subscription.roles or []) + throw new Meteor.Error 'error-user-not-moderator', 'User is not a moderator', { method: 'removeRoomModerator' } + RocketChat.models.Subscriptions.removeRoleById(subscription._id, 'moderator') - user = RocketChat.models.Users.findOneById userId fromUser = RocketChat.models.Users.findOneById Meteor.userId() RocketChat.models.Messages.createSubscriptionRoleRemovedWithRoomIdAndUser rid, user, u: diff --git a/server/methods/removeRoomOwner.coffee b/server/methods/removeRoomOwner.coffee index 1a0c09011107b223a1de16c050d9467b8c6295ef..dcc9a21470ef346eb9950e9c784d43d8d89d0bc2 100644 --- a/server/methods/removeRoomOwner.coffee +++ b/server/methods/removeRoomOwner.coffee @@ -10,17 +10,24 @@ Meteor.methods unless RocketChat.authz.hasPermission Meteor.userId(), 'set-owner', rid throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'removeRoomOwner' } - subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, userId + user = RocketChat.models.Users.findOneById userId + + unless user?.username + throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'removeRoomOwner' } + + subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId rid, user._id unless subscription? throw new Meteor.Error 'error-invalid-room', 'Invalid room', { method: 'removeRoomOwner' } + if 'owner' not in (subscription.roles or []) + throw new Meteor.Error 'error-user-not-owner', 'User is not an owner', { method: 'removeRoomOwner' } + numOwners = RocketChat.authz.getUsersInRole('owner', rid).count() if numOwners is 1 throw new Meteor.Error 'error-remove-last-owner', 'This is the last owner. Please set a new owner before removing this one.', { method: 'removeRoomOwner' } RocketChat.models.Subscriptions.removeRoleById(subscription._id, 'owner') - user = RocketChat.models.Users.findOneById userId fromUser = RocketChat.models.Users.findOneById Meteor.userId() RocketChat.models.Messages.createSubscriptionRoleRemovedWithRoomIdAndUser rid, user, u: diff --git a/server/methods/removeUserFromRoom.coffee b/server/methods/removeUserFromRoom.coffee index fbccfbed8109e7f67ec4634d85bceab1e34713f5..5029f792bda18f0de41940bed4213b795e0f6988 100644 --- a/server/methods/removeUserFromRoom.coffee +++ b/server/methods/removeUserFromRoom.coffee @@ -13,6 +13,9 @@ Meteor.methods room = RocketChat.models.Rooms.findOneById data.rid + if room.t is 'd' + throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'removeUserFromRoom' } + if data.username not in (room?.usernames or []) throw new Meteor.Error 'error-user-not-in-room', 'User is not in this room', { method: 'removeUserFromRoom' } diff --git a/server/methods/saveUserProfile.coffee b/server/methods/saveUserProfile.coffee index 428c8c1a0c9d2fbaa1f1614951c1ddd0fd4dc4ef..d591ecfdc1e6cbcef2b781e390525d004dba27e3 100644 --- a/server/methods/saveUserProfile.coffee +++ b/server/methods/saveUserProfile.coffee @@ -1,5 +1,5 @@ Meteor.methods - saveUserProfile: (settings) -> + saveUserProfile: (settings, customFields) -> check settings, Object @@ -43,4 +43,6 @@ Meteor.methods RocketChat.models.Users.setProfile Meteor.userId(), profile + RocketChat.saveCustomFields Meteor.userId(), customFields + return true diff --git a/server/publications/channelAndPrivateAutocomplete.coffee b/server/publications/channelAndPrivateAutocomplete.coffee index af229b8302af0ad279552be2c2cb24315cb2e2de..190fd834bcd41cd474be79a6e45339ca12f84826 100644 --- a/server/publications/channelAndPrivateAutocomplete.coffee +++ b/server/publications/channelAndPrivateAutocomplete.coffee @@ -15,6 +15,7 @@ Meteor.publish 'channelAndPrivateAutocomplete', (selector) -> sort: name: 1 + # CACHE: can we stop using publications here? cursorHandle = RocketChat.models.Rooms.findByNameStartingAndTypes(selector.name, ['c', 'p'], options).observeChanges added: (_id, record) -> pub.added('autocompleteRecords', _id, record) diff --git a/server/publications/fullUserData.coffee b/server/publications/fullUserData.coffee index 65147c24a80746ae15e28cf1f1416e68681b0083..abe05de05a576f3df07bb65a00b06e74b3e1c806 100644 --- a/server/publications/fullUserData.coffee +++ b/server/publications/fullUserData.coffee @@ -2,43 +2,9 @@ Meteor.publish 'fullUserData', (filter, limit) -> unless @userId return @ready() - fields = - name: 1 - username: 1 - status: 1 - utcOffset: 1 - type: 1 - active: 1 + result = RocketChat.getFullUserData { userId: @userId, filter, limit } - if RocketChat.authz.hasPermission( @userId, 'view-full-other-user-info') is true - fields = _.extend fields, - emails: 1 - phone: 1 - statusConnection: 1 - createdAt: 1 - lastLogin: 1 - services: 1 - requirePasswordChange: 1 - requirePasswordChangeReason: 1 - roles: 1 - else - limit = 1 - - filter = s.trim filter - - if not filter and limit is 1 + if not result return @ready() - options = - fields: fields - limit: limit - sort: { username: 1 } - - if filter - if limit is 1 - return RocketChat.models.Users.findByUsername filter, options - else - filterReg = new RegExp s.escapeRegExp(filter), "i" - return RocketChat.models.Users.findByUsernameNameOrEmailAddress filterReg, options - - return RocketChat.models.Users.find {}, options + return result diff --git a/server/publications/privateHistory.coffee b/server/publications/privateHistory.coffee index 115796f52c8fdeea347acec3447d2df5f083481f..5f8d8e90f048aeabad67e5c9475fa7822b08a482 100644 --- a/server/publications/privateHistory.coffee +++ b/server/publications/privateHistory.coffee @@ -2,7 +2,7 @@ Meteor.publish 'privateHistory', -> unless this.userId return this.ready() - RocketChat.models.Rooms.findByContainigUsername RocketChat.models.Users.findOneById(this.userId).username, + RocketChat.models.Rooms.findByContainingUsername RocketChat.models.Users.findOneById(this.userId).username, fields: t: 1 name: 1 @@ -10,4 +10,3 @@ Meteor.publish 'privateHistory', -> ts: 1 lm: 1 cl: 1 - diff --git a/server/publications/room.coffee b/server/publications/room.coffee deleted file mode 100644 index c251ff777f3ca1baf07ace6933d16b335f045ad3..0000000000000000000000000000000000000000 --- a/server/publications/room.coffee +++ /dev/null @@ -1,11 +0,0 @@ -Meteor.publish 'room', (typeName) -> - unless this.userId - return this.ready() - - if typeof typeName isnt 'string' - return this.ready() - - type = typeName.substr(0, 1) - name = typeName.substr(1) - - return RocketChat.roomTypes.runPublish(this, type, name) diff --git a/server/publications/room.js b/server/publications/room.js new file mode 100644 index 0000000000000000000000000000000000000000..c665df94131f0e05287e7abac2e68cbff7b90de7 --- /dev/null +++ b/server/publications/room.js @@ -0,0 +1,97 @@ +const options = { + fields: { + _id: 1, + name: 1, + t: 1, + cl: 1, + u: 1, + // usernames: 1, + topic: 1, + muted: 1, + archived: 1, + jitsiTimeout: 1, + description: 1, + default: 1, + + // @TODO create an API to register this fields based on room type + livechatData: 1, + tags: 1, + sms: 1, + code: 1, + open: 1, + v: 1, + label: 1, + ro: 1 + } +}; + + +const roomMap = (record) => { + if (record._room) { + return _.pick(record._room, ...Object.keys(options.fields)); + } + console.log('Empty Room for Subscription', record); + return {}; +}; + + +Meteor.methods({ + 'rooms/get'(updatedAt) { + if (!Meteor.userId()) { + return []; + } + + this.unblock(); + + if (updatedAt instanceof Date) { + return { + update: RocketChat.models.Rooms.findBySubscriptionUserIdUpdatedAfter(Meteor.userId(), updatedAt, options).fetch(), + remove: RocketChat.models.Rooms.trashFindDeletedAfter(updatedAt, {}, {fields: {_id: 1, _deletedAt: 1}}).fetch() + }; + } + + return RocketChat.models.Rooms.findBySubscriptionUserId(Meteor.userId(), options).fetch(); + }, + + getRoomByTypeAndName(type, name) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomByTypeAndName' }); + } + + const roomFind = RocketChat.roomTypes.getRoomFind(type); + + let room; + + if (roomFind) { + room = roomFind.call(this, name); + } else { + room = RocketChat.models.Rooms.findByTypeAndName(type, name).fetch(); + } + + if (!room) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getRoomByTypeAndName' }); + } + + if (!Meteor.call('canAccessRoom', room._id, Meteor.userId())) { + throw new Meteor.Error('error-no-permission', 'No permission', { method: 'getRoomByTypeAndName' }); + } + + return roomMap({_room: room}); + } +}); + +RocketChat.models.Rooms.cache.on('sync', (type, room/*, diff*/) => { + const records = RocketChat.models.Subscriptions.findByRoomId(room._id).fetch(); + for (const record of records) { + RocketChat.Notifications.notifyUserInThisInstance(record.u._id, 'rooms-changed', type, roomMap({_room: room})); + } +}); + +RocketChat.models.Subscriptions.on('changed', (type, subscription/*, diff*/) => { + if (type === 'inserted') { + const room = RocketChat.models.Rooms.findOneById(subscription.rid); + if (room) { + RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'rooms-changed', type, roomMap({_room: room})); + } + } +}); diff --git a/server/publications/subscription.coffee b/server/publications/subscription.coffee index ab44cdb5efad903d526b51d386dbaa70000bd745..b51560c25d42dc9a25d116d5873145ec18e6e37c 100644 --- a/server/publications/subscription.coffee +++ b/server/publications/subscription.coffee @@ -18,6 +18,8 @@ fields = emailNotifications: 1 unreadAlert: 1 _updatedAt: 1 + blocked: 1 + blocker: 1 Meteor.methods @@ -30,14 +32,17 @@ Meteor.methods options = fields: fields - if updatedAt instanceof Date - return RocketChat.models.Subscriptions.dinamicFindChangesAfter('findByUserId', updatedAt, Meteor.userId(), options); + records = RocketChat.models.Subscriptions.findByUserId(Meteor.userId(), options).fetch() - return RocketChat.models.Subscriptions.findByUserId(Meteor.userId(), options).fetch() + if updatedAt instanceof Date + return { + update: records.filter (record) -> + return record._updatedAt > updatedAt + remove: RocketChat.models.Subscriptions.trashFindDeletedAfter(updatedAt, {'u._id': Meteor.userId()}, {fields: {_id: 1, _deletedAt: 1}}).fetch() + } + return records -RocketChat.models.Subscriptions.on 'change', (type, args...) -> - records = RocketChat.models.Subscriptions.getChangedRecords type, args[0], fields - for record in records - RocketChat.Notifications.notifyUser record.u._id, 'subscriptions-changed', type, record +RocketChat.models.Subscriptions.on 'changed', (type, subscription) -> + RocketChat.Notifications.notifyUserInThisInstance subscription.u._id, 'subscriptions-changed', type, RocketChat.models.Subscriptions.processQueryOptionsOnResult(subscription, {fields: fields}) diff --git a/server/publications/userData.coffee b/server/publications/userData.coffee index 1301239dbf6dec6862854278624563f469171835..39429349f5320c83741c2533251d2fda9906961e 100644 --- a/server/publications/userData.coffee +++ b/server/publications/userData.coffee @@ -16,6 +16,7 @@ Meteor.publish 'userData', -> roles: 1 active: 1 defaultRoom: 1 + customFields: 1 'services.github': 1 'services.gitlab': 1 requirePasswordChange: 1 diff --git a/server/restapi/README.md b/server/restapi/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9905e0753b673ac42ad04da09efd3d71e04c77c4 --- /dev/null +++ b/server/restapi/README.md @@ -0,0 +1,3 @@ +# Important Information + +The REST API has moved to `/api/v1/${endpoint}`, please see the [Rocket.Chat Documentation](https://rocket.chat/docs/developer-guides/rest-api) for details on the current REST API. If a feature is currently missing, feel free to open a new pull request to add it. :heart: diff --git a/server/restapi/restapi.coffee b/server/restapi/restapi.coffee deleted file mode 100644 index 8f3f3bba98e00398406ca22cb9f2f8607ffd941b..0000000000000000000000000000000000000000 --- a/server/restapi/restapi.coffee +++ /dev/null @@ -1,281 +0,0 @@ -Api = new Restivus - useDefaultAuth: true - prettyJson: true - enableCors: false - - -Api.addRoute 'info', authRequired: false, - get: -> RocketChat.Info - - -Api.addRoute 'version', authRequired: false, - get: -> - version = {api: '0.1', rocketchat: '0.5'} - status: 'success', versions: version - -Api.addRoute 'publicRooms', authRequired: true, - get: -> - rooms = RocketChat.models.Rooms.findByType('c', { sort: { msgs:-1 } }).fetch() - status: 'success', rooms: rooms - -### -@api {get} /joinedRooms Get joined rooms. -### -Api.addRoute 'joinedRooms', authRequired: true, - get: -> - rooms = RocketChat.models.Rooms.findByContainigUsername(@user.username).fetch() - status: 'success', rooms: rooms - -# join a room -Api.addRoute 'rooms/:id/join', authRequired: true, - post: -> - Meteor.runAsUser this.userId, () => - Meteor.call('joinRoom', @urlParams.id) - status: 'success' # need to handle error - -# leave a room -Api.addRoute 'rooms/:id/leave', authRequired: true, - post: -> - Meteor.runAsUser this.userId, () => - Meteor.call('leaveRoom', @urlParams.id) - status: 'success' # need to handle error - - -### -@api {get} /rooms/:id/messages?skip=:skip&limit=:limit Get messages in a room. -@apiParam {Number} id Room ID -@apiParam {Number} [skip=0] Number of results to skip at the beginning -@apiParam {Number} [limit=50] Maximum number of results to return -### -Api.addRoute 'rooms/:id/messages', authRequired: true, - get: -> - try - rid = @urlParams.id - # `variable | 0` means converting to int - skip = @queryParams.skip | 0 or 0 - limit = @queryParams.limit | 0 or 50 - limit = 50 if limit > 50 - if Meteor.call('canAccessRoom', rid, this.userId) - msgs = RocketChat.models.Messages.findVisibleByRoomId(rid, - sort: - ts: -1 - skip: skip - limit: limit - ).fetch() - status: 'success', messages: msgs - else - statusCode: 403 # forbidden - body: status: 'fail', message: 'Cannot access room.' - catch e - statusCode: 400 # bad request or other errors - body: status: 'fail', message: e.name + ' :: ' + e.message - - - -# send a message in a room - POST body should be { "msg" : "this is my message"} -Api.addRoute 'rooms/:id/send', authRequired: true, - post: -> - Meteor.runAsUser this.userId, () => - console.log @bodyParams.msg - Meteor.call('sendMessage', {msg: this.bodyParams.msg, rid: @urlParams.id} ) - status: 'success' #need to handle error - -# get list of online users in a room -Api.addRoute 'rooms/:id/online', authRequired: true, - get: -> - room = RocketChat.models.Rooms.findOneById @urlParams.id - online = RocketChat.models.Users.findUsersNotOffline(fields: - username: 1 - status: 1).fetch() - onlineInRoom = [] - for user, i in online - if room.usernames.indexOf(user.username) != -1 - onlineInRoom.push user.username - - status: 'success', online: onlineInRoom - -# validate an array of users -Api.testapiValidateUsers = (users) -> - for user, i in users - if user.name? - if user.email? - if user.pass? - try - nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$', 'i' - catch - nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$', 'i' - - if nameValidation.test user.name - if /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+\b/i.test user.email - continue - throw new Meteor.Error 'invalid-user-record', "[restapi] bulk/register -> record #" + i + " is invalid" - return - - -### -@api {post} /bulk/register Register multiple users based on an input array. -@apiName register -@apiGroup TestAndAdminAutomation -@apiVersion 0.0.1 -@apiDescription Caller must have 'testagent' or 'adminautomation' role. -NOTE: remove room is NOT recommended; use Meteor.reset() to clear db and re-seed instead -@apiParam {json} rooms An array of users in the body of the POST. -@apiParamExample {json} POST Request Body example: - { - 'users':[ {'email': 'user1@user1.com', - 'name': 'user1', - 'pass': 'abc123' }, - {'email': 'user2@user2.com', - 'name': 'user2', - 'pass': 'abc123'}, - ... - ] - } -@apiSuccess {json} ids An array of IDs of the registered users. -@apiSuccessExample {json} Success-Response: - HTTP/1.1 200 OK - { - 'ids':[ {'uid': 'uid_1'}, - {'uid': 'uid_2'}, - ... - ] - } -### -Api.addRoute 'bulk/register', authRequired: true, - post: - # restivus 0.8.4 does not support alanning:roles using groups - #roleRequired: ['testagent', 'adminautomation'] - action: -> - if RocketChat.authz.hasPermission(@userId, 'bulk-register-user') - try - - Api.testapiValidateUsers @bodyParams.users - this.response.setTimeout (500 * @bodyParams.users.length) - ids = [] - endCount = @bodyParams.users.length - 1 - for incoming, i in @bodyParams.users - ids[i] = {uid: Meteor.call 'registerUser', incoming} - Meteor.runAsUser ids[i].uid, () => - Meteor.call 'setUsername', incoming.name - Meteor.call 'joinDefaultChannels' - - status: 'success', ids: ids - catch e - statusCode: 400 # bad request or other errors - body: status: 'fail', message: e.name + ' :: ' + e.message - else - console.log '[restapi] bulk/register -> '.red, "User does not have 'bulk-register-user' permission" - statusCode: 403 - body: status: 'error', message: 'You do not have permission to do this' - - - - -# validate an array of rooms -Api.testapiValidateRooms = (rooms) -> - for room, i in rooms - if room.name? - if room.members? - if room.members.length > 0 - try - nameValidation = new RegExp '^' + RocketChat.settings.get('UTF8_Names_Validation') + '$', 'i' - catch - nameValidation = new RegExp '^[0-9a-zA-Z-_.]+$', 'i' - - if nameValidation.test room.name - continue - throw new Meteor.Error 'invalid-room-record', "[restapi] bulk/createRoom -> record #" + i + " is invalid" - return - - -### -@api {post} /bulk/createRoom Create multiple rooms based on an input array. -@apiName createRoom -@apiGroup TestAndAdminAutomation -@apiVersion 0.0.1 -@apiParam {json} rooms An array of rooms in the body of the POST. 'name' is room name, 'members' is array of usernames -@apiParamExample {json} POST Request Body example: - { - 'rooms':[ {'name': 'room1', - 'members': ['user1', 'user2'] - }, - {'name': 'room2', - 'members': ['user1', 'user2', 'user3'] - } - ... - ] - } -@apiDescription Caller must have 'testagent' or 'adminautomation' role. -NOTE: remove room is NOT recommended; use Meteor.reset() to clear db and re-seed instead - -@apiSuccess {json} ids An array of ids of the rooms created. -@apiSuccessExample {json} Success-Response: - HTTP/1.1 200 OK - { - 'ids':[ {'rid': 'rid_1'}, - {'rid': 'rid_2'}, - ... - ] - } -### -Api.addRoute 'bulk/createRoom', authRequired: true, - post: - # restivus 0.8.4 does not support alanning:roles using groups - #roleRequired: ['testagent', 'adminautomation'] - action: -> - # user must also have create-c permission because - # createChannel method requires it - if RocketChat.authz.hasPermission(@userId, 'bulk-create-c') - try - this.response.setTimeout (1000 * @bodyParams.rooms.length) - Api.testapiValidateRooms @bodyParams.rooms - ids = [] - Meteor.runAsUser this.userId, () => - (if incoming.private - ids[i] = Meteor.call 'createPrivateGroup', incoming.name, incoming.members - else - ids[i] = Meteor.call 'createChannel', incoming.name, incoming.members) for incoming,i in @bodyParams.rooms - status: 'success', ids: ids # need to handle error - catch e - statusCode: 400 # bad request or other errors - body: status: 'fail', message: e.name + ' :: ' + e.message - else - console.log '[restapi] bulk/createRoom -> '.red, "User does not have 'bulk-create-c' permission" - statusCode: 403 - body: status: 'error', message: 'You do not have permission to do this' - -# archive a room by it's ID -Api.addRoute 'room/:id/archive', authRequired: true, - post: - action: -> - # user must also have archive-room permission - if RocketChat.authz.hasPermission(@userId, 'archive-room') - try - Meteor.runAsUser this.userId, () => - Meteor.call('archiveRoom', @urlParams.id) - status: 'success' # need to handle error - catch e - statusCode: 400 # bad request or other errors - body: status: 'fail', message: e.name + ' :: ' + e.message - else - console.log '[restapi] archiveRoom -> '.red, "User does not have 'archive-room' permission" - statusCode: 403 - body: status: 'error', message: 'You do not have permission to do this' - -# unarchive a room by it's ID -Api.addRoute 'room/:id/unarchive', authRequired: true, - post: - action: -> - # user must also have unarchive-room permission - if RocketChat.authz.hasPermission(@userId, 'unarchive-room') - try - Meteor.runAsUser this.userId, () => - Meteor.call('unarchiveRoom', @urlParams.id) - status: 'success' # need to handle error - catch e - statusCode: 400 # bad request or other errors - body: status: 'fail', message: e.name + ' :: ' + e.message - else - console.log '[restapi] unarchiveRoom -> '.red, "User does not have 'unarchive-room' permission" - statusCode: 403 - body: status: 'error', message: 'You do not have permission to do this' diff --git a/server/startup/avatar.coffee b/server/startup/avatar.coffee index 28ae6bc4f2b17d013b1d5a714f59a07221d4d947..bfb2e4f92acb57072a8a141ac71b309fffcca50f 100644 --- a/server/startup/avatar.coffee +++ b/server/startup/avatar.coffee @@ -1,3 +1,5 @@ +import getFileType from 'file-type' + Meteor.startup -> storeType = 'GridFS' @@ -18,7 +20,11 @@ Meteor.startup -> height = RocketChat.settings.get 'Accounts_AvatarSize' width = height - RocketChatFile.gm(readStream, file.fileName).background('#ffffff').resize(width, height+'^>').gravity('Center').extent(width, height).stream('jpeg').pipe(writeStream) + # - Resize the image using the width or height as minium value + # - Keep the image aspect ratio + # - Crop image to keep max size of width and height + # - Use extent to set background color for transparent images + RocketChatFile.gm(readStream, file.fileName).background('#ffffff').resize(width, height+'^').gravity('Center').crop(width, height).extent(width, height).stream('jpeg').pipe(writeStream) path = "~/uploads" @@ -34,6 +40,8 @@ Meteor.startup -> params = username: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')) + username = params.username.replace(/\.jpg$/, '').replace(/^@/, '') + if _.isEmpty params.username res.writeHead 403 res.write 'Forbidden' @@ -42,15 +50,13 @@ Meteor.startup -> if params.username[0] isnt '@' if Meteor.settings?.public?.sandstorm - user = RocketChat.models.Users.findOneByUsername(params.username.replace('.jpg', '')) + user = RocketChat.models.Users.findOneByUsername(username) if user?.services?.sandstorm?.picture res.setHeader 'Location', user.services.sandstorm.picture res.writeHead 302 res.end() return - file = RocketChatFileAvatarInstance.getFileWithReadStream encodeURIComponent(params.username) - else - params.username = params.username.replace '@', '' + file = RocketChatFileAvatarInstance.getFileWithReadStream encodeURIComponent("#{username}.jpg") #console.log "[avatar] checking username #{@params.username} (derrived from path #{req.url})" res.setHeader 'Content-Disposition', 'inline' @@ -70,7 +76,6 @@ Meteor.startup -> colors = ['#F44336','#E91E63','#9C27B0','#673AB7','#3F51B5','#2196F3','#03A9F4','#00BCD4','#009688','#4CAF50','#8BC34A','#CDDC39','#FFC107','#FF9800','#FF5722','#795548','#9E9E9E','#607D8B'] - username = params.username.replace('.jpg', '') if RocketChat.settings.get 'UI_Use_Name_Avatar' user = RocketChat.models.Users.findOneByUsername(username, { fields: { name: 1 } }) if user?.name @@ -116,8 +121,17 @@ Meteor.startup -> res.setHeader 'Cache-Control', 'public, max-age=0' res.setHeader 'Expires', '-1' res.setHeader 'Last-Modified', file.uploadDate?.toUTCString() or new Date().toUTCString() - res.setHeader 'Content-Type', 'image/jpeg' res.setHeader 'Content-Length', file.length + if file.contentType? + res.setHeader 'Content-Type', file.contentType + else + file.readStream.once 'data', (chunk) -> + fileType = getFileType(chunk) + if fileType? + res.setHeader 'Content-Type', fileType.mime + else + res.setHeader 'Content-Type', 'image/jpeg' + file.readStream.pipe res return diff --git a/server/startup/initialData.coffee b/server/startup/initialData.coffee index 84e492d9122625380c31e8f2a4f9eae0bbcda80f..5644753e73f4ae7e62426ca936083f48a4ddfa0d 100644 --- a/server/startup/initialData.coffee +++ b/server/startup/initialData.coffee @@ -5,7 +5,7 @@ Meteor.startup -> RocketChat.models.Rooms.createWithIdTypeAndName 'GENERAL', 'c', 'general', default: true - if not RocketChat.models.Users.findOneById('rocket.cat')? + if not RocketChat.models.Users.db.findOneById('rocket.cat')? RocketChat.models.Users.create _id: 'rocket.cat' name: "Rocket.Cat" @@ -89,3 +89,41 @@ Meteor.startup -> if oldestUser RocketChat.authz.addUserRoles( oldestUser._id, 'admin') console.log "No admins are found. Set #{oldestUser.username} as admin for being the oldest user" + + + RocketChat.models.Users.removeById 'rocketchat.internal.admin.test' + + if process.env.TEST_MODE is 'true' + console.log 'Inserting admin test user:'.green + + adminUser = + _id: 'rocketchat.internal.admin.test' + name: 'RocketChat Internal Admin Test' + username: 'rocketchat.internal.admin.test' + emails: [ + address: 'rocketchat.internal.admin.test@rocket.chat' + verified: true + ] + status: 'offline' + statusDefault: 'online' + utcOffset: 0 + active: true + type: 'user' + + console.log "Name: #{adminUser.name}".green + console.log "Email: #{adminUser.emails[0].address}".green + console.log "Username: #{adminUser.username}".green + console.log "Password: #{adminUser._id}".green + + if RocketChat.models.Users.db.findOneByEmailAddress(adminUser.emails[0].address) + throw new Meteor.Error "Email #{adminUser.emails[0].address} already exists", 'Rocket.Chat can\'t run in test mode' + + if not RocketChat.checkUsernameAvailability(adminUser.username) + throw new Meteor.Error "Username #{adminUser.username} already exists", 'Rocket.Chat can\'t run in test mode' + + RocketChat.models.Users.create adminUser + + Accounts.setPassword adminUser._id, adminUser._id + RocketChat.authz.addUserRoles(adminUser._id, 'admin') + + RocketChat.addUserToDefaultChannels(adminUser, true) diff --git a/server/startup/migrations/v067.js b/server/startup/migrations/v067.js new file mode 100644 index 0000000000000000000000000000000000000000..86a10fd7417e186b4863b140e93de83dd7d95177 --- /dev/null +++ b/server/startup/migrations/v067.js @@ -0,0 +1,12 @@ +RocketChat.Migrations.add({ + version: 67, + up: function() { + if (RocketChat && RocketChat.models && RocketChat.models.LivechatDepartment) { + RocketChat.models.LivechatDepartment.model.update({}, { + $set: { + showOnRegistration: true + } + }, { multi: true }); + } + } +}); diff --git a/server/startup/migrations/v068.js b/server/startup/migrations/v068.js new file mode 100644 index 0000000000000000000000000000000000000000..8039aa97b58e8b64ee5398ead8508769c8944d28 --- /dev/null +++ b/server/startup/migrations/v068.js @@ -0,0 +1,17 @@ +RocketChat.Migrations.add({ + version: 68, + up: function() { + var GoogleSiteVerification_id = RocketChat.models.Settings.findOne({ _id: 'GoogleSiteVerification_id' }); + + if (GoogleSiteVerification_id && GoogleSiteVerification_id.value) { + RocketChat.models.Settings.update({ _id: 'Meta_google-site-verification' }, { + $set: { + value: GoogleSiteVerification_id.value + } + }); + } + + RocketChat.models.Settings.remove({ _id: 'GoogleSiteVerification_id' }); + } +}); + diff --git a/server/startup/migrations/v069.js b/server/startup/migrations/v069.js new file mode 100644 index 0000000000000000000000000000000000000000..8e56b7a932d39114a7e217ef7778946342733dba --- /dev/null +++ b/server/startup/migrations/v069.js @@ -0,0 +1,87 @@ +RocketChat.Migrations.add({ + version: 69, + up: function() { + RocketChat.models.Settings.update({ + '_id': 'theme-color-custom-scrollbar-color', + 'value': 'rgba(255, 255, 255, 0.05)' + }, { + $set: { + 'editor': 'expression', + 'value': '@transparent-darker' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-info-font-color', + 'value': '#aaaaaa' + }, { + $set: { + 'editor': 'expression', + 'value': '@secondary-font-color' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-link-font-color', + 'value': '#008ce3' + }, { + $set: { + 'editor': 'expression', + 'value': '@primary-action-color' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-status-away', + 'value': '#fcb316' + }, { + $set: { + 'editor': 'expression', + 'value': '@pending-color' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-status-busy', + 'value': '#d30230' + }, { + $set: { + 'editor': 'expression', + 'value': '@error-color' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-status-offline', + 'value': 'rgba(150, 150, 150, 0.50)' + }, { + $set: { + 'editor': 'expression', + 'value': '@transparent-darker' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-status-online', + 'value': '#35ac19' + }, { + $set: { + 'editor': 'expression', + 'value': '@success-color' + } + }); + RocketChat.models.Settings.update({ + '_id': 'theme-color-tertiary-background-color', + 'value': '#eaeaea' + }, { + $set: { + 'editor': 'expression', + 'value': '@component-color' + } + }); + return RocketChat.models.Settings.update({ + '_id': 'theme-color-tertiary-font-color', + 'value': 'rgba(255, 255, 255, 0.6)' + }, { + $set: { + 'editor': 'expression', + 'value': '@transparent-lightest' + } + }); + } +}); + diff --git a/server/startup/migrations/v070.js b/server/startup/migrations/v070.js new file mode 100644 index 0000000000000000000000000000000000000000..cd4df94d909a9a39416ba542eff0d5f2c49f8c46 --- /dev/null +++ b/server/startup/migrations/v070.js @@ -0,0 +1,14 @@ +RocketChat.Migrations.add({ + version: 70, + up: function() { + const settings = RocketChat.models.Settings.find({ _id: /^Accounts_OAuth_Custom_.+/ }).fetch(); + for (const setting of settings) { + const _id = setting._id; + setting._id = setting._id.replace(/Accounts_OAuth_Custom_([A-Za-z0-9]+)_(.+)/, 'Accounts_OAuth_Custom-$1-$2'); + setting._id = setting._id.replace(/Accounts_OAuth_Custom_([A-Za-z0-9]+)/, 'Accounts_OAuth_Custom-$1'); + + RocketChat.models.Settings.remove({ _id: _id }); + RocketChat.models.Settings.insert(setting); + } + } +}); diff --git a/server/startup/migrations/v071.js b/server/startup/migrations/v071.js new file mode 100644 index 0000000000000000000000000000000000000000..b9dc8259ab9c7edd567dc28716115d53adbfd748 --- /dev/null +++ b/server/startup/migrations/v071.js @@ -0,0 +1,9 @@ +RocketChat.Migrations.add({ + version: 71, + up: function() { + //Removes the reactions on messages which are the system type "rm" ;) + RocketChat.models.Messages.find({ 't': 'rm', 'reactions': { $exists: true, $not: {$size: 0} } }, { t: 1 }).forEach(function(message) { + RocketChat.models.Messages.update({ _id: message._id }, { $set: { reactions: [] }}); + }); + } +}); diff --git a/server/startup/migrations/v072.js b/server/startup/migrations/v072.js new file mode 100644 index 0000000000000000000000000000000000000000..01f298b18be7dd118d186a508bc9bbaf070c4e65 --- /dev/null +++ b/server/startup/migrations/v072.js @@ -0,0 +1,15 @@ +RocketChat.Migrations.add({ + version: 72, + up: function() { + RocketChat.models.Users.find({ type: 'visitor', 'emails.address': { $exists: true } }, { emails: 1 }).forEach(function(visitor) { + RocketChat.models.Users.update({ _id: visitor._id }, { + $set: { + visitorEmails: visitor.emails + }, + $unset: { + emails: 1 + } + }); + }); + } +}); diff --git a/server/startup/migrations/v073.js b/server/startup/migrations/v073.js new file mode 100644 index 0000000000000000000000000000000000000000..e279a0b65ae233b288a4e4db5703c3c38a447524 --- /dev/null +++ b/server/startup/migrations/v073.js @@ -0,0 +1,15 @@ +RocketChat.Migrations.add({ + version: 73, + up: function() { + RocketChat.models.Users.find({ 'oauth.athorizedClients': { $exists: true } }, { oauth: 1 }).forEach(function(user) { + RocketChat.models.Users.update({ _id: user._id }, { + $set: { + 'oauth.authorizedClients': user.oauth.athorizedClients + }, + $unset: { + 'oauth.athorizedClients': 1 + } + }); + }); + } +}); diff --git a/server/startup/migrations/v074.js b/server/startup/migrations/v074.js new file mode 100644 index 0000000000000000000000000000000000000000..656b226c4efccac8f6ca5232ab9a05e0dfd2b35f --- /dev/null +++ b/server/startup/migrations/v074.js @@ -0,0 +1,16 @@ +RocketChat.Migrations.add({ + version: 74, + up: function() { + if (RocketChat && RocketChat.models && RocketChat.models.Settings) { + RocketChat.models.Settings.remove({_id: 'Assets_favicon_64'}); + RocketChat.models.Settings.remove({_id: 'Assets_favicon_96'}); + RocketChat.models.Settings.remove({_id: 'Assets_favicon_128'}); + RocketChat.models.Settings.remove({_id: 'Assets_favicon_256'}); + RocketChat.models.Settings.update({_id: 'Assets_favicon_192'}, { + $set: { + i18nLabel: 'android-chrome 192x192 (png)' + } + }); + } + } +}); diff --git a/server/startup/migrations/v075.js b/server/startup/migrations/v075.js new file mode 100644 index 0000000000000000000000000000000000000000..aaf3ba9b74fed44338763405956f72450d6b5554 --- /dev/null +++ b/server/startup/migrations/v075.js @@ -0,0 +1,14 @@ +RocketChat.Migrations.add({ + version: 71.1, + up: function() { + ServiceConfiguration.configurations.remove({}); + } +}); + +RocketChat.Migrations.add({ + version: 75, + up: function() { + ServiceConfiguration.configurations.remove({}); + } +}); + diff --git a/server/startup/migrations/v076.js b/server/startup/migrations/v076.js new file mode 100644 index 0000000000000000000000000000000000000000..4c19f9aaeee7d7a063156c172b9ccabe0212c534 --- /dev/null +++ b/server/startup/migrations/v076.js @@ -0,0 +1,10 @@ +RocketChat.Migrations.add({ + version: 76, + up: function() { + if (RocketChat && RocketChat.models && RocketChat.models.Settings) { + RocketChat.models.Settings.find({section: 'Colors (alphas)'}).forEach((setting) => { + RocketChat.models.Settings.remove({ _id: setting._id }); + }); + } + } +}); diff --git a/server/startup/migrations/v077.js b/server/startup/migrations/v077.js new file mode 100644 index 0000000000000000000000000000000000000000..6e757ccb23d51247aa07fa7a81e13d352cb52627 --- /dev/null +++ b/server/startup/migrations/v077.js @@ -0,0 +1,23 @@ +RocketChat.Migrations.add({ + version: 77, + up: function() { + if (RocketChat && RocketChat.models && RocketChat.models.Rooms) { + RocketChat.models.Rooms.find({ + t: 'l', + 'v._id': { $exists: true }, + 'v.username': { $exists: false } + }, { fields: { 'v._id': 1 } }).forEach(function(room) { + const user = RocketChat.models.Users.findOne({ _id: room.v._id }, { username: 1 }); + + RocketChat.models.Rooms.update({ + _id: room._id + }, { + $set: { + 'v.username': user.username + } + }); + }); + } + } +}); + diff --git a/server/startup/migrations/v078.js b/server/startup/migrations/v078.js new file mode 100644 index 0000000000000000000000000000000000000000..41f5dbb4a5a7179da2aae24ead3b162a61ad9314 --- /dev/null +++ b/server/startup/migrations/v078.js @@ -0,0 +1,6 @@ +RocketChat.Migrations.add({ + version: 78, + up: function() { + RocketChat.models.Permissions.update({ _id: { $in: ['create-c', 'create-d', 'create-p'] }}, { $addToSet: { roles: 'bot' }}, { multi: true }); + } +}); diff --git a/server/startup/migrations/v079.js b/server/startup/migrations/v079.js new file mode 100644 index 0000000000000000000000000000000000000000..a3e4571bab9cf91c0d4e42f6a5aa120b55531099 --- /dev/null +++ b/server/startup/migrations/v079.js @@ -0,0 +1,16 @@ +RocketChat.Migrations.add({ + version: 79, + up: function() { + const integrations = RocketChat.models.Integrations.find({type: 'webhook-incoming'}).fetch(); + + for (const integration of integrations) { + if (typeof integration.channel === 'string') { + RocketChat.models.Integrations.update({_id: integration._id}, { + $set: { + channel: integration.channel.split(',').map(channel => channel.trim()) + } + }); + } + } + } +}); diff --git a/server/startup/roomPublishes.coffee b/server/startup/roomPublishes.coffee index c1c1c7397210f41154bc4c706a21bcd5c58c4fdb..b80504eeebbccc5a2757d3084384d85172b17fb9 100644 --- a/server/startup/roomPublishes.coffee +++ b/server/startup/roomPublishes.coffee @@ -11,6 +11,7 @@ Meteor.startup -> muted: 1 archived: 1 ro: 1 + reactWhenReadOnly: 1 jitsiTimeout: 1 description: 1 sysMes: 1 @@ -20,10 +21,12 @@ Meteor.startup -> options.fields.joinCode = 1 if RocketChat.authz.hasPermission(this.userId, 'view-c-room') + # CACHE: can we stop using publications here? return RocketChat.models.Rooms.findByTypeAndName 'c', identifier, options else if RocketChat.authz.hasPermission(this.userId, 'view-joined-room') roomId = RocketChat.models.Subscriptions.findByTypeNameAndUserId('c', identifier, this.userId).fetch() if roomId.length > 0 + # CACHE: can we stop using publications here? return RocketChat.models.Rooms.findById(roomId[0]?.rid, options) return this.ready() @@ -40,11 +43,13 @@ Meteor.startup -> muted: 1 archived: 1 ro: 1 + reactWhenReadOnly: 1 jitsiTimeout: 1 description: 1 sysMes: 1 user = RocketChat.models.Users.findOneById this.userId, fields: username: 1 + # CACHE: can we stop using publications here? return RocketChat.models.Rooms.findByTypeAndNameContainingUsername 'p', identifier, user.username, options RocketChat.roomTypes.setPublish 'd', (identifier) -> @@ -60,5 +65,6 @@ Meteor.startup -> user = RocketChat.models.Users.findOneById this.userId, fields: username: 1 if RocketChat.authz.hasAtLeastOnePermission(this.userId, ['view-d-room', 'view-joined-room']) - return RocketChat.models.Rooms.findByTypeContainigUsernames 'd', [user.username, identifier], options + # CACHE: can we stop using publications here? + return RocketChat.models.Rooms.findByTypeContainingUsernames 'd', [user.username, identifier], options return this.ready() diff --git a/server/startup/serverRunning.coffee b/server/startup/serverRunning.coffee index 4852c80c4e1ef8dc554d21ac75060ed5198278b7..4eb537553d3648eecbcd439efb1b9d76cf33cfa9 100644 --- a/server/startup/serverRunning.coffee +++ b/server/startup/serverRunning.coffee @@ -1,14 +1,16 @@ Meteor.startup -> - isOplogState = 'Enabled' - if not MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle? - isOplogState = 'Disabled' + oplogState = 'Disabled' + if MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle?.onOplogEntry? + oplogState = 'Enabled' + if RocketChat.settings.get('Force_Disable_OpLog_For_Cache') is true + oplogState += ' (Disabled for Cache Sync)' Meteor.setTimeout -> msg = [ " Version: #{RocketChat.Info.version}" "Process Port: #{process.env.PORT}" " Site URL: #{RocketChat.settings.get('Site_Url')}" - " OpLog: #{isOplogState}" + " OpLog: #{oplogState}" ].join('\n') SystemLogger.startup_box msg, 'SERVER RUNNING' diff --git a/server/stream/messages.coffee b/server/stream/messages.coffee index 45cd8cf9688d64aa98af2cbfbd1545fca01cf65b..6141449dd368d4ea4e0223a01e1188d0f1b382ef 100644 --- a/server/stream/messages.coffee +++ b/server/stream/messages.coffee @@ -37,10 +37,25 @@ Meteor.startup -> if not RocketChat.settings.get 'Message_ShowEditedStatus' fields = { 'editedAt': 0 } - RocketChat.models.Messages.on 'change', (type, args...) -> - records = RocketChat.models.Messages.getChangedRecords type, args[0], fields - - for record in records - if record._hidden isnt true and not record.imported? - msgStream.emit '__my_messages__', record, {} - msgStream.emit record.rid, record + publishMessage = (type, record) -> + if record._hidden isnt true and not record.imported? + msgStream.emitWithoutBroadcast '__my_messages__', record, {} + msgStream.emitWithoutBroadcast record.rid, record + + query = + collection: RocketChat.models.Messages.collectionName + + RocketChat.models.Messages._db.on 'change', ({action, id, data, oplog}) => + switch action + when 'insert' + data._id = id; + publishMessage 'inserted', data + break; + + when 'update:record' + publishMessage 'updated', data; + break; + + when 'update:diff' + publishMessage 'updated', RocketChat.models.Messages.findOne({_id: id}) + break; diff --git a/server/stream/streamBroadcast.coffee b/server/stream/streamBroadcast.coffee index 4ac497ea5826020d30ccbd619f0ea88f9c222159..1afaa9f1b1e993e0285486f0b2dafd86b811f9ca 100644 --- a/server/stream/streamBroadcast.coffee +++ b/server/stream/streamBroadcast.coffee @@ -33,7 +33,7 @@ startMatrixBroadcast = -> logger.auth.info "prevent self connect", instance return - if record.extraInformation.host is process.env.INSTANCE_IP + if record.extraInformation.host is process.env.INSTANCE_IP and RocketChat.isDocker() is false instance = "localhost:#{record.extraInformation.port}" if connections[instance]?.instanceRecord? @@ -53,7 +53,7 @@ startMatrixBroadcast = -> removed: (record) -> instance = "#{record.extraInformation.host}:#{record.extraInformation.port}" - if record.extraInformation.host is process.env.INSTANCE_IP + if record.extraInformation.host is process.env.INSTANCE_IP and RocketChat.isDocker() is false instance = "localhost:#{record.extraInformation.port}" if connections[instance]? and not InstanceStatus.getCollection().findOne({'extraInformation.host': record.extraInformation.host, 'extraInformation.port': record.extraInformation.port})? @@ -61,32 +61,32 @@ startMatrixBroadcast = -> connections[instance].disconnect() delete connections[instance] - Meteor.methods - broadcastAuth: (remoteId, selfId) -> - check selfId, String - check remoteId, String +Meteor.methods + broadcastAuth: (remoteId, selfId) -> + check selfId, String + check remoteId, String - @unblock() - if selfId is InstanceStatus.id() and remoteId isnt InstanceStatus.id() and InstanceStatus.getCollection().findOne({_id: remoteId})? - @connection.broadcastAuth = true + @unblock() + if selfId is InstanceStatus.id() and remoteId isnt InstanceStatus.id() and InstanceStatus.getCollection().findOne({_id: remoteId})? + @connection.broadcastAuth = true - return @connection.broadcastAuth is true + return @connection.broadcastAuth is true - stream: (streamName, eventName, args) -> - # Prevent call from self and client - if not @connection? - return 'self-not-authorized' + stream: (streamName, eventName, args) -> + # Prevent call from self and client + if not @connection? + return 'self-not-authorized' - # Prevent call from unauthrorized connections - if @connection.broadcastAuth isnt true - return 'not-authorized' + # Prevent call from unauthrorized connections + if @connection.broadcastAuth isnt true + return 'not-authorized' - if not Meteor.StreamerCentral.instances[streamName]? - return 'stream-not-exists' + if not Meteor.StreamerCentral.instances[streamName]? + return 'stream-not-exists' - Meteor.StreamerCentral.instances[streamName]._emit(eventName, args) + Meteor.StreamerCentral.instances[streamName]._emit(eventName, args) - return undefined + return undefined startStreamCastBroadcast = (value) -> instance = 'StreamCast' diff --git a/tests/chimp-config.js b/tests/chimp-config.js index 56b10394416a73a07d4c34f53c2e8f47bd6b2196..93821020e4db8be1b2b6944e4bb06fe744d6de16 100644 --- a/tests/chimp-config.js +++ b/tests/chimp-config.js @@ -1,138 +1,153 @@ +// import path from 'path'; +// import {isCI} from '../lib/ci'; + module.exports = { - // - - - - CHIMP - - - - - watch: false, - watchTags: '@watch,@focus', - domainSteps: null, - e2eSteps: null, - fullDomain: false, - domainOnly: false, - e2eTags: '@e2e', - watchWithPolling: false, - server: false, - serverPort: 8060, - serverHost: 'localhost', - sync: true, - offline: false, - showXolvioMessages: true, +// // - - - - CHIMP - - - - +// watch: false, +// watchTags: '@watch,@focus', +// domainSteps: null, +// e2eSteps: null, +// fullDomain: false, +// domainOnly: false, +// e2eTags: '@e2e', +// watchWithPolling: false, +// server: false, +// serverPort: 8060, +// serverHost: 'localhost', +// sync: true, +// offline: false, +// showXolvioMessages: true, - // - - - - CUCUMBER - - - - - // path: './features', - format: 'pretty', - tags: '~@ignore', - singleSnippetPerFile: true, - recommendedFilenameSeparator: '_', - chai: false, - // screenshotsOnError: isCI(), - screenshotsPath: '.screenshots', - captureAllStepScreenshots: false, - saveScreenshotsToDisk: true, - // Note: With a large viewport size and captureAllStepScreenshots enabled, - // you may run out of memory. Use browser.setViewportSize to make the - // viewport size smaller. - saveScreenshotsToReport: false, - jsonOutput: null, - // compiler: 'js:' + path.resolve(__dirname, '../lib/babel-register.js'), - conditionOutput: true, +// // - - - - CUCUMBER - - - - + path: 'tests/steps', +// format: 'pretty', +// tags: '~@ignore', +// singleSnippetPerFile: true, +// recommendedFilenameSeparator: '_', +// chai: false, +// screenshotsOnError: isCI(), +// screenshotsPath: '.screenshots', +// captureAllStepScreenshots: false, +// saveScreenshotsToDisk: true, +// // Note: With a large viewport size and captureAllStepScreenshots enabled, +// // you may run out of memory. Use browser.setViewportSize to make the +// // viewport size smaller. +// saveScreenshotsToReport: false, +// jsonOutput: null, +// compiler: 'js:' + path.resolve(__dirname, '../lib/babel-register.js'), +// conditionOutput: true, - // - - - - SELENIUM - - - - - browser: 'chrome', - platform: 'ANY', - name: '', - user: '', - key: '', - port: null, - host: null, - // deviceName: null, +// // - - - - SELENIUM - - - - +// browser: null, +// platform: 'ANY', +// name: '', +// user: '', +// key: '', +// port: null, +// host: null, +// // deviceName: null, - // - - - - WEBDRIVER-IO - - - - - webdriverio: { - desiredCapabilities: {}, - logLevel: 'silent', - // logOutput: null, - host: '127.0.0.1', - port: 4444, - path: '/wd/hub', - baseUrl: null, - coloredLogs: true, - screenshotPath: null, - waitforTimeout: 500, - waitforInterval: 250 - }, +// // - - - - WEBDRIVER-IO - - - - +// webdriverio: { +// desiredCapabilities: {}, +// logLevel: 'silent', +// // logOutput: null, +// host: '127.0.0.1', +// port: 4444, +// path: '/wd/hub', +// baseUrl: null, +// coloredLogs: true, +// screenshotPath: null, +// waitforTimeout: 500, +// waitforInterval: 250, +// }, - // - - - - SELENIUM-STANDALONE - seleniumStandaloneOptions: { - // check for more recent versions of selenium here: - // http://selenium-release.storage.googleapis.com/index.html - version: '2.53.1', - baseURL: 'https://selenium-release.storage.googleapis.com', - drivers: { - chrome: { - // check for more recent versions of chrome driver here: - // http://chromedriver.storage.googleapis.com/index.html - version: '2.22', - arch: process.arch, - baseURL: 'https://chromedriver.storage.googleapis.com' - }, - ie: { - // check for more recent versions of internet explorer driver here: - // http://selenium-release.storage.googleapis.com/index.html - version: '2.50.0', - arch: 'ia32', - baseURL: 'https://selenium-release.storage.googleapis.com' - } - } - }, +// // - - - - SELENIUM-STANDALONE +// seleniumStandaloneOptions: { +// // check for more recent versions of selenium here: +// // http://selenium-release.storage.googleapis.com/index.html +// version: '2.53.1', +// baseURL: 'https://selenium-release.storage.googleapis.com', +// drivers: { +// chrome: { +// // check for more recent versions of chrome driver here: +// // http://chromedriver.storage.googleapis.com/index.html +// version: '2.25', +// arch: process.arch, +// baseURL: 'https://chromedriver.storage.googleapis.com' +// }, +// ie: { +// // check for more recent versions of internet explorer driver here: +// // http://selenium-release.storage.googleapis.com/index.html +// version: '2.50.0', +// arch: 'ia32', +// baseURL: 'https://selenium-release.storage.googleapis.com' +// }, +// firefox: { +// // check for more recent versions of gecko driver here: +// // https://github.com/mozilla/geckodriver/releases +// version: '0.11.1', +// arch: process.arch, +// baseURL: 'https://github.com/mozilla/geckodriver/releases/download' +// } +// } +// }, - // - - - - SESSION-MANAGER - - - - - noSessionReuse: false, +// // - - - - SESSION-MANAGER - - - - +// noSessionReuse: false, - // - - - - SIMIAN - - - - - simianResultEndPoint: 'api.simian.io/v1.0/result', - simianAccessToken: false, - simianResultBranch: null, - simianRepositoryId: null, +// // - - - - SIMIAN - - - - +// simianResultEndPoint: 'api.simian.io/v1.0/result', +// simianAccessToken: false, +// simianResultBranch: null, +// simianRepositoryId: null, - // - - - - MOCHA - - - - +// // - - - - MOCHA - - - - mocha: true, - // mochaTags and mochaGrep only work when watch is false (disabled) - mochaTags: '', - mochaGrep: null, - path: './tests/steps', - mochaTimeout: 60000, - mochaReporter: 'spec', - mochaSlow: 0, - - // - - - - JASMINE - - - - - jasmine: false, - jasmineConfig: { - specDir: '.', - specFiles: [ - '**/*@(_spec|-spec|Spec).@(js|jsx)' - ], - helpers: [ - 'support/**/*.@(js|jsx)' - ], - stopSpecOnExpectationFailure: true, - random: false - }, - jasmineReporterConfig: { - // This options are passed to jasmine.configureDefaultReporter(...) - // See: http://jasmine.github.io/2.4/node.html#section-Reporters + mochaCommandLineOptions: ['--color'], + mochaConfig: { + // tags and grep only work when watch mode is false + tags: '', + grep: null, + timeout: 60000, + reporter: 'spec', + slow: 100, + bail: false // bail after first test failure }, - // - - - - METEOR - - - - - ddp: false, +// // - - - - JASMINE - - - - +// jasmine: false, +// jasmineConfig: { +// specDir: '.', +// specFiles: [ +// '**/*@(_spec|-spec|Spec).@(js|jsx)', +// ], +// helpers: [ +// 'support/**/*.@(js|jsx)', +// ], +// stopSpecOnExpectationFailure: false, +// random: false, +// }, +// jasmineReporterConfig: { +// // This options are passed to jasmine.configureDefaultReporter(...) +// // See: http://jasmine.github.io/2.4/node.html#section-Reporters +// }, + +// // - - - - METEOR - - - - + ddp: 'http://localhost:3000' +// serverExecuteTimeout: 10000, - // - - - - PHANTOM - - - - - phantom_w: 1280, - phantom_h: 1024, +// // - - - - PHANTOM - - - - +// phantom_w: 1280, +// phantom_h: 1024, +// phantom_ignoreSSLErrors: false, - // - - - - DEBUGGING - - - - - log: 'info', - debug: false, - seleniumDebug: null, - debugCucumber: null, - debugBrkCucumber: null, - debugMocha: null, - debugBrkMocha: null +// // - - - - DEBUGGING - - - - +// log: 'info', +// debug: false, +// seleniumDebug: null, +// debugCucumber: null, +// debugBrkCucumber: null, +// debugMocha: null, +// debugBrkMocha: null, }; diff --git a/tests/test-data/channel.js b/tests/data/channel.js similarity index 100% rename from tests/test-data/channel.js rename to tests/data/channel.js diff --git a/tests/data/checks.js b/tests/data/checks.js new file mode 100644 index 0000000000000000000000000000000000000000..f29b68081f1f20200fd59a10e7f65399bd09130d --- /dev/null +++ b/tests/data/checks.js @@ -0,0 +1,84 @@ +import loginPage from '../pageobjects/login.page'; +import mainContent from '../pageobjects/main-content.page'; +import sideNav from '../pageobjects/side-nav.page'; + +export var publicChannelCreated = false; +export var privateChannelCreated = false; +export var directMessageCreated = false; + +export function setPublicChannelCreated(status) { + publicChannelCreated = status; +} + +export function setPrivateChannelCreated(status) { + privateChannelCreated = status; +} + +export function setDirectMessageCreated(status) { + directMessageCreated = status; +} + +export function checkIfUserIsValid(username, email, password) { + if (!sideNav.accountBoxUserName.isVisible()) { + //if the user is not logged in. + console.log(' User not logged. logging in...'); + loginPage.open(); + loginPage.login({email, password}); + try { + mainContent.mainContent.waitForExist(5000); + } catch (e) { + //if the user dont exist. + console.log(' User dont exist. Creating user...'); + loginPage.gotToRegister(); + loginPage.registerNewUser({username, email, password}); + browser.waitForExist('form#login-card input#username', 5000); + browser.click('.submit > button'); + mainContent.mainContent.waitForExist(5000); + } + } else if (sideNav.accountBoxUserName.getText() !== username) { + //if the logged user is not the right one + console.log(' Wrong logged user. Changing user...'); + sideNav.accountBoxUserName.waitForVisible(5000); + sideNav.accountBoxUserName.click(); + sideNav.logout.waitForVisible(5000); + sideNav.logout.click(); + + loginPage.open(); + loginPage.login({email, password}); + mainContent.mainContent.waitForExist(5000); + } else { + console.log(' User already logged'); + } +} + +export function checkIfUserIsAdmin(username, email, password) { + if (!sideNav.accountBoxUserName.isVisible()) { + //if the user is not logged in. + console.log(' User not logged. logging in...'); + loginPage.open(); + loginPage.login({email, password}); + try { + mainContent.mainContent.waitForExist(5000); + } catch (e) { + //if the user dont exist. + console.log(' Admin User dont exist. Creating user...'); + loginPage.gotToRegister(); + loginPage.registerNewUser({username, email, password}); + browser.waitForExist('form#login-card input#username', 5000); + browser.click('.submit > button'); + mainContent.mainContent.waitForExist(5000); + } + } else if (sideNav.accountBoxUserName.getText() !== username) { + //if the logged user is not the right one + console.log(' Wrong logged user. Changing user...'); + sideNav.accountBoxUserName.waitForVisible(5000); + sideNav.accountBoxUserName.click(); + sideNav.logout.waitForVisible(5000); + sideNav.logout.click(); + + loginPage.open(); + loginPage.login({email, password}); + } else { + console.log(' User already logged'); + } +} diff --git a/tests/test-data/interactions.js b/tests/data/interactions.js similarity index 100% rename from tests/test-data/interactions.js rename to tests/data/interactions.js diff --git a/tests/data/user.js b/tests/data/user.js new file mode 100644 index 0000000000000000000000000000000000000000..3693d2244fc72afbb00187ede741c0149efa5960 --- /dev/null +++ b/tests/data/user.js @@ -0,0 +1,7 @@ +export const username = 'user.test.'+Date.now(); +export const email = username+'@rocket.chat'; +export const password = 'rocket.chat'; + +export const adminUsername = 'rocketchat.internal.admin.test'; +export const adminEmail = adminUsername+'@rocket.chat'; +export const adminPassword = adminUsername; diff --git a/tests/pageobjects/administration.page.js b/tests/pageobjects/administration.page.js new file mode 100644 index 0000000000000000000000000000000000000000..2e337b314fa5bb496e447af3a9733af438682ff3 --- /dev/null +++ b/tests/pageobjects/administration.page.js @@ -0,0 +1,120 @@ +import Page from './Page'; + +class Administration extends Page { + get flexNav() { return browser.element('.flex-nav'); } + get flexNavContent() { return browser.element('.flex-nav .content'); } + get layoutLink() { return browser.element('.flex-nav .content [href="/admin/Layout"]'); } + get infoLink() { return browser.element('.flex-nav .content [href="/admin/info"]'); } + get roomsLink() { return browser.element('.flex-nav .content [href="/admin/rooms"]'); } + get usersLink() { return browser.element('.flex-nav .content [href="/admin/users"]'); } + get generalLink() { return browser.element('.flex-nav .content [href="/admin/General"]'); } + get permissionsLink() { return browser.element('.flex-nav .content [href="/admin/permissions"]'); } + get customScriptBtn() { return browser.element('.section:nth-of-type(6) .collapse'); } + get customScriptLoggedOutTextArea() { return browser.element('.section:nth-of-type(6) .CodeMirror-scroll'); } + get customScriptLoggedInTextArea() { return browser.element('.CodeMirror.cm-s-default:nth-of-type(2)'); } + get infoRocketChatTableTitle() { return browser.element('.content h3'); } + get infoRocketChatTable() { return browser.element('.content .statistics-table'); } + get infoCommitTableTitle() { return browser.element('.content h3:nth-of-type(2)'); } + get infoCommitTable() { return browser.element('.content .statistics-table:nth-of-type(2)'); } + get infoRuntimeTableTitle() { return browser.element('.content h3:nth-of-type(3)'); } + get infoRuntimeTable() { return browser.element('.content .statistics-table:nth-of-type(3)'); } + get infoBuildTableTitle() { return browser.element('.content h3:nth-of-type(4)'); } + get infoBuildTable() { return browser.element('.content .statistics-table:nth-of-type(4)'); } + get infoUsageTableTitle() { return browser.element('.content h3:nth-of-type(5)'); } + get infoUsageTable() { return browser.element('.content .statistics-table:nth-of-type(5)'); } + get roomsSearchForm() { return browser.element('.content .search'); } + get roomsFilter() { return browser.element('#rooms-filter'); } + get roomsChannelsCheckbox() { return browser.element('label:nth-of-type(1) input[name="room-type"]'); } + get roomsDirectCheckbox() { return browser.element('label:nth-of-type(2) input[name="room-type"]'); } + get roomsPrivateCheckbox() { return browser.element('label:nth-of-type(3) input[name="room-type"]'); } + get roomsGeneralChannel() { return browser.element('td=general'); } + get usersRocketCat() { return browser.element('td=Rocket.Cat'); } + get usersInternalAdmin() { return browser.element('td=rocketchat.internal.admin.test'); } + get usersFilter() { return browser.element('#users-filter'); } + get rolesNewRolesButton() { return browser.element('.button.new-role'); } + get rolesPermissionGrid() { return browser.element('.permission-grid'); } + get rolesAdmin() { return browser.element('[title="Admin"]'); } + get rolesModerator() { return browser.element('[title="Moderator"]'); } + get rolesOwner() { return browser.element('[title="Owner"]'); } + get rolesReturnLink() { return browser.element('[href="/admin/permissions"]'); } + get rolesNewRoleName() { return browser.element('[name="name"]'); } + get rolesNewRoleDesc() { return browser.element('[name="description"]'); } + get rolesNewRoleScope() { return browser.element('[name="scope"]'); } + get rolesAddBtn() { return browser.element('button.add'); } + get rolesRoomsSearchForm() { return browser.element('.search [name="room"]'); } + + //permissions grids checkboxes + + get rolesUserCreateC() { return browser.element('[name="perm[user][create-c]"]'); } + get rolesUserCreateP() { return browser.element('[name="perm[user][create-p]"]'); } + get rolesUserCreateD() { return browser.element('[name="perm[user][create-d]"]'); } + get rolesUserMentionAll() { return browser.element('[name="perm[user][mention-all]"]'); } + get rolesUserPreviewC() { return browser.element('[name="perm[user][preview-c-room]"]'); } + get rolesUserViewC() { return browser.element('[name="perm[user][view-c-room]"]'); } + get rolesUserViewD() { return browser.element('[name="perm[user][view-d-room]"]'); } + get rolesUserViewP() { return browser.element('[name="perm[user][view-p-room]"]'); } + get rolesUserHistory() { return browser.element('[name="perm[user][view-history]"]'); } + get rolesOwnerDeleteMessage() { return browser.element('[name="perm[owner][delete-message]"]'); } + get rolesOwnerEditMessage() { return browser.element('[name="perm[owner][edit-message]"]'); } + + + get emojiFilter() { return browser.element('#emoji-filter'); } + + //settings + get generalButtonExpandIframe() { return browser.element('.section:nth-of-type(2) .button.expand'); } + get generalButtonExpandNotifications() { return browser.element('.section:nth-of-type(3) .button.expand'); } + get generalButtonExpandRest() { return browser.element('.section:nth-of-type(4) .button.expand'); } + get generalButtonExpandReporting() { return browser.element('.section:nth-of-type(5) .button.expand'); } + get generalButtonExpandStreamCast() { return browser.element('.section:nth-of-type(6) .button.expand'); } + get generalButtonExpandTranslations() { return browser.element('.section:nth-of-type(7) .button.expand'); } + get generalButtonExpandUTF8() { return browser.element('.section:nth-of-type(8) .button.expand'); } + + get generalSiteUrl() { return browser.element('[name="Site_Url"]'); } + get generalSiteUrlReset() { return browser.element('.reset-setting[data-setting="Site_Url"]'); } + get generalSiteName() { return browser.element('[name="Site_Name"]'); } + get generalSiteNameReset() { return browser.element('.reset-setting[data-setting="Site_Name"]'); } + get generalLanguage() { return browser.element('[name="Language"]'); } + get generalLanguagePtOption() { return browser.element('[value="pt"]'); } + get generalLanguageReset() { return browser.element('.reset-setting[data-setting="Language"]'); } + get generalSelfSignedCertsTrue() { return browser.element('label:nth-of-type(1) [name="Allow_Invalid_SelfSigned_Certs"]'); } + get generalSelfSignedCertsFalse() { return browser.element('label:nth-of-type(2) [name="Allow_Invalid_SelfSigned_Certs"]'); } + get generalSelfSignedCertsReset() { return browser.element('.reset-setting[data-setting="Allow_Invalid_SelfSigned_Certs"]'); } + get generalFavoriteRoomTrue() { return browser.element('label:nth-of-type(1) [name="Favorite_Rooms"]'); } + get generalFavoriteRoomFalse() { return browser.element('label:nth-of-type(2) [name="Favorite_Rooms"]'); } + get generalFavoriteRoomReset() { return browser.element('.reset-setting[data-setting="Favorite_Rooms"]'); } + get generalCdnPrefix() { return browser.element('[name="CDN_PREFIX"]'); } + get generalCdnPrefixReset() { return browser.element('.reset-setting[data-setting="CDN_PREFIX"]'); } + get generalForceSSLTrue() { return browser.element('label:nth-of-type(1) [name="Force_SSL"]'); } + get generalForceSSLFalse() { return browser.element('label:nth-of-type(2) [name="Force_SSL"]'); } + get generalForceSSLReset() { return browser.element('.reset-setting[data-setting="Force_SSL"]'); } + get generalGoogleTagId() { return browser.element('[name="GoogleTagManager_id"]'); } + get generalGoogleTagIdReset() { return browser.element('.reset-setting[data-setting="GoogleTagManager_id"]'); } + get generalBugsnagKey() { return browser.element('[name="Bugsnag_api_key"]'); } + get generalBugsnagKeyReset() { return browser.element('.reset-setting[data-setting="Bugsnag_api_key"]'); } + get generalIframeSendTrue() { return browser.element('label:nth-of-type(1) [name="Iframe_Integration_send_enable"]'); } + get generalIframeSendFalse() { return browser.element('label:nth-of-type(2) [name="Iframe_Integration_send_enable"]'); } + get generalIframeSendReset() { return browser.element('.reset-setting[data-setting="Iframe_Integration_send_enable"]'); } + get generalIframeSendTargetOrigin() { return browser.element('[name="Iframe_Integration_send_target_origin"]'); } + get generalIframeSendTargetOriginReset() { return browser.element('.reset-setting[data-setting="Iframe_Integration_send_target_origin"]'); } + get generalIframeRecieveTrue() { return browser.element('label:nth-of-type(1) [name="Iframe_Integration_receive_enable"]'); } + get generalIframeRecieveFalse() { return browser.element('label:nth-of-type(2) [name="Iframe_Integration_receive_enable"]'); } + get generalIframeRecieveFalseReset() { return browser.element('.reset-setting[data-setting="Iframe_Integration_receive_enable"]'); } + get generalIframeRecieveOrigin() { return browser.element('[name="Iframe_Integration_receive_origin"]'); } + get generalIframeRecieveOriginReset() { return browser.element('.reset-setting[data-setting="Iframe_Integration_receive_origin"]'); } + get generalNotificationDuration() { return browser.element('[name="Desktop_Notifications_Duration"]'); } + get generalNotificationDurationReset() { return browser.element('.reset-setting[data-setting="Desktop_Notifications_Duration"]'); } + get generalRestApiUserLimit() { return browser.element('[name="API_User_Limit"]'); } + get generalRestApiUserLimitReset() { return browser.element('.reset-setting[data-setting="API_User_Limit"]'); } + get generalReportingTrue() { return browser.element('label:nth-of-type(1) [name="Statistics_reporting"]'); } + get generalReportingFalse() { return browser.element('label:nth-of-type(2) [name="Statistics_reporting"]'); } + get generalReportingReset() { return browser.element('.reset-setting[data-setting="Statistics_reporting"]'); } + get generalStreamCastAdress() { return browser.element('[name="Stream_Cast_Address"]'); } + get generalStreamCastAdressReset() { return browser.element('.reset-setting[data-setting="Stream_Cast_Address"]'); } + get generalUTF8Regex() { return browser.element('[name="UTF8_Names_Validation"]'); } + get generalUTF8RegexReset() { return browser.element('.reset-setting[data-setting="UTF8_Names_Validation"]'); } + get generalUTF8NamesSlugTrue() { return browser.element('label:nth-of-type(1) [name="UTF8_Names_Slugify"]'); } + get generalUTF8NamesSlugFalse() { return browser.element('label:nth-of-type(2) [name="UTF8_Names_Slugify"]'); } + get generalUTF8NamesSlugReset() { return browser.element('.reset-setting[data-setting="UTF8_Names_Slugify"]'); } +} + +module.exports = new Administration(); diff --git a/tests/pageobjects/flex-tab.page.js b/tests/pageobjects/flex-tab.page.js index c31a12a0ecadba1670d7bbbda35c5b9d7b06da12..4ddb165e3f6a6b45aa9a347db96112ca6b9774f5 100644 --- a/tests/pageobjects/flex-tab.page.js +++ b/tests/pageobjects/flex-tab.page.js @@ -67,14 +67,36 @@ class FlexTab extends Page { get sweetAlertOverlay() { return browser.element('.sweet-overlay'); } get toastAlert() { return browser.element('.toast'); } + //admin view flextab items + get usersSendInvitationTab() { return browser.element('[aria-label="Invite Users"] .icon-paper-plane'); } + get usersAddUserTab() { return browser.element('[aria-label="Add User"] .icon-plus'); } + get usersSendInvitationTextArea() { return browser.element('#inviteEmails'); } + get usersButtonCancel() { return browser.element('button.cancel'); } + get usersSendInvitationSend() { return browser.element('button.send'); } + get usersButtonSave() { return browser.element('button.save'); } + get usersAddUserName() { return browser.element('#name'); } + get usersAddUserUsername() { return browser.element('#username'); } + get usersAddUserEmail() { return browser.element('#email'); } + get usersAddUserPassword() { return browser.element('#password'); } + get usersAddUserRole() { return browser.element('#role'); } + get usersAddUserVerifiedCheckbox() { return browser.element('#verified'); } + get usersAddUserChangePasswordCheckbox() { return browser.element('#changePassword'); } + get usersAddUserDefaultChannelCheckbox() { return browser.element('#joinDefaultChannels'); } + get usersAddUserWelcomeEmailCheckbox() { return browser.element('#sendWelcomeEmail'); } + get usersAddUserRandomPassword() { return browser.element('#randomPassword'); } + get emojiNewAliases() { return browser.element('#aliases'); } + get emojiNewImageInput() { return browser.element('#image'); } + + getUserEl(username) { return browser.element(`.flex-tab button[title="${username}"] > p`); } + confirmPopup() { + this.confirmBtn.waitForVisible(5000); this.confirmBtn.click(); this.sweetAlertOverlay.waitForVisible(5000, true); } dismissToast() { this.toastAlert.click(); - browser.pause(4000); } archiveChannel() { @@ -93,38 +115,43 @@ class FlexTab extends Page { } removePeopleFromChannel(user) { - const userEl = browser.element('.flex-tab button[title="'+user+'"]'); + const userEl = this.getUserEl(user); userEl.waitForVisible(); userEl.click(); - browser.pause(300); this.removeUserBtn.click(); } setUserOwner(user) { - const userEl = browser.element('.flex-tab button[title="'+user+'"]'); + const userEl = this.getUserEl(user); userEl.waitForVisible(); userEl.click(); - browser.pause(300); this.setOwnerBtn.waitForVisible(5000); this.setOwnerBtn.click(); this.viewAllBtn.click(); + browser.pause(100); } setUserModerator(user) { - const userEl = browser.element('.flex-tab button[title="'+user+'"]'); + const userEl = this.getUserEl(user); userEl.waitForVisible(); userEl.click(); - browser.pause(300); + this.setModeratorBtn.waitForVisible(); this.setModeratorBtn.click(); this.viewAllBtn.click(); + browser.pause(100); } muteUser(user) { - const userEl = browser.element('.flex-tab button[title="'+user+'"]'); - userEl.waitForVisible(); - userEl.click(); - browser.pause(300); - this.muteUserBtn.click(); + const userEl = this.getUserEl(user); + if (this.showAll.isVisible()) { + this.muteUserBtn.waitForVisible(5000); + this.muteUserBtn.click(); + } else { + userEl.waitForVisible(5000); + userEl.click(); + this.muteUserBtn.waitForVisible(5000); + this.muteUserBtn.click(); + } } } diff --git a/tests/pageobjects/login.page.js b/tests/pageobjects/login.page.js index 79280a9ca9eeb39ea337e16f8773ceb0acb92f54..c5790391f55b749b0a05d491bbd68319c879c39f 100644 --- a/tests/pageobjects/login.page.js +++ b/tests/pageobjects/login.page.js @@ -39,6 +39,15 @@ class LoginPage extends Page { this.submit(); } + registerNewAdmin({adminUsername, adminEmail, adminPassword}) { + this.nameField.setValue(adminUsername); + this.emailField.setValue(adminEmail); + this.passwordField.setValue(adminPassword); + this.confirmPasswordField.setValue(adminPassword); + + this.submit(); + } + login({email, password}) { this.emailOrUsernameField.setValue(email); this.passwordField.setValue(password); diff --git a/tests/pageobjects/main-content.page.js b/tests/pageobjects/main-content.page.js index 3eb80336d21a59fd456ef66bfcd0c84090e251b7..5433da362a50ec93c9beb2bffd58732571fb7cfa 100644 --- a/tests/pageobjects/main-content.page.js +++ b/tests/pageobjects/main-content.page.js @@ -14,11 +14,17 @@ class MainContent extends Page { get emojiBtn() { return browser.element('.inner-left-toolbar .emoji-picker-icon'); } get channelTitle() { return browser.element('.room-title'); } get popupFileConfirmBtn() { return browser.element('.sa-confirm-button-container .confirm'); } + get popupFileName() { return browser.element('#file-name'); } + get popupFileDescription() { return browser.element('#file-description'); } + get popupFileConfirmBtn() { return browser.element('.sa-confirm-button-container .confirm'); } get popupFilePreview() { return browser.element('.upload-preview-file'); } get popupFileTitle() { return browser.element('.upload-preview-title'); } get popupFileCancelBtn() { return browser.element('.sa-button-container .cancel'); } get lastMessageUser() { return browser.element('.message:last-child .user-card-message:nth-of-type(2)'); } get lastMessage() { return browser.element('.message:last-child .body'); } + get lastMessageImg() { return browser.element('.message:last-child .body .inline-image'); } + get lastMessageDesc() { return browser.element('.message:last-child .body .attachment-description p'); } + get lastMessageRoleAdded() { return browser.element('.message:last-child.subscription-role-added .body'); } get beforeLastMessage() { return browser.element('.message:nth-last-child(2) .body'); } get lastMessageUserTag() { return browser.element('.message:last-child .role-tag'); } get lastMessageImg() { return browser.element('.message:last-child .attachment-image img'); } @@ -57,9 +63,7 @@ class MainContent extends Page { get messagePopUpTitle() { return browser.element('.message-popup-title'); } get messagePopUpItems() { return browser.element('.message-popup-items'); } get messagePopUpFirstItem() { return browser.element('.popup-item.selected'); } - get settingLanguageSelect() { return browser.element('#language '); } - get settingLanguageEnglish() { return browser.element('[value="en"]'); } - get settingSaveBtn() { return browser.element('.button.save'); } + get mentionAllPopUp() { return browser.element('.popup-item[data-id="all"]'); } sendMessage(text) { this.setTextToInput(text); @@ -83,20 +87,18 @@ class MainContent extends Page { fileUpload(filePath) { this.sendMessage('Prepare for the file'); this.fileAttachment.chooseFile(filePath); - browser.pause(1000); } openMessageActionMenu() { this.lastMessage.moveToObject(); + this.messageOptionsBtn.waitForVisible(5000); this.messageOptionsBtn.click(); this.messageActionMenu.waitForVisible(5000); } setLanguageToEnglish() { this.settingLanguageSelect.click(); - browser.pause(500); this.settingLanguageEnglish.click(); - browser.pause(300); this.settingSaveBtn.click(); } @@ -106,13 +108,11 @@ class MainContent extends Page { case 'edit': this.messageEdit.waitForVisible(5000); this.messageEdit.click(); - browser.pause(1000); this.messageInput.addValue('this message was edited'); break; case 'reply': this.messageReply.waitForVisible(5000); this.messageReply.click(); - browser.pause(1000); this.messageInput.addValue(' this is a reply message'); break; case 'delete': @@ -130,7 +130,6 @@ class MainContent extends Page { case 'quote': this.messageQuote.waitForVisible(5000); this.messageQuote.click(); - browser.pause(1000); this.messageInput.addValue(' this is a quote message'); break; case 'star': @@ -157,4 +156,4 @@ class MainContent extends Page { } } -module.exports = new MainContent(); \ No newline at end of file +module.exports = new MainContent(); diff --git a/tests/pageobjects/side-nav.page.js b/tests/pageobjects/side-nav.page.js index c1fadddc73b907f51cf3459799a874ee1d700f8c..bc0ccb47a7ac8f70480be755002690cf59d1eb74 100644 --- a/tests/pageobjects/side-nav.page.js +++ b/tests/pageobjects/side-nav.page.js @@ -4,8 +4,8 @@ class SideNav extends Page { get directMessageTarget() { return browser.element('.flex-nav input#who'); } get saveDirectMessageBtn() { return browser.element('.save-direct-message'); } - get channelType() { return browser.element('#channel-type'); } - get channelReadOnly() { return browser.element('#channel-ro'); } + get channelType() { return browser.element('label[for="channel-type"]'); } + get channelReadOnly() { return browser.element('label[for="channel-ro"]'); } get channelName() { return browser.element('#channel-name'); } get saveChannelBtn() { return browser.element('.save-channel'); } @@ -31,9 +31,9 @@ class SideNav extends Page { get statusBusy() { return browser.element('.busy'); } get statusOffline() { return browser.element('.offline'); } get account() { return browser.element('#account'); } + get admin() { return browser.element('#admin'); } get logout() { return browser.element('#logout'); } get sideNavBar() { return browser.element('.side-nav '); } - get sideNavBtn() { return browser.element('.fixed-title .burger'); } get preferences() { return browser.element('.account-link:nth-of-type(1)'); } get profile() { return browser.element('.account-link:nth-of-type(2)'); } @@ -42,7 +42,10 @@ class SideNav extends Page { openChannel(channelName) { browser.click('.rooms-list > .wrapper > ul [title="'+channelName+'"]'); - this.messageInput.waitForExist(); + this.messageInput.waitForExist(5000); + browser.waitUntil(function() { + return browser.getText('.room-title') === channelName; + }, 5000); } getChannelFromList(channelName) { @@ -50,6 +53,7 @@ class SideNav extends Page { } createChannel(channelName, isPrivate, isReadOnly) { + this.newChannelBtn.waitForVisible(10000); this.newChannelBtn.click(); this.channelType.waitForVisible(10000); this.channelName.setValue(channelName); @@ -59,8 +63,11 @@ class SideNav extends Page { if (isReadOnly) { this.channelReadOnly.click(); } + browser.pause(500); this.saveChannelBtn.click(); + browser.pause(500); browser.waitForExist('[title="'+channelName+'"]', 1000); + this.channelType.waitForVisible(500, true); } addPeopleToChannel(user) { @@ -79,13 +86,13 @@ class SideNav extends Page { } startDirectMessage(user) { + this.newDirectMessageBtn.waitForVisible(3000); this.newDirectMessageBtn.click(); - browser.pause(1000); this.directMessageTarget.waitForVisible(3000); this.directMessageTarget.setValue(user); browser.waitForVisible('.-autocomplete-item', 3000); - browser.pause(500); browser.click('.-autocomplete-item'); + browser.pause(200); this.saveDirectMessageBtn.click(); browser.waitForExist('[title="'+user+'"]'); } diff --git a/tests/steps/0-login.js b/tests/steps/00-login.js similarity index 69% rename from tests/steps/0-login.js rename to tests/steps/00-login.js index 3c54c70c9df00bd91b40d956e735155e4d21b1c6..d61805039ee169921bf1486b9139c7b578e2ef1c 100644 --- a/tests/steps/0-login.js +++ b/tests/steps/00-login.js @@ -3,7 +3,7 @@ import loginPage from '../pageobjects/login.page'; describe('login', () => { - it('load page', () => { + before(()=>{ loginPage.open(); }); @@ -45,19 +45,23 @@ describe('login', () => { }); }); - describe('email / username', () => { - it('it should be required', () => { + describe('required fields', () => { + before(() => { loginPage.submit(); - loginPage.emailOrUsernameField.getAttribute('class').should.contain('error'); - loginPage.emailOrUsernameInvalidText.getText().should.not.be.empty; }); - }); - describe('password', () => { - it('it should be required', () => { - loginPage.submit(); - loginPage.passwordField.getAttribute('class').should.contain('error'); - loginPage.passwordInvalidText.getText().should.not.be.empty; + describe('email / username', () => { + it('should be required', () => { + loginPage.emailOrUsernameField.getAttribute('class').should.contain('error'); + loginPage.emailOrUsernameInvalidText.getText().should.not.be.empty; + }); + }); + + describe('password', () => { + it('should be required', () => { + loginPage.passwordField.getAttribute('class').should.contain('error'); + loginPage.passwordInvalidText.getText().should.not.be.empty; + }); }); }); }); diff --git a/tests/steps/1-register.js b/tests/steps/01-register.js similarity index 99% rename from tests/steps/1-register.js rename to tests/steps/01-register.js index 8c187ef656438cd5aa1cfce81f2353351645986a..599d600881d5280ea17db97ce347aed43beac041 100644 --- a/tests/steps/1-register.js +++ b/tests/steps/01-register.js @@ -3,7 +3,7 @@ import loginPage from '../pageobjects/login.page'; describe('register', () => { - it('load page', () => { + before(() => { loginPage.open(); loginPage.gotToRegister(); }); diff --git a/tests/steps/2-forgot-password.js b/tests/steps/02-forgot-password.js similarity index 97% rename from tests/steps/2-forgot-password.js rename to tests/steps/02-forgot-password.js index 3a6454843faa29ecfe0d7677a8d332db6e15c56e..f9af855e443e1fe83e4d360218c78ca9ac450d21 100644 --- a/tests/steps/2-forgot-password.js +++ b/tests/steps/02-forgot-password.js @@ -2,8 +2,8 @@ import loginPage from '../pageobjects/login.page'; -describe('register', () => { - it('load page', () => { +describe.skip('register', () => { + before(() => { loginPage.open(); loginPage.gotToForgotPassword(); }); diff --git a/tests/steps/03-user-creation.js b/tests/steps/03-user-creation.js new file mode 100644 index 0000000000000000000000000000000000000000..1d146c8cdff7c036b93ab3887c357d6c50f839ab --- /dev/null +++ b/tests/steps/03-user-creation.js @@ -0,0 +1,59 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import loginPage from '../pageobjects/login.page'; +import mainContent from '../pageobjects/main-content.page'; +import sideNav from '../pageobjects/side-nav.page'; + +//test data imports +import {username, email, password, adminUsername, adminEmail, adminPassword} from '../data/user.js'; + + + +//Basic usage test start +describe('User Creation', function() { + this.retries(2); + + before(() => { + loginPage.open(); + }); + + /*If you are using a clean database dont pass any environment variables + if you have an existing database please pass the username (ADMIN_USERNAME) and password (ADMIN_PASS) of the admin as environment variables.*/ + + if (process.env.ADMIN_USERNAME && process.env.ADMIN_PASS) { + console.log('Admin login and password provided, skipping admin creation.'); + } else { + it.skip('create the admin user', () => { + loginPage.gotToRegister(); + + loginPage.registerNewAdmin({adminUsername, adminEmail, adminPassword}); + + browser.waitForExist('form#login-card input#username', 5000); + + browser.click('.submit > button'); + + mainContent.mainContent.waitForExist(5000); + }); + + it.skip('logout', () => { + sideNav.accountBoxUserName.waitForVisible(5000); + sideNav.accountBoxUserName.click(); + + sideNav.logout.waitForVisible(5000); + sideNav.logout.click(); + }); + } + + it('create user', () => { + loginPage.gotToRegister(); + + loginPage.registerNewUser({username, email, password}); + + browser.waitForExist('form#login-card input#username', 5000); + + browser.click('.submit > button'); + + mainContent.mainContent.waitForExist(5000); + }); +}); diff --git a/tests/steps/04-main-elements-render.js b/tests/steps/04-main-elements-render.js new file mode 100644 index 0000000000000000000000000000000000000000..31ad15b49b08ab58e0d5b93fde96b80d8b8a460b --- /dev/null +++ b/tests/steps/04-main-elements-render.js @@ -0,0 +1,356 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import flexTab from '../pageobjects/flex-tab.page'; +import mainContent from '../pageobjects/main-content.page'; +import sideNav from '../pageobjects/side-nav.page'; + +//test data imports +import {checkIfUserIsValid} from '../data/checks'; +import {username, email, password} from '../data/user.js'; + +describe('Main Elements Render', function() { + before(()=>{ + checkIfUserIsValid(username, email, password); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + }); + + describe('side nav bar', () => { + describe('render', () => { + it('should show the logged username', () => { + sideNav.accountBoxUserName.isVisible().should.be.true; + }); + + it('should show the logged user avatar', () => { + sideNav.accountBoxUserAvatar.isVisible().should.be.true; + }); + + it('should show the new channel button', () => { + sideNav.newChannelBtn.isVisible().should.be.true; + }); + + it('should show the plus icon', () => { + sideNav.newChannelIcon.isVisible().should.be.true; + }); + + it('should show the "More Channels" button', () => { + sideNav.moreChannels.isVisible().should.be.true; + }); + + it('should show the new direct message button', () => { + sideNav.newDirectMessageBtn.isVisible().should.be.true; + }); + + it('should show the plus icon', () => { + sideNav.newDirectMessageIcon.isVisible().should.be.true; + }); + + it('should show the "More Direct Messages" button', () => { + sideNav.moreDirectMessages.isVisible().should.be.true; + }); + + it('should show "general" channel', () => { + sideNav.general.isVisible().should.be.true; + }); + + it.skip('should not show eye icon on general', () => { + sideNav.channelHoverIcon.isVisible().should.be.true; + }); + }); + }); + + describe('user options', () => { + describe('render', () => { + before(() => { + sideNav.accountBoxUserName.click(); + sideNav.userOptions.waitForVisible(5000); + }); + + after(() => { + sideNav.accountBoxUserName.click(); + }); + + it('should show user options', () => { + sideNav.userOptions.isVisible().should.be.true; + }); + + it('should show online button', () => { + sideNav.statusOnline.isVisible().should.be.true; + }); + + it('should show away button', () => { + sideNav.statusAway.isVisible().should.be.true; + }); + + it('should show busy button', () => { + sideNav.statusBusy.isVisible().should.be.true; + }); + + it('should show offline button', () => { + sideNav.statusOffline.isVisible().should.be.true; + }); + + it('should show settings button', () => { + sideNav.account.isVisible().should.be.true; + }); + + it('should show logout button', () => { + sideNav.logout.isVisible().should.be.true; + }); + }); + }); + + describe('main content', () => { + describe('render', () => { + before(()=> { + sideNav.logout.waitForVisible(5000, true); + sideNav.getChannelFromList('general').waitForVisible(5000); + sideNav.openChannel('general'); + }); + + it('should show the title of the channel', () => { + mainContent.channelTitle.isVisible().should.be.true; + }); + + it('should show the empty favorite star', () => { + mainContent.emptyFavoriteStar.isVisible().should.be.true; + }); + + it('clicks the star', () => { + mainContent.emptyFavoriteStar.click(); + }); + + it('should not show the empty favorite star', () => { + mainContent.favoriteStar.isVisible().should.be.true; + }); + + it('clicks the star', () => { + mainContent.favoriteStar.click(); + }); + + it('should show the message input bar', () => { + mainContent.messageInput.isVisible().should.be.true; + }); + + it('should show the file attachment button', () => { + mainContent.fileAttachmentBtn.isVisible().should.be.true; + }); + + it('should show the audio recording button', () => { + mainContent.recordBtn.isVisible().should.be.true; + }); + + it('should show the video call button', () => { + mainContent.videoCamBtn.isVisible().should.be.true; + }); + + it('should not show the send button', () => { + mainContent.sendBtn.isVisible().should.be.false; + }); + + it('should show the emoji button', () => { + mainContent.emojiBtn.isVisible().should.be.true; + }); + + it('adds some text to the input', () => { + mainContent.addTextToInput('Some Text'); + }); + + it('should show the send button', () => { + mainContent.sendBtn.isVisible().should.be.true; + }); + + it('should not show the file attachment button', () => { + mainContent.fileAttachmentBtn.isVisible().should.be.false; + }); + + it('should not show the audio recording button', () => { + mainContent.recordBtn.isVisible().should.be.false; + }); + + it('should not show the video call button', () => { + mainContent.videoCamBtn.isVisible().should.be.false; + }); + + it('should show the last message', () => { + mainContent.lastMessage.isVisible().should.be.true; + }); + + it('the last message should be from the loged user', () => { + mainContent.lastMessageUser.getText().should.equal(username); + }); + + it('should not show the Admin tag', () => { + mainContent.lastMessageUserTag.isVisible().should.be.false; + }); + }); + }); + + describe('flextab usage', () => { + describe('render', () => { + before(()=> { + sideNav.getChannelFromList('general').waitForVisible(5000); + sideNav.openChannel('general'); + }); + describe('Room Info Tab', () => { + before(()=> { + flexTab.channelTab.click(); + }); + + after(()=> { + flexTab.channelTab.click(); + }); + + it('should show the room info button', () => { + flexTab.channelTab.isVisible().should.be.true; + }); + + it('should show the room info tab content', () => { + flexTab.channelSettings.waitForVisible(5000); + flexTab.channelSettings.isVisible().should.be.true; + }); + + it('should show the room name', ()=> { + flexTab.firstSetting.waitForVisible(); + flexTab.firstSetting.getText().should.equal('general'); + }); + + }); + + describe('Search Tab', () => { + before(()=> { + flexTab.searchTab.click(); + }); + + after(()=> { + flexTab.searchTab.click(); + }); + + it('should show the message search button', () => { + flexTab.searchTab.isVisible().should.be.true; + }); + + it('should show the message tab content', () => { + flexTab.searchTabContent.isVisible().should.be.true; + }); + }); + + describe('Members Tab', () => { + before(()=> { + flexTab.membersTab.click(); + }); + + after(()=> { + flexTab.membersTab.click(); + }); + + it('should show the members tab button', () => { + flexTab.membersTab.isVisible().should.be.true; + }); + + it('should show the members content', () => { + flexTab.membersTabContent.isVisible().should.be.true; + }); + + it.skip('should show the members search bar', () => { + flexTab.userSearchBar.isVisible().should.be.true; + }); + + it.skip('should show the show all link', () => { + flexTab.showAll.isVisible().should.be.true; + }); + }); + + describe('Notifications Tab', () => { + before(()=> { + flexTab.notificationsTab.click(); + }); + + after(()=> { + flexTab.notificationsTab.click(); + }); + + it('should show the notifications button', () => { + flexTab.notificationsTab.isVisible().should.be.true; + }); + + it('should show the notifications Tab content', () => { + flexTab.notificationsSettings.isVisible().should.be.true; + }); + }); + + describe('Files Tab', () => { + before(()=> { + flexTab.filesTab.click(); + }); + + after(()=> { + flexTab.filesTab.click(); + }); + + it('should show the files button', () => { + flexTab.filesTab.isVisible().should.be.true; + }); + + it('should show the files Tab content', () => { + flexTab.filesTabContent.isVisible().should.be.true; + }); + }); + + describe('Mentions Tab', () => { + before(()=> { + flexTab.mentionsTab.click(); + }); + + after(()=> { + flexTab.mentionsTab.click(); + }); + + it('should show the mentions button', () => { + flexTab.mentionsTab.isVisible().should.be.true; + }); + + it('should show the mentions Tab content', () => { + flexTab.mentionsTabContent.isVisible().should.be.true; + }); + }); + + describe('Starred Messages Tab', () => { + before(()=> { + flexTab.starredTab.click(); + }); + + after(()=> { + flexTab.starredTab.click(); + }); + + it('should show the starred messages button', () => { + flexTab.starredTab.isVisible().should.be.true; + }); + + it('should show the starred messages Tab content', () => { + flexTab.starredTabContent.isVisible().should.be.true; + }); + }); + + describe('Pinned Messages Tab', () => { + before(()=> { + flexTab.pinnedTab.click(); + }); + + after(()=> { + flexTab.pinnedTab.click(); + }); + + it('should show the pinned button', () => { + flexTab.pinnedTab.isVisible().should.be.true; + }); + + it('should show the pinned messages Tab content', () => { + flexTab.pinnedTabContent.isVisible().should.be.true; + }); + }); + }); + }); +}); diff --git a/tests/steps/05-channel-creation.js b/tests/steps/05-channel-creation.js new file mode 100644 index 0000000000000000000000000000000000000000..7d34fdc934adf0a97e7cb5573cadee7c2d898dc8 --- /dev/null +++ b/tests/steps/05-channel-creation.js @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import sideNav from '../pageobjects/side-nav.page'; +import {publicChannelName, privateChannelName} from '../data/channel.js'; +import {targetUser} from '../data/interactions.js'; + +//test data imports +import {checkIfUserIsValid, setPublicChannelCreated, setPrivateChannelCreated, setDirectMessageCreated} from '../data/checks'; +import {username, email, password} from '../data/user.js'; +//Basic usage test start +describe('Channel creation', function() { + before(()=>{ + checkIfUserIsValid(username, email, password); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + }); + + beforeEach(()=>{ + sideNav.getChannelFromList('general').waitForVisible(5000); + sideNav.openChannel('general'); + }); + + afterEach(function() { + if (this.currentTest.state !== 'passed') { + setPublicChannelCreated(false); + switch (this.currentTest.title) { + case 'create a public channel': + setPublicChannelCreated(false); + console.log('Public channel Not Created!'); + break; + case 'create a private channel': + setPrivateChannelCreated(false); + console.log('Private channel Not Created!'); + break; + case 'start a direct message with rocket.cat': + setDirectMessageCreated(false); + console.log('Direct Message Not Created!'); + break; + } + } + }); + + describe('create a public channel', function() { + it('create a public channel', function() { + sideNav.createChannel(publicChannelName, false, false); + setPublicChannelCreated(true); + }); + }); + + describe('create a private channel', function() { + it('create a private channel', function() { + sideNav.createChannel(privateChannelName, true, false); + setPrivateChannelCreated(true); + }); + }); + + describe('direct channel', function() { + it('start a direct message with rocket.cat', function() { + sideNav.startDirectMessage(targetUser); + setDirectMessageCreated(true); + }); + }); +}); diff --git a/tests/steps/06-messaging.js b/tests/steps/06-messaging.js new file mode 100644 index 0000000000000000000000000000000000000000..3b29f6a7aaba759ca0be51e9ebded200300c5cea --- /dev/null +++ b/tests/steps/06-messaging.js @@ -0,0 +1,322 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import mainContent from '../pageobjects/main-content.page'; +import sideNav from '../pageobjects/side-nav.page'; + +//test data imports +import {username, email, password} from '../data/user.js'; +import {publicChannelName, privateChannelName} from '../data/channel.js'; +import {targetUser, imgURL} from '../data/interactions.js'; +import {checkIfUserIsValid, publicChannelCreated, privateChannelCreated, directMessageCreated, setPublicChannelCreated, setPrivateChannelCreated, setDirectMessageCreated} from '../data/checks'; + + +//Test data +const message = 'message from '+username; +var currentTest = 'none'; + +function messagingTest() { + describe('Normal message', ()=> { + it('send a message', () => { + mainContent.sendMessage(message); + }); + + it('should show the last message', () => { + mainContent.lastMessage.isVisible().should.be.true; + }); + + if (!currentTest === 'direct') { + it('the last message should be from the loged user', () => { + mainContent.lastMessageUser.getText().should.equal(username); + }); + } + + if (currentTest === 'general') { + it('should not show the Admin tag', () => { + mainContent.lastMessageUserTag.isVisible().should.be.false; + }); + } + }); + + describe('fileUpload', ()=> { + after(() => { + }); + it('send a attachment', () => { + mainContent.fileUpload(imgURL); + }); + + it('should show the confirm button', () => { + mainContent.popupFileConfirmBtn.isVisible().should.be.true; + }); + + it('should show the cancel button', () => { + mainContent.popupFileCancelBtn.isVisible().should.be.true; + }); + + it('should show the file preview', () => { + mainContent.popupFilePreview.isVisible().should.be.true; + }); + + it('should show the confirm button', () => { + mainContent.popupFileConfirmBtn.isVisible().should.be.true; + }); + + it('should show the file title', () => { + mainContent.popupFileTitle.isVisible().should.be.true; + }); + + it('should show the file name input', () => { + mainContent.popupFileName.isVisible().should.be.true; + }); + + it('should fill the file name input', () => { + mainContent.popupFileName.setValue('File Name'); + }); + + it('should show the file name input', () => { + mainContent.popupFileDescription.isVisible().should.be.true; + }); + + it('should fill the file name input', () => { + mainContent.popupFileDescription.setValue('File Description'); + }); + + it('click the confirm', () => { + mainContent.popupFileConfirmBtn.click(); + mainContent.popupFileConfirmBtn.waitForVisible(5000, true); + }); + + it('should show the file in the message', () => { + mainContent.lastMessageDesc.waitForVisible(10000); + mainContent.lastMessageDesc.getText().should.equal('File Description'); + }); + }); +} + +function messageActionsTest() { + describe('Message actions', ()=> { + before(() => { + mainContent.sendMessage('Message for Message Actions Tests'); + }); + describe('Message Actions Render', ()=> { + before(() => { + mainContent.openMessageActionMenu(); + }); + + after(() => { + browser.pause(100); + mainContent.selectAction('close'); + mainContent.messageActionMenu.waitForVisible(5000, true); + }); + + it('should show the message action menu', () => { + mainContent.messageActionMenu.isVisible().should.be.true; + }); + + it('should show the reply action', () => { + mainContent.messageReply.isVisible().should.be.true; + }); + + it('should show the edit action', () => { + mainContent.messageEdit.isVisible().should.be.true; + }); + + it('should show the delete action', () => { + mainContent.messageDelete.isVisible().should.be.true; + }); + + it('should show the permalink action', () => { + mainContent.messagePermalink.isVisible().should.be.true; + }); + + it('should show the copy action', () => { + mainContent.messageCopy.isVisible().should.be.true; + }); + + it('should show the quote the action', () => { + mainContent.messageQuote.isVisible().should.be.true; + }); + + it('should show the star action', () => { + mainContent.messageStar.isVisible().should.be.true; + }); + + it('should show the reaction action', () => { + mainContent.messageReaction.isVisible().should.be.true; + }); + + it('should show the close action', () => { + mainContent.messageClose.isVisible().should.be.true; + }); + + if (currentTest === 'general') { + it('should not show the pin action', () => { + mainContent.messagePin.isVisible().should.be.false; + }); + } + + it('should not show the mark as unread action', () => { + mainContent.messageUnread.isVisible().should.be.false; + }); + }); + + describe('Message Actions usage', () => { + describe('Message Reply', () => { + before(() => { + mainContent.openMessageActionMenu(); + }); + it('reply the message', () => { + mainContent.selectAction('reply'); + mainContent.sendBtn.click(); + }); + + it.skip('checks if the message was replied', () => { + mainContent.lastMessageTextAttachment.waitForExist(5000); + mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); + }); + }); + + + describe('Message edit', () => { + before(() => { + mainContent.sendMessage('Message for Message edit Tests'); + mainContent.openMessageActionMenu(); + }); + + it('edit the message', () => { + mainContent.selectAction('edit'); + mainContent.sendBtn.click(); + }); + }); + + + describe('Message delete', () => { + before(() => { + mainContent.sendMessage('Message for Message Delete Tests'); + mainContent.openMessageActionMenu(); + }); + + it('delete the message', () => { + mainContent.selectAction('delete'); + mainContent.popupFileConfirmBtn.click(); + browser.waitForVisible('.sweet-overlay', 3000, true); + }); + + it('should not show the deleted message', () => { + mainContent.lastMessage.should.not.equal('Message for Message Delete Tests'); + }); + }); + + describe('Message quote', () => { + const message = 'Message for quote Tests - ' + Date.now(); + + before(() => { + mainContent.sendMessage(message); + mainContent.openMessageActionMenu(); + }); + + it('quote the message', () => { + mainContent.selectAction('quote'); + mainContent.sendBtn.click(); + + browser.waitUntil(function() { + return browser.getText(mainContent.lastMessageTextAttachment.selector) === message; + }, 2000); + }); + }); + + describe('Message star', () => { + before(() => { + mainContent.sendMessage('Message for star Tests'); + mainContent.openMessageActionMenu(); + }); + + it('star the message', () => { + mainContent.selectAction('star'); + }); + }); + + describe('Message copy', () => { + before(() => { + mainContent.sendMessage('Message for copy Tests'); + mainContent.openMessageActionMenu(); + }); + + it('copy the message', () => { + mainContent.selectAction('copy'); + }); + }); + + describe('Message Permalink', () => { + before(() => { + mainContent.sendMessage('Message for permalink Tests'); + mainContent.openMessageActionMenu(); + }); + + + it('permalink the message', () => { + mainContent.selectAction('permalink'); + }); + }); + }); + }); +} + +describe('Messaging in different channels', () => { + before(()=>{ + checkIfUserIsValid(username, email, password); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + }); + + + describe('Messaging in GENERAL channel', () => { + before(()=>{ + sideNav.openChannel('general'); + currentTest = 'general'; + }); + messagingTest(); + messageActionsTest(); + }); + + describe('Messaging in created public channel', () => { + before(()=>{ + if (!publicChannelCreated) { + sideNav.createChannel(publicChannelName, false, false); + setPublicChannelCreated(true); + console.log(' public channel not found, creating one...'); + } + currentTest = 'public'; + sideNav.openChannel(publicChannelName); + }); + messagingTest(); + messageActionsTest(); + }); + + describe('Messaging in created private channel', () => { + before(()=>{ + if (!privateChannelCreated) { + sideNav.createChannel(privateChannelName, true, false); + setPrivateChannelCreated(true); + console.log(' private channel not found, creating one...'); + } + currentTest = 'private'; + sideNav.openChannel(privateChannelName); + }); + messagingTest(); + messageActionsTest(); + }); + + describe('Messaging in created direct message', () => { + before(()=>{ + if (!directMessageCreated) { + sideNav.startDirectMessage(targetUser); + setDirectMessageCreated(true); + console.log(' Direct message not found, creating one...'); + } + currentTest = 'direct'; + sideNav.openChannel(publicChannelName); + }); + messagingTest(); + }); +}); diff --git a/tests/steps/5-emoji.js b/tests/steps/07-emoji.js similarity index 72% rename from tests/steps/5-emoji.js rename to tests/steps/07-emoji.js index 344d2d4e8c41d463c881dd0be9b260dcf0002cbc..604d70b155754ef97c9878d96f70c8ae4a16896b 100644 --- a/tests/steps/5-emoji.js +++ b/tests/steps/07-emoji.js @@ -4,16 +4,27 @@ import mainContent from '../pageobjects/main-content.page'; import sideNav from '../pageobjects/side-nav.page'; +import {username, email, password} from '../data/user.js'; +import {checkIfUserIsValid} from '../data/checks'; + describe('emoji', ()=> { - it('opens general', ()=> { + before(()=>{ + checkIfUserIsValid(username, email, password); + sideNav.getChannelFromList('general').waitForExist(5000); sideNav.openChannel('general'); }); - it('opens emoji menu', ()=> { - mainContent.emojiBtn.click(); - }); describe('render', ()=> { + before(()=> { + mainContent.emojiBtn.click(); + }); + + after(() => { + mainContent.emojiSmile.click(); + mainContent.setTextToInput(''); + }); + it('should show the emoji picker menu', ()=> { mainContent.emojiPickerMainScreen.isVisible().should.be.true; }); @@ -65,24 +76,36 @@ describe('emoji', ()=> { it('should show the emoji picker search bar', ()=> { mainContent.emojiPickerFilter.isVisible().should.be.true; }); + }); - it('send a smile emoji', ()=> { - mainContent.emojiSmile.click(); - }); + describe('usage', ()=> { + describe('send emoji via screen', ()=> { + before(()=> { + mainContent.emojiBtn.click(); + mainContent.emojiPickerPeopleIcon.click(); + }); - it('the value on the message input should be the same as the emoji clicked', ()=> { - mainContent.messageInput.getValue().should.equal(':smile:'); - }); + it('select a grinning emoji', ()=> { + mainContent.emojiGrinning.waitForVisible(5000); + mainContent.emojiGrinning.click(); + }); - it('send the emoji', ()=> { - mainContent.addTextToInput(' '); - mainContent.sendBtn.click(); - }); + it('the value on the message input should be the same as the emoji clicked', ()=> { + mainContent.messageInput.getValue().should.equal(':grinning:'); + }); - it('the value on the message should be the same as the emoji clicked', ()=> { - mainContent.lastMessage.getText().should.equal('😄'); + it('send the emoji', ()=> { + mainContent.addTextToInput(' '); + mainContent.sendBtn.click(); + }); + + it('the value on the message should be the same as the emoji clicked', ()=> { + mainContent.lastMessage.getText().should.equal('😀'); + }); }); + }); + describe('send emoji via text', ()=> { it('adds emoji text to the message input', ()=> { mainContent.addTextToInput(':smile'); }); diff --git a/tests/steps/08-resolutions.js b/tests/steps/08-resolutions.js new file mode 100644 index 0000000000000000000000000000000000000000..a810e8c596578286570342e52ad4ba75369acf45 --- /dev/null +++ b/tests/steps/08-resolutions.js @@ -0,0 +1,105 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import mainContent from '../pageobjects/main-content.page'; +import sideNav from '../pageobjects/side-nav.page'; + +import {username, email, password} from '../data/user.js'; +import {checkIfUserIsValid} from '../data/checks'; + +describe.skip('resolutions tests', ()=> { + describe('mobile render', ()=> { + before(()=> { + checkIfUserIsValid(username, email, password); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + browser.windowHandleSize({ + width: 650, + height: 800 + }); + }); + + after(()=> { + browser.windowHandleSize({ + width: 1450, + height: 900 + }); + }); + + describe('moving elements ', () => { + it('should close de sidenav', () => { + mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); + }); + + it('press the navbar button', () => { + sideNav.sideNavBtn.click(); + }); + + it('should open de sidenav', () => { + mainContent.mainContent.getLocation().should.not.deep.equal({x:0, y:0}); + }); + + it('open general channel', () => { + sideNav.openChannel('general'); + }); + + it('should close de sidenav', () => { + mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); + }); + + it('press the navbar button', () => { + sideNav.sideNavBtn.click(); + }); + + it('opens the user preferences screen', () => { + sideNav.accountBoxUserName.waitForVisible(); + sideNav.accountBoxUserName.click(); + sideNav.account.waitForVisible(); + sideNav.account.click(); + }); + + it('press the preferences link', () => { + sideNav.preferences.waitForVisible(); + sideNav.preferences.click(); + }); + + it('should close de sidenav', () => { + mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); + }); + + it('press the navbar button', () => { + sideNav.sideNavBtn.click(); + }); + + it('press the profile link', () => { + sideNav.profile.waitForVisible(); + sideNav.profile.click(); + }); + + it('should close de sidenav', () => { + mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); + }); + + it('press the navbar button', () => { + sideNav.sideNavBtn.click(); + }); + + it('press the avatar link', () => { + sideNav.avatar.waitForVisible(); + sideNav.avatar.click(); + }); + + it('should close de sidenav', () => { + mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); + }); + + it('press the navbar button', () => { + sideNav.sideNavBtn.click(); + }); + + it('close the preferences menu', () => { + sideNav.preferencesClose.click(); + }); + }); + }); +}); diff --git a/tests/steps/09-channel.js b/tests/steps/09-channel.js new file mode 100644 index 0000000000000000000000000000000000000000..32a45a47c09b2e8d4708d6a01050669e362d6efd --- /dev/null +++ b/tests/steps/09-channel.js @@ -0,0 +1,264 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import flexTab from '../pageobjects/flex-tab.page'; +import mainContent from '../pageobjects/main-content.page'; +import sideNav from '../pageobjects/side-nav.page'; + +import {username, email, password} from '../data/user.js'; +import {checkIfUserIsValid, publicChannelCreated, setPublicChannelCreated} from '../data/checks'; +import {publicChannelName} from '../data/channel.js'; +import {targetUser} from '../data/interactions.js'; + +describe('channel usage', ()=> { + before(() => { + checkIfUserIsValid(username, email, password); + if (!publicChannelCreated) { + sideNav.createChannel(publicChannelName, false, false); + setPublicChannelCreated(true); + console.log('public channel not found, creating one...'); + } + sideNav.openChannel(publicChannelName); + }); + + describe('Adding a user to the room', () => { + before(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + after(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + it('add people to the room', () => { + flexTab.addPeopleToChannel(targetUser); + }); + + }); + + describe('Channel settings', ()=> { + describe('Channel name edit', ()=> { + before(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.channelTab.waitForVisible(); + flexTab.channelTab.click(); + }); + + after(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.channelTab.waitForVisible(5000); + flexTab.channelTab.click(); + browser.pause(400); + }); + + it('should show the old name', ()=> { + flexTab.firstSetting.waitForVisible(); + flexTab.firstSetting.getText().should.equal(publicChannelName); + }); + + it('click the edit name', ()=> { + flexTab.editNameBtn.waitForVisible(); + flexTab.editNameBtn.click(); + }); + + it('edit the name input', ()=> { + flexTab.editNameTextInput.waitForVisible(); + flexTab.editNameTextInput.setValue('NAME-EDITED-'+publicChannelName); + }); + + it('save the name', ()=> { + flexTab.editNameSave.click(); + + }); + + it('should show the new name', ()=> { + var channelName = sideNav.getChannelFromList('NAME-EDITED-'+publicChannelName); + channelName.getText().should.equal('NAME-EDITED-'+publicChannelName); + }); + }); + + describe('Channel topic edit', ()=> { + before(()=> { + flexTab.channelTab.waitForVisible(); + flexTab.channelTab.click(); + }); + + after(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.channelTab.waitForVisible(); + flexTab.channelTab.click(); + }); + + it('click the edit topic', ()=> { + flexTab.editTopicBtn.waitForVisible(5000); + flexTab.editTopicBtn.click(); + }); + + it('edit the topic input', ()=> { + flexTab.editTopicTextInput.waitForVisible(5000); + flexTab.editTopicTextInput.setValue('TOPIC EDITED'); + }); + + it('save the topic', ()=> { + flexTab.editNameSave.click(); + }); + + it('should show the new topic', ()=> { + flexTab.secondSetting.getText().should.equal('TOPIC EDITED'); + }); + }); + + describe('Channel description edit', ()=> { + before(()=> { + flexTab.channelTab.waitForVisible(); + flexTab.channelTab.click(); + }); + + after(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.channelTab.waitForVisible(); + flexTab.channelTab.click(); + }); + + it('click the edit description', ()=> { + flexTab.editDescriptionBtn.waitForVisible(); + flexTab.editDescriptionBtn.click(); + }); + + it('edit the description input', ()=> { + flexTab.editDescriptionTextInput.waitForVisible(5000); + flexTab.editDescriptionTextInput.setValue('DESCRIPTION EDITED'); + }); + + it('save the description', ()=> { + flexTab.editNameSave.click(); + }); + + it('should show the new description', ()=> { + flexTab.thirdSetting.getText().should.equal('DESCRIPTION EDITED'); + }); + }); + }); + + describe('Members tab usage', () => { + describe('Owner added', () => { + before(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + after(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + it('sets rocket cat as owner', ()=> { + flexTab.setUserOwner(targetUser); + }); + + it('dismiss the toast', ()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + }); + + it('the last message should be a subscription role added', ()=> { + mainContent.lastMessageRoleAdded.isVisible().should.be.true; + }); + + it('should show the target username in owner add message', ()=> { + mainContent.lastMessage.getText().should.have.string(targetUser); + }); + }); + + describe('Moderator added', () => { + before(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + after(()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + it('sets rocket cat as moderator', ()=> { + flexTab.setUserModerator(targetUser); + }); + + it('dismiss the toast', ()=> { + if (flexTab.toastAlert.isVisible()) { + flexTab.dismissToast(); + flexTab.toastAlert.waitForVisible(5000, true); + } + }); + + it('the last message should be a subscription role added', ()=> { + mainContent.lastMessageRoleAdded.isVisible().should.be.true; + }); + + it('should show the target username in moderator add message', ()=> { + mainContent.lastMessage.getText().should.have.string(targetUser); + }); + }); + + describe.skip('User muted', () => { + before(()=> { + flexTab.membersTab.waitForVisible(5000); + flexTab.membersTab.click(); + }); + + after(()=> { + flexTab.membersTab.waitForVisible(); + flexTab.membersTab.click(); + }); + + it('mute rocket cat', ()=> { + flexTab.muteUser(targetUser); + }); + + it('confirms the popup', ()=> { + flexTab.confirmPopup(); + }); + }); + }); +}); diff --git a/tests/steps/7-user-preferences.js b/tests/steps/10-user-preferences.js similarity index 90% rename from tests/steps/7-user-preferences.js rename to tests/steps/10-user-preferences.js index 5bc466194c811900ad510586d1886908707a52c1..f662c41df0c6be256a4c4d1c4da9df0f5ec49ec1 100644 --- a/tests/steps/7-user-preferences.js +++ b/tests/steps/10-user-preferences.js @@ -6,17 +6,22 @@ import mainContent from '../pageobjects/main-content.page'; import sideNav from '../pageobjects/side-nav.page'; import preferencesMainContent from '../pageobjects/preferences-main-content.page'; -import {username, password} from '../test-data/user.js'; -import {imgURL} from '../test-data/interactions.js'; +import {username, password, email} from '../data/user.js'; +import {imgURL} from '../data/interactions.js'; -describe('user preferences', ()=> { +import {checkIfUserIsValid} from '../data/checks'; + + +describe.skip('user preferences', ()=> { + before(() => { + checkIfUserIsValid(username, email, password); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); - it('opens the user preferences screen', () => { sideNav.accountBoxUserName.waitForVisible(); sideNav.accountBoxUserName.click(); sideNav.account.waitForVisible(); sideNav.account.click(); - browser.pause(1000); }); describe('render', ()=> { @@ -34,7 +39,6 @@ describe('user preferences', ()=> { it('click on the profile link', ()=> { sideNav.profile.click(); - browser.pause(1000); }); it('should show the username input', ()=> { @@ -62,7 +66,6 @@ describe('user preferences', ()=> { describe.skip('user info change', ()=> { it('click on the profile link', ()=> { sideNav.profile.click(); - browser.pause(1000); }); it('change the name field', ()=> { @@ -95,7 +98,6 @@ describe('user preferences', ()=> { it('close the preferences menu', () => { sideNav.preferencesClose.click(); - browser.pause(3000); }); it('open GENERAL', () => { @@ -130,4 +132,4 @@ describe('user preferences', ()=> { flexTab.membersTab.click(); }); }); -}); \ No newline at end of file +}); diff --git a/tests/steps/11-admin.js b/tests/steps/11-admin.js new file mode 100644 index 0000000000000000000000000000000000000000..3f3fd0b24f44277826edb3a8243a13a582fa8a1c --- /dev/null +++ b/tests/steps/11-admin.js @@ -0,0 +1,686 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import sideNav from '../pageobjects/side-nav.page'; +import flexTab from '../pageobjects/flex-tab.page'; +import admin from '../pageobjects/administration.page'; + +//test data imports +import {checkIfUserIsAdmin} from '../data/checks'; +import {adminUsername, adminEmail, adminPassword} from '../data/user.js'; + +describe('Admin Login', () => { + before(() => { + checkIfUserIsAdmin(adminUsername, adminEmail, adminPassword); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + }); + + after(() => { + sideNav.preferencesClose.waitForVisible(5000); + sideNav.preferencesClose.click(); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + }); + + describe('Admin view', () => { + before(() => { + sideNav.accountBoxUserName.click(); + sideNav.admin.waitForVisible(5000); + }); + + it('Enter the admin view', () => { + sideNav.admin.click(); + admin.flexNavContent.waitForVisible(5000); + }); + + describe('info', () => { + before(() =>{ + admin.infoLink.waitForVisible(5000); + admin.infoLink.click(); + admin.infoRocketChatTable.waitForVisible(5000); + }); + it('the first title should be Rocket.Chat', () => { + admin.infoRocketChatTableTitle.getText().should.equal('Rocket.Chat'); + }); + + it('should show the rocket chat table', () => { + admin.infoRocketChatTable.isVisible().should.be.true; + }); + + it('the second title should be Commit', () => { + admin.infoCommitTableTitle.getText().should.equal('Commit'); + }); + + it('should show the Commit table', () => { + admin.infoCommitTable.isVisible().should.be.true; + }); + + it('the first title should be Runtime_Environment', () => { + admin.infoRuntimeTableTitle.getText().should.equal('Runtime_Environment'); + }); + + it('should show the Runtime_Environment table', () => { + admin.infoRuntimeTable.isVisible().should.be.true; + }); + + it('the first title should be Build_Environment', () => { + admin.infoBuildTableTitle.getText().should.equal('Build_Environment'); + }); + + it('should show the Build_Environment table', () => { + admin.infoBuildTable.isVisible().should.be.true; + }); + }); + + describe('rooms', () => { + before(() => { + admin.roomsLink.waitForVisible(5000); + admin.roomsLink.click(); + admin.roomsFilter.waitForVisible(5000); + }); + + after(() => { + admin.infoLink.click(); + }); + + describe('render', () => { + it('should show the search form', () => { + admin.roomsSearchForm.isVisible().should.be.true; + }); + + it('should show the rooms Filter', () => { + admin.roomsFilter.isVisible().should.be.true; + }); + + it('should show the channel checkbox', () => { + admin.roomsChannelsCheckbox.isVisible().should.be.true; + }); + + it('should show the direct messsage checkbox', () => { + admin.roomsDirectCheckbox.isVisible().should.be.true; + }); + + it('should show the Private channel checkbox', () => { + admin.roomsPrivateCheckbox.isVisible().should.be.true; + }); + + it('should show the general channel', () => { + admin.roomsGeneralChannel.isVisible().should.be.true; + }); + }); + + describe('filter text', () => { + before(() => { + admin.roomsFilter.click(); + admin.roomsFilter.setValue('general'); + }); + + after(() => { + admin.roomsFilter.click(); + admin.roomsFilter.setValue(''); + }); + + it('should show the general channel', () => { + admin.roomsGeneralChannel.isVisible().should.be.true; + }); + }); + + describe('filter text with wrong channel', () => { + before(() => { + admin.roomsFilter.click(); + browser.pause(5000); + admin.roomsFilter.setValue('something else'); + }); + + after(() => { + admin.roomsFilter.click(); + admin.roomsFilter.setValue(''); + }); + + it('should not show the general channel', () => { + admin.roomsGeneralChannel.isVisible().should.be.false; + }); + }); + + describe('filter checkbox', () => { + var checkbox = 1; + before(() => { + admin.roomsFilter.setValue(''); + //add value triggers a key event that changes search±±±±±±±±± + admin.roomsFilter.addValue(' '); + admin.roomsGeneralChannel.waitForVisible(5000); + }); + beforeEach(() => { + switch (checkbox) { + case 1: + admin.roomsChannelsCheckbox.click(); + break; + case 2: + admin.roomsDirectCheckbox.click(); + break; + case 3: + admin.roomsPrivateCheckbox.click(); + break; + } + }); + + afterEach(() => { + switch (checkbox) { + case 1: + admin.roomsChannelsCheckbox.click(); + checkbox ++; + break; + case 2: + admin.roomsDirectCheckbox.click(); + checkbox ++; + break; + case 3: + admin.roomsPrivateCheckbox.click(); + break; + } + }); + + it('should show the general channel', () => { + admin.roomsGeneralChannel.isVisible().should.be.true; + }); + + it('should not show the general channel', () => { + admin.roomsGeneralChannel.isVisible().should.be.false; + }); + + it('should not show the general channel', () => { + admin.roomsGeneralChannel.isVisible().should.be.false; + }); + }); + }); + + describe('users', () => { + before(() => { + admin.usersLink.waitForVisible(5000); + admin.usersLink.click(); + admin.usersFilter.waitForVisible(5000); + }); + + after(() => { + admin.infoLink.click(); + }); + + it('should show the search form', () => { + admin.usersFilter.isVisible().should.be.true; + }); + + + it('should show rocket.cat', () => { + admin.usersRocketCat.isVisible().should.be.true; + }); + + describe('filter text', () => { + before(() => { + admin.usersFilter.click(); + browser.pause(5000); + admin.usersFilter.setValue('Rocket.Cat'); + }); + + after(() => { + admin.usersFilter.click(); + admin.usersFilter.setValue(''); + }); + + it('should show rocket.cat', () => { + admin.usersRocketCat.waitForVisible(); + admin.usersRocketCat.isVisible().should.be.true; + }); + }); + + describe('filter text with wrong user', () => { + before(() => { + admin.usersFilter.click(); + browser.pause(5000); + admin.usersFilter.setValue('something else'); + }); + + after(() => { + admin.usersFilter.click(); + admin.usersFilter.setValue(''); + }); + + it('should not show rocket.cat', () => { + admin.usersRocketCat.isVisible().should.be.false; + }); + }); + + describe('users flex tab ', () => { + describe('send invitation', () => { + before(() => { + flexTab.usersSendInvitationTab.waitForVisible(5000); + flexTab.usersSendInvitationTab.click(); + flexTab.usersSendInvitationTextArea.waitForVisible(5000); + }); + + after(() => { + flexTab.usersSendInvitationTab.waitForVisible(5000); + flexTab.usersSendInvitationTab.click(); + flexTab.usersSendInvitationTextArea.waitForVisible(5000, true); + }); + + it('should show the send invitation text area', () => { + flexTab.usersSendInvitationTextArea.isVisible().should.be.true; + }); + + it('should show the cancel button', () => { + flexTab.usersButtonCancel.isVisible().should.be.true; + }); + + it('should show the send button', () => { + flexTab.usersSendInvitationSend.isVisible().should.be.true; + }); + }); + + describe('create user ', () => { + before(() => { + flexTab.usersAddUserTab.waitForVisible(5000); + flexTab.usersAddUserTab.click(); + flexTab.usersAddUserName.waitForVisible(5000); + }); + + after(() => { + flexTab.usersAddUserTab.waitForVisible(5000); + flexTab.usersAddUserTab.click(); + flexTab.usersAddUserName.waitForVisible(5000, true); + }); + + it('should show the name field', () => { + flexTab.usersAddUserName.isVisible().should.be.true; + }); + + it('should show the username field', () => { + flexTab.usersAddUserUsername.isVisible().should.be.true; + }); + + it('should show the email field', () => { + flexTab.usersAddUserEmail.isVisible().should.be.true; + }); + + it('should show the verified checkbox', () => { + flexTab.usersAddUserVerifiedCheckbox.isVisible().should.be.true; + }); + + it('should show the password field', () => { + flexTab.usersAddUserPassword.isVisible().should.be.true; + }); + + it('should show the random password button', () => { + flexTab.usersAddUserRandomPassword.isVisible().should.be.true; + }); + + it('should show the require password change button', () => { + flexTab.usersAddUserChangePasswordCheckbox.isVisible().should.be.true; + }); + + it('should show the role dropdown', () => { + flexTab.usersAddUserRole.isVisible().should.be.true; + }); + + it('should show the join default channel checkbox', () => { + flexTab.usersAddUserDefaultChannelCheckbox.isVisible().should.be.true; + }); + + it('should show the send welcome checkbox', () => { + flexTab.usersAddUserWelcomeEmailCheckbox.isVisible().should.be.true; + }); + + it('should show the save button', () => { + flexTab.usersButtonSave.isVisible().should.be.true; + }); + + it('should show the cancel button', () => { + flexTab.usersButtonCancel.isVisible().should.be.true; + }); + }); + }); + }); + + describe('roles', () => { + before(() =>{ + admin.permissionsLink.waitForVisible(5000); + admin.permissionsLink.click(); + admin.rolesPermissionGrid.waitForVisible(5000); + }); + + after(() => { + admin.infoLink.click(); + }); + + it('should show the permissions grid', () => { + admin.rolesPermissionGrid.isVisible().should.be.true; + }); + + it('should show the new role button', () => { + admin.rolesNewRolesButton.isVisible().should.be.true; + }); + + it('should show the admin link', () => { + admin.rolesAdmin.isVisible().should.be.true; + }); + + describe('new role', () => { + before(() => { + admin.rolesNewRolesButton.waitForVisible(5000); + admin.rolesNewRolesButton.click(); + admin.rolesReturnLink.waitForVisible(5000); + }); + + after(() => { + admin.rolesReturnLink.click(); + }); + + it('should show the return to permissions', () => { + admin.rolesReturnLink.isVisible().should.be.true; + }); + + it('should show the new role name field', () => { + admin.rolesNewRoleName.isVisible().should.be.true; + }); + + it('should show the new role description field', () => { + admin.rolesNewRoleDesc.isVisible().should.be.true; + }); + + it('should show the new role scope', () => { + admin.rolesNewRoleScope.isVisible().should.be.true; + }); + }); + + describe('admin role', () => { + before(() => { + admin.rolesAdmin.waitForVisible(5000); + admin.rolesAdmin.click(); + admin.usersInternalAdmin.waitForVisible(5000); + }); + + after(() => { + admin.rolesReturnLink.click(); + }); + + it('should show internal admin', () => { + admin.usersInternalAdmin.isVisible().should.be.true; + }); + }); + }); + + describe('general settings', () => { + before(() => { + admin.generalLink.waitForVisible(5000); + admin.generalLink.click(); + admin.generalSiteUrl.waitForVisible(5000); + }); + + describe('general', () => { + it('should show site url field', () => { + admin.generalSiteUrl.isVisible().should.be.true; + }); + + it('should change site url field', () => { + admin.generalSiteUrl.setValue('something'); + }); + + it('should show the reset button', () => { + admin.generalSiteUrlReset.waitForVisible(5000); + admin.generalSiteUrlReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalSiteUrlReset.click(); + }); + + it('the site url field should be different from the last input', () => { + admin.generalSiteUrl.getText().should.not.equal('something'); + }); + + it('should show site name field', () => { + admin.generalSiteName.isVisible().should.be.true; + }); + + it('should change site name field', () => { + admin.generalSiteName.setValue('something'); + }); + + it('should show the reset button', () => { + admin.generalSiteNameReset.waitForVisible(5000); + admin.generalSiteNameReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalSiteNameReset.click(); + }); + + it('the name field should be different from the last input', () => { + admin.generalSiteName.getText().should.not.equal('something'); + }); + + it('should show language field', () => { + admin.generalLanguage.isVisible().should.be.true; + }); + + it('should change the language ', () => { + admin.generalLanguage.click(); + admin.generalLanguagePtOption.waitForVisible(5000); + admin.generalLanguagePtOption.click(); + }); + + it('should show the reset button', () => { + admin.generalLanguageReset.waitForVisible(5000); + admin.generalLanguageReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalLanguageReset.click(); + }); + + it('should show invalid self signed certs checkboxes', () => { + admin.generalSelfSignedCertsFalse.isVisible().should.be.true; + admin.generalSelfSignedCertsTrue.isVisible().should.be.true; + }); + + it('should change the invalid self signed certs checkboxes', () => { + admin.generalSelfSignedCertsTrue.click(); + }); + + it('should show the reset button', () => { + admin.generalSelfSignedCertsReset.waitForVisible(5000); + admin.generalSelfSignedCertsReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalSelfSignedCertsReset.click(); + }); + + it('should show favorite rooms checkboxes', () => { + admin.generalFavoriteRoomFalse.isVisible().should.be.true; + admin.generalFavoriteRoomTrue.isVisible().should.be.true; + }); + + it('should change the favorite rooms checkboxes', () => { + admin.generalFavoriteRoomFalse.click(); + }); + + it('should show the reset button', () => { + admin.generalFavoriteRoomReset.waitForVisible(5000); + admin.generalFavoriteRoomReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalFavoriteRoomReset.click(); + }); + + it('should show cdn prefix field', () => { + admin.generalCdnPrefix.isVisible().should.be.true; + }); + + it('should change site url field', () => { + admin.generalCdnPrefix.setValue('something'); + }); + + it('should show the reset button', () => { + admin.generalCdnPrefixReset.waitForVisible(5000); + admin.generalCdnPrefixReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalCdnPrefixReset.click(); + }); + + it('should show the force SSL checkboxes', () => { + admin.generalForceSSLTrue.isVisible().should.be.true; + admin.generalForceSSLFalse.isVisible().should.be.true; + }); + + it('should change the force ssl checkboxes', () => { + admin.generalForceSSLTrue.click(); + }); + + it('should show the reset button', () => { + admin.generalForceSSLReset.waitForVisible(5000); + admin.generalForceSSLReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalForceSSLReset.click(); + }); + + it('should show google tag id field', () => { + admin.generalGoogleTagId.isVisible().should.be.true; + }); + + it('should change google tag id field', () => { + admin.generalGoogleTagId.setValue('something'); + }); + + it('should show the reset button', () => { + admin.generalGoogleTagIdReset.waitForVisible(5000); + admin.generalGoogleTagIdReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalGoogleTagIdReset.click(); + }); + + it('should show bugsnag key field', () => { + admin.generalBugsnagKey.isVisible().should.be.true; + }); + + it('should change bugsnag key id field', () => { + admin.generalBugsnagKey.setValue('something'); + }); + + it('should show the reset button', () => { + admin.generalBugsnagKeyReset.waitForVisible(5000); + admin.generalBugsnagKeyReset.isVisible().should.be.true; + }); + + it('should click the reset button', () => { + admin.generalBugsnagKeyReset.click(); + }); + }); + + describe('iframe', () => { + before(() => { + admin.generalButtonExpandIframe.waitForVisible(5000); + admin.generalButtonExpandIframe.click(); + admin.generalIframeSendTrue.waitForVisible(5000); + admin.generalIframeSendTrue.scroll(); + }); + + it('should show iframe send checkboxes', () => { + admin.generalIframeSendTrue.isVisible().should.be.true; + admin.generalIframeSendFalse.isVisible().should.be.true; + }); + + it('should show send origin field', () => { + admin.generalIframeSendTargetOrigin.isVisible().should.be.true; + }); + + it('should show iframe send checkboxes', () => { + admin.generalIframeRecieveFalse.isVisible().should.be.true; + admin.generalIframeRecieveTrue.isVisible().should.be.true; + }); + + it('should show send origin field', () => { + admin.generalIframeRecieveOrigin.isVisible().should.be.true; + }); + }); + + describe('notifications', () => { + before(() => { + admin.generalButtonExpandNotifications.waitForVisible(5000); + admin.generalButtonExpandNotifications.click(); + admin.generalNotificationDuration.waitForVisible(5000); + admin.generalNotificationDuration.scroll(); + }); + + it('should show the notifications durations field', () => { + admin.generalNotificationDuration.isVisible().should.be.true; + }); + }); + + describe('rest api', () => { + before(() => { + admin.generalButtonExpandRest.waitForVisible(5000); + admin.generalButtonExpandRest.click(); + admin.generalRestApiUserLimit.waitForVisible(5000); + admin.generalRestApiUserLimit.scroll(); + }); + + it('should show the API user add limit field', () => { + admin.generalRestApiUserLimit.isVisible().should.be.true; + }); + }); + + describe('reporting', () => { + before(() => { + admin.generalButtonExpandReporting.waitForVisible(5000); + admin.generalButtonExpandReporting.click(); + admin.generalReportingTrue.waitForVisible(5000); + admin.generalReportingTrue.scroll(); + }); + + it('should show the report to rocket.chat checkboxes', () => { + admin.generalReportingTrue.isVisible().should.be.true; + admin.generalReportingFalse.isVisible().should.be.true; + }); + }); + + describe('stream cast', () => { + before(() => { + admin.generalButtonExpandStreamCast.waitForVisible(5000); + admin.generalButtonExpandStreamCast.click(); + admin.generalStreamCastAdress.waitForVisible(5000); + admin.generalStreamCastAdress.scroll(); + }); + + it('should show the stream cast adress field', () => { + admin.generalStreamCastAdress.isVisible().should.be.true; + }); + }); + + describe('stream cast', () => { + before(() => { + admin.generalButtonExpandUTF8.waitForVisible(5000); + admin.generalButtonExpandUTF8.click(); + admin.generalUTF8Regex.waitForVisible(5000); + admin.generalUTF8Regex.scroll(); + }); + + it('should show the utf8 regex field', () => { + admin.generalUTF8Regex.isVisible().should.be.true; + }); + + it('should show the utf8 names slug checkboxes', () => { + admin.generalUTF8NamesSlugTrue.isVisible().should.be.true; + admin.generalUTF8NamesSlugFalse.isVisible().should.be.true; + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/steps/12-admin-settings.js b/tests/steps/12-admin-settings.js new file mode 100644 index 0000000000000000000000000000000000000000..5184efb7275b745d86cb88e2303fb33c3dfe517f --- /dev/null +++ b/tests/steps/12-admin-settings.js @@ -0,0 +1,161 @@ +/* eslint-env mocha */ +/* eslint-disable func-names, prefer-arrow-callback */ + +import sideNav from '../pageobjects/side-nav.page'; +import flexTab from '../pageobjects/flex-tab.page'; +import admin from '../pageobjects/administration.page'; +import mainContent from '../pageobjects/main-content.page'; +import {checkIfUserIsValid} from '../data/checks'; + +//test data imports +import {checkIfUserIsAdmin} from '../data/checks'; +import {username, email, password, adminUsername, adminEmail, adminPassword} from '../data/user.js'; + +describe('Admin settings', () => { + before(() => { + checkIfUserIsAdmin(adminUsername, adminEmail, adminPassword); + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + sideNav.accountBoxUserName.waitForVisible(5000); + sideNav.accountBoxUserName.click(); + sideNav.admin.waitForVisible(5000); + sideNav.admin.click(); + }); + + describe('user creation via admin view', () => { + before(() => { + admin.usersLink.waitForVisible(5000); + admin.usersLink.click(); + admin.usersFilter.waitForVisible(5000); + flexTab.usersAddUserTab.waitForVisible(5000); + flexTab.usersAddUserTab.click(); + flexTab.usersAddUserName.waitForVisible(5000); + }); + + after(() => { + admin.infoLink.waitForVisible(5000); + admin.infoLink.click(); + }); + + it('create a user', () => { + flexTab.usersAddUserName.setValue('adminCreated'+username); + flexTab.usersAddUserUsername.setValue('adminCreated'+username); + flexTab.usersAddUserEmail.setValue('adminCreated'+email); + flexTab.usersAddUserVerifiedCheckbox.click(); + flexTab.usersAddUserPassword.setValue(password); + flexTab.usersAddUserChangePasswordCheckbox.click(); + flexTab.usersButtonSave.click(); + }); + + it('should show the user in the list', () => { + browser.pause(200); + var element = browser.element('td=adminCreated'+username); + element.isVisible().should.be.visible; + }); + }); + + describe('permissions', () => { + before(() => { + admin.permissionsLink.waitForVisible(5000); + admin.permissionsLink.click(); + admin.rolesPermissionGrid.waitForVisible(5000); + }); + + describe('changing the permissions', () => { + it('should change the create c room permission', () => { + if (admin.rolesUserCreateC.isSelected()) { + admin.rolesUserCreateC.waitForVisible(5000); + admin.rolesUserCreateC.scroll(); + admin.rolesUserCreateC.click(); + } + }); + + it('should change the create d room permission', () => { + if (admin.rolesUserCreateD.isSelected()) { + admin.rolesUserCreateD.waitForVisible(5000); + admin.rolesUserCreateD.scroll(); + admin.rolesUserCreateD.click(); + } + }); + + it('should change the create p room permission', () => { + if (admin.rolesUserCreateP.isSelected()) { + admin.rolesUserCreateP.waitForVisible(5000); + admin.rolesUserCreateP.scroll(); + admin.rolesUserCreateP.click(); + } + }); + + it('should change the mention all permission', () => { + if (admin.rolesUserMentionAll.isSelected()) { + admin.rolesUserMentionAll.waitForVisible(5000); + admin.rolesUserMentionAll.scroll(); + admin.rolesUserMentionAll.click(); + } + }); + + it('should change the delete message all permission for owners', () => { + if (admin.rolesOwnerDeleteMessage.isSelected()) { + admin.rolesOwnerDeleteMessage.waitForVisible(5000); + admin.rolesOwnerDeleteMessage.scroll(); + admin.rolesOwnerDeleteMessage.click(); + } + }); + + it('should change the edit message all permission for owners', () => { + if (admin.rolesOwnerEditMessage.isSelected()) { + admin.rolesOwnerEditMessage.waitForVisible(5000); + admin.rolesOwnerEditMessage.scroll(); + admin.rolesOwnerEditMessage.click(); + } + }); + }); + }); + describe('test the permissions', () => { + before(() => { + sideNav.preferencesClose.waitForVisible(5000); + sideNav.preferencesClose.click(); + + checkIfUserIsValid('adminCreated'+username, 'adminCreated'+email, password); + }); + + it('should not show the plus icon on channels ', () => { + sideNav.newChannelIcon.isVisible().should.be.false; + }); + + it('when clicked should not show the new channel name input ', () => { + sideNav.newChannelBtn.click(); + sideNav.channelName.isVisible().should.be.false; + }); + + it('should not show the plus icon on direct messages ', () => { + sideNav.newDirectMessageIcon.isVisible().should.be.false; + }); + + it('when clicked should not show the new direct message user input ', () => { + sideNav.newDirectMessageBtn.click(); + sideNav.directMessageTarget.isVisible().should.be.false; + }); + + it('go to general', () => { + sideNav.getChannelFromList('general').waitForExist(5000); + sideNav.openChannel('general'); + }); + + it('try to use @all and should be warned by rocket.cat ', () => { + mainContent.addTextToInput('@all'); + mainContent.mentionAllPopUp.waitForVisible(5000); + mainContent.mentionAllPopUp.click(); + mainContent.sendBtn.click(); + mainContent.lastMessage.getText().should.equal('Notify all in this room is not allowed'); + }); + + it.skip('should not be able to delete own message ', () => { + //waiting for changes in the delete-message permission + }); + + it.skip('should not be able to edit own message ', () => { + //waiting for changes in the edit-message permission + }); + }); +}); \ No newline at end of file diff --git a/tests/steps/3-basic-usage.js b/tests/steps/3-basic-usage.js deleted file mode 100644 index 76530743bd30979f86dc4d57c4c5ea3d8beded71..0000000000000000000000000000000000000000 --- a/tests/steps/3-basic-usage.js +++ /dev/null @@ -1,975 +0,0 @@ -/* eslint-env mocha */ -/* eslint-disable func-names, prefer-arrow-callback */ - -import loginPage from '../pageobjects/login.page'; -import flexTab from '../pageobjects/flex-tab.page'; -import mainContent from '../pageobjects/main-content.page'; -import sideNav from '../pageobjects/side-nav.page'; - -//test data imports -import {username, email, password} from '../test-data/user.js'; -import {publicChannelName, privateChannelName} from '../test-data/channel.js'; -import {targetUser, imgURL} from '../test-data/interactions.js'; - -//Test data -const message = 'message from '+username; - - -//Basic usage test start -describe('Basic usage', function() { - this.retries(2); - - it('load page', () => { - loginPage.open(); - }); - - it('create user', () => { - loginPage.gotToRegister(); - - loginPage.registerNewUser({username, email, password}); - - browser.waitForExist('form#login-card input#username', 5000); - - browser.click('.submit > button'); - - mainContent.mainContent.waitForExist(5000); - }); - - it('logout', () => { - sideNav.accountBoxUserName.waitForVisible(5000); - sideNav.accountBoxUserName.click(); - browser.pause(200); - - sideNav.logout.waitForVisible(5000); - sideNav.logout.click(); - }); - - it('login', () => { - loginPage.login({email, password}); - mainContent.mainContent.waitForExist(5000); - }); - - describe('side nav bar', () => { - describe('render', () => { - it('should show the logged username', () => { - sideNav.accountBoxUserName.isVisible().should.be.true; - }); - - it('should show the logged user avatar', () => { - sideNav.accountBoxUserAvatar.isVisible().should.be.true; - }); - - it('should show the new channel button', () => { - sideNav.newChannelBtn.isVisible().should.be.true; - }); - - it('should show the plus icon', () => { - sideNav.newChannelIcon.isVisible().should.be.true; - }); - - it('should show the "More Channels" button', () => { - sideNav.moreChannels.isVisible().should.be.true; - }); - - it('should show the new direct message button', () => { - sideNav.newDirectMessageBtn.isVisible().should.be.true; - }); - - it('should show the plus icon', () => { - sideNav.newDirectMessageIcon.isVisible().should.be.true; - }); - - it('should show the "More Direct Messages" button', () => { - sideNav.moreDirectMessages.isVisible().should.be.true; - }); - - it('should show "general" channel', () => { - sideNav.general.isVisible().should.be.true; - }); - - it('should not show eye icon on general', () => { - sideNav.channelHoverIcon.isVisible().should.be.false; - }); - }); - - describe('user options', () => { - describe('render', () => { - before(() => { - sideNav.accountBoxUserName.click(); - }); - - after(() => { - sideNav.accountBoxUserName.click(); - }); - - it('should show user options', () => { - sideNav.userOptions.waitForVisible(); - sideNav.userOptions.isVisible().should.be.true; - }); - - it('should show online button', () => { - sideNav.statusOnline.isVisible().should.be.true; - }); - - it('should show away button', () => { - sideNav.statusAway.isVisible().should.be.true; - }); - - it('should show busy button', () => { - sideNav.statusBusy.isVisible().should.be.true; - }); - - it('should show offline button', () => { - sideNav.statusOffline.isVisible().should.be.true; - }); - - it('should show settings button', () => { - sideNav.account.isVisible().should.be.true; - }); - - it('should show logout button', () => { - sideNav.logout.isVisible().should.be.true; - }); - }); - }); - }); - - describe('Setting the tests Preferences', () => { - it('opens the user preferences screen', () => { - sideNav.accountBoxUserName.waitForVisible(); - sideNav.accountBoxUserName.click(); - sideNav.account.waitForVisible(); - sideNav.account.click(); - }); - - it('Sets the language to english', () => { - mainContent.setLanguageToEnglish(); - browser.pause(10000); - }); - - it('close the preferences menu', () => { - sideNav.preferencesClose.click(); - }); - }); - - describe('general channel', () => { - it('open GENERAL', () => { - sideNav.getChannelFromList('general').waitForExist(5000); - sideNav.openChannel('general'); - }); - - it('send a message', () => { - mainContent.sendMessage(message); - }); - - describe('main content usage', () => { - describe('render', () => { - it('should show the title of the channel', () => { - mainContent.channelTitle.isVisible().should.be.true; - }); - - it('should show the empty favorite star', () => { - mainContent.emptyFavoriteStar.isVisible().should.be.true; - }); - - it('clicks the star', () => { - mainContent.emptyFavoriteStar.click(); - }); - - it('should not show the empty favorite star', () => { - mainContent.favoriteStar.isVisible().should.be.true; - }); - - it('clicks the star', () => { - mainContent.favoriteStar.click(); - }); - - it('should show the message input bar', () => { - mainContent.messageInput.isVisible().should.be.true; - }); - - it('should show the file attachment button', () => { - mainContent.fileAttachmentBtn.isVisible().should.be.true; - }); - - it('should show the audio recording button', () => { - mainContent.recordBtn.isVisible().should.be.true; - }); - - it('should show the video call button', () => { - mainContent.videoCamBtn.isVisible().should.be.true; - }); - - it('should not show the send button', () => { - mainContent.sendBtn.isVisible().should.be.false; - }); - - it('should show the emoji button', () => { - mainContent.emojiBtn.isVisible().should.be.true; - }); - - it('adds some text to the input', () => { - mainContent.addTextToInput('Some Text'); - }); - - it('should show the send button', () => { - mainContent.sendBtn.isVisible().should.be.true; - }); - - it('should not show the file attachment button', () => { - mainContent.fileAttachmentBtn.isVisible().should.be.false; - }); - - it('should not show the audio recording button', () => { - mainContent.recordBtn.isVisible().should.be.false; - }); - - it('should not show the video call button', () => { - mainContent.videoCamBtn.isVisible().should.be.false; - }); - - it('should show the last message', () => { - mainContent.lastMessage.isVisible().should.be.true; - }); - - it('the last message should be from the loged user', () => { - mainContent.lastMessageUser.getText().should.equal(username); - }); - - it('should not show the Admin tag', () => { - mainContent.lastMessageUserTag.isVisible().should.be.false; - }); - }); - - describe('fileUpload', ()=> { - it('send a attachment', () => { - mainContent.fileUpload(imgURL); - }); - - it('should show the confirm button', () => { - mainContent.popupFileConfirmBtn.isVisible().should.be.true; - }); - - it('should show the cancel button', () => { - mainContent.popupFileCancelBtn.isVisible().should.be.true; - }); - - it('should show the file preview', () => { - mainContent.popupFilePreview.isVisible().should.be.true; - }); - - it('should show the confirm button', () => { - mainContent.popupFileConfirmBtn.isVisible().should.be.true; - }); - - it('should show the file title', () => { - mainContent.popupFileTitle.isVisible().should.be.true; - }); - - it('click the confirm', () => { - mainContent.popupFileConfirmBtn.click(); - }); - }); - - describe('messages actions in general room', ()=> { - describe('render', () => { - it('open GENERAL', () => { - sideNav.openChannel('general'); - }); - - it('send a message to be tested', () => { - mainContent.sendMessage('Message for Message Actions Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('should show the message action menu', () => { - mainContent.messageActionMenu.isVisible().should.be.true; - }); - - it('should show the reply action', () => { - mainContent.messageReply.isVisible().should.be.true; - }); - - it('should show the edit action', () => { - mainContent.messageEdit.isVisible().should.be.true; - }); - - it('should show the delete action', () => { - mainContent.messageDelete.isVisible().should.be.true; - }); - - it('should show the permalink action', () => { - mainContent.messagePermalink.isVisible().should.be.true; - }); - - it('should show the copy action', () => { - mainContent.messageCopy.isVisible().should.be.true; - }); - - it('should show the quote the action', () => { - mainContent.messageQuote.isVisible().should.be.true; - }); - - it('should show the star action', () => { - mainContent.messageStar.isVisible().should.be.true; - }); - - it('should show the reaction action', () => { - mainContent.messageReaction.isVisible().should.be.true; - }); - - it('should show the close action', () => { - mainContent.messageClose.isVisible().should.be.true; - }); - - it('should not show the pin action', () => { - mainContent.messagePin.isVisible().should.be.false; - }); - - it('should not show the mark as unread action', () => { - mainContent.messageUnread.isVisible().should.be.false; - }); - - it('close the action menu', () => { - mainContent.selectAction('close'); - }); - }); - - describe('usage', () => { - it('send a message to test the reply', () => { - mainContent.sendMessage('Message for reply Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('reply the message', () => { - mainContent.selectAction('reply'); - mainContent.sendBtn.click(); - }); - - it('checks if the message was replied', () => { - mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); - }); - - it('send a message to test the edit', () => { - mainContent.addTextToInput('Message for Message edit Tests '); - mainContent.sendBtn.click(); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('edit the message', () => { - mainContent.selectAction('edit'); - mainContent.sendBtn.click(); - }); - - it('send a message to test the delete', () => { - mainContent.sendMessage('Message for Message Delete Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('delete the message', () => { - mainContent.selectAction('delete'); - mainContent.popupFileConfirmBtn.click(); - }); - - it('should not show the deleted message', () => { - mainContent.lastMessage.should.not.equal('Message for Message Delete Tests'); - }); - - it('send a message to test the quote', () => { - mainContent.sendMessage('Message for quote Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('quote the message', () => { - mainContent.selectAction('quote'); - mainContent.sendBtn.click(); - }); - - it('checks if the message was quoted', () => { - mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); - }); - - it('send a message to test the star', () => { - mainContent.sendMessage('Message for star Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('star the message', () => { - mainContent.selectAction('star'); - }); - - it('send a message to test the copy', () => { - mainContent.sendMessage('Message for copy Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('copy the message', () => { - mainContent.selectAction('copy'); - }); - - it('send a message to test the permalink', () => { - mainContent.sendMessage('Message for permalink Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('permalink the message', () => { - mainContent.selectAction('permalink'); - }); - }); - }); - }); - }); - - describe('flextab usage', () => { - describe('render', () => { - it('should show the room info button', () => { - flexTab.channelTab.isVisible().should.be.true; - }); - - it('should show the room info tab content', () => { - browser.pause(7000); - flexTab.channelTab.click(); - flexTab.channelSettings.isVisible().should.be.true; - }); - - it('should show the message search button', () => { - flexTab.searchTab.isVisible().should.be.true; - }); - - it('should show the message tab content', () => { - browser.pause(5000); - flexTab.searchTab.click(); - flexTab.searchTabContent.isVisible().should.be.true; - }); - - it('should show the members tab button', () => { - flexTab.membersTab.isVisible().should.be.true; - }); - - it('should show the members content', () => { - flexTab.membersTab.click(); - flexTab.membersTabContent.isVisible().should.be.true; - }); - - it.skip('should show the members search bar', () => { - flexTab.userSearchBar.isVisible().should.be.true; - }); - - it('should show the show all link', () => { - flexTab.showAll.isVisible().should.be.true; - }); - - it('should show the notifications button', () => { - flexTab.notificationsTab.isVisible().should.be.true; - }); - - it('should show the notifications Tab content', () => { - flexTab.notificationsTab.click(); - flexTab.notificationsSettings.isVisible().should.be.true; - }); - - it('should show the files button', () => { - flexTab.filesTab.isVisible().should.be.true; - }); - - it('should show the files Tab content', () => { - flexTab.filesTab.click(); - flexTab.filesTabContent.isVisible().should.be.true; - }); - - it('should show the mentions button', () => { - flexTab.mentionsTab.isVisible().should.be.true; - }); - - it('should show the mentions Tab content', () => { - flexTab.mentionsTab.click(); - flexTab.mentionsTabContent.isVisible().should.be.true; - }); - - it('should show the starred button', () => { - flexTab.starredTab.isVisible().should.be.true; - }); - - it('should show the starred Tab content', () => { - flexTab.starredTab.click(); - flexTab.starredTabContent.isVisible().should.be.true; - }); - - it('should show the pinned button', () => { - flexTab.pinnedTab.isVisible().should.be.true; - }); - - it('should show the pinned messages Tab content', () => { - flexTab.pinnedTab.click(); - flexTab.pinnedTabContent.isVisible().should.be.true; - }); - }); - }); - - describe('direct channel', () => { - it('start a direct message with rocket.cat', () => { - sideNav.startDirectMessage(targetUser); - }); - - it('open the direct message', () => { - sideNav.openChannel(targetUser); - }); - - it('send a direct message', () => { - mainContent.sendMessage(message); - }); - - it('should show the last message', () => { - mainContent.lastMessage.isVisible().should.be.true; - }); - - it('the last message should be from the loged user', () => { - mainContent.lastMessageUser.getText().should.equal(username); - }); - - it('should not show the Admin tag', () => { - mainContent.lastMessageUserTag.isVisible().should.be.false; - }); - - describe('messages actions in direct messages', ()=> { - describe('render', () => { - it('open GENERAL', () => { - sideNav.openChannel('general'); - }); - - it('send a message to be tested', () => { - mainContent.sendMessage('Message for Message Actions Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('should show the message action menu', () => { - mainContent.messageActionMenu.isVisible().should.be.true; - }); - - it('should show the reply action', () => { - mainContent.messageReply.isVisible().should.be.true; - }); - - it('should show the edit action', () => { - mainContent.messageEdit.isVisible().should.be.true; - }); - - it('should show the delete action', () => { - mainContent.messageDelete.isVisible().should.be.true; - }); - - it('should show the permalink action', () => { - mainContent.messagePermalink.isVisible().should.be.true; - }); - - it('should show the copy action', () => { - mainContent.messageCopy.isVisible().should.be.true; - }); - - it('should show the quote the action', () => { - mainContent.messageQuote.isVisible().should.be.true; - }); - - it('should show the star action', () => { - mainContent.messageStar.isVisible().should.be.true; - }); - - it('should show the reaction action', () => { - mainContent.messageReaction.isVisible().should.be.true; - }); - - it('should show the close action', () => { - mainContent.messageClose.isVisible().should.be.true; - }); - - it('should not show the pin action', () => { - mainContent.messagePin.isVisible().should.be.false; - }); - - it('should not show the mark as unread action', () => { - mainContent.messageUnread.isVisible().should.be.false; - }); - - it('close the action menu', () => { - mainContent.selectAction('close'); - }); - }); - - describe('usage', () => { - it('send a message to test the reply', () => { - mainContent.sendMessage('Message for reply Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('reply the message', () => { - mainContent.selectAction('reply'); - mainContent.sendBtn.click(); - }); - - it('checks if the message was replied', () => { - mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); - }); - - it('send a message to test the edit', () => { - mainContent.addTextToInput('Message for Message edit Tests '); - mainContent.sendBtn.click(); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('edit the message', () => { - mainContent.selectAction('edit'); - mainContent.sendBtn.click(); - }); - - it('send a message to test the delete', () => { - mainContent.sendMessage('Message for Message Delete Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('delete the message', () => { - mainContent.selectAction('delete'); - mainContent.popupFileConfirmBtn.click(); - }); - - it('should not show the deleted message', () => { - mainContent.lastMessage.should.not.equal('Message for Message Delete Tests'); - }); - - it('send a message to test the quote', () => { - mainContent.sendMessage('Message for quote Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('quote the message', () => { - mainContent.selectAction('quote'); - mainContent.sendBtn.click(); - }); - - it('checks if the message was quoted', () => { - mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); - }); - - it('send a message to test the star', () => { - mainContent.sendMessage('Message for star Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('star the message', () => { - mainContent.selectAction('star'); - }); - - it('send a message to test the copy', () => { - mainContent.sendMessage('Message for copy Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('copy the message', () => { - mainContent.selectAction('copy'); - }); - - it('send a message to test the permalink', () => { - mainContent.sendMessage('Message for permalink Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('permalink the message', () => { - mainContent.selectAction('permalink'); - }); - }); - }); - }); - - describe('public channel', () => { - it('create a public channel', () => { - sideNav.createChannel(publicChannelName, false, false); - }); - - it('open the public channel', () => { - sideNav.openChannel(publicChannelName); - browser.pause(3000); - }); - - it('send a message in the public channel', () => { - mainContent.sendMessage(message); - }); - - it('should show the last message', () => { - mainContent.lastMessage.isVisible().should.be.true; - }); - - it('the last message should be from the loged user', () => { - mainContent.lastMessageUser.getText().should.equal(username); - }); - - it('should not show the Admin tag', () => { - var messageTag = mainContent.lastMessageUserTag.getText(); - messageTag.should.not.equal('Admin'); - }); - - it('should show the Owner tag', () => { - var messageTag = mainContent.lastMessageUserTag.getText(); - messageTag.should.equal('Owner'); - }); - - it('add people to the room', () => { - flexTab.membersTab.click(); - flexTab.addPeopleToChannel(targetUser); - }); - - it('remove people from room', () => { - flexTab.removePeopleFromChannel(targetUser); - flexTab.confirmPopup(); - }); - - it.skip('archive the room', () => { - flexTab.channelTab.click(); - flexTab.archiveChannel(); - flexTab.channelTab.click(); - }); - - it('open GENERAL', () => { - sideNav.openChannel('general'); - }); - }); - - describe('private channel', () => { - it('create a private channel', () => { - sideNav.createChannel(privateChannelName, true, false); - }); - - it('send a message in the private channel', () => { - mainContent.sendMessage(message); - }); - - describe('messages actions in private room', ()=> { - describe('render', () => { - it('send a message to be tested', () => { - mainContent.sendMessage('Message for Message Actions Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('should show the message action menu', () => { - mainContent.messageActionMenu.isVisible().should.be.true; - }); - - it('should show the reply action', () => { - mainContent.messageReply.isVisible().should.be.true; - }); - - it('should show the edit action', () => { - mainContent.messageEdit.isVisible().should.be.true; - }); - - it('should show the delete action', () => { - mainContent.messageDelete.isVisible().should.be.true; - }); - - it('should show the permalink action', () => { - mainContent.messagePermalink.isVisible().should.be.true; - }); - - it('should show the copy action', () => { - mainContent.messageCopy.isVisible().should.be.true; - }); - - it('should show the quote the action', () => { - mainContent.messageQuote.isVisible().should.be.true; - }); - - it('should show the star action', () => { - mainContent.messageStar.isVisible().should.be.true; - }); - - it('should show the reaction action', () => { - mainContent.messageReaction.isVisible().should.be.true; - }); - - it('should show the close action', () => { - mainContent.messageClose.isVisible().should.be.true; - }); - - it('should show show the pin action', () => { - mainContent.messagePin.isVisible().should.be.true; - }); - - it('should not show the mark as unread action', () => { - mainContent.messageUnread.isVisible().should.be.false; - }); - - it('close the action menu', () => { - mainContent.selectAction('close'); - }); - }); - - describe('usage', () => { - it('send a message to test the reply', () => { - mainContent.sendMessage('Message for reply Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('reply the message', () => { - mainContent.selectAction('reply'); - mainContent.sendBtn.click(); - }); - - it('checks if the message was replied', () => { - mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); - }); - - it('send a message to test the edit', () => { - mainContent.addTextToInput('Message for Message edit Tests '); - mainContent.sendBtn.click(); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('edit the message', () => { - mainContent.selectAction('edit'); - mainContent.sendBtn.click(); - }); - - it('send a message to test the delete', () => { - mainContent.sendMessage('Message for Message Delete Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('delete the message', () => { - mainContent.selectAction('delete'); - mainContent.popupFileConfirmBtn.click(); - }); - - it('should not show the deleted message', () => { - mainContent.lastMessage.should.not.equal('Message for Message Delete Tests'); - }); - - it('send a message to test the quote', () => { - mainContent.sendMessage('Message for quote Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('quote the message', () => { - mainContent.selectAction('quote'); - mainContent.sendBtn.click(); - }); - - it('checks if the message was quoted', () => { - mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText()); - }); - - it('send a message to test the star', () => { - mainContent.sendMessage('Message for star Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('star the message', () => { - mainContent.selectAction('star'); - }); - - it('send a message to test the copy', () => { - mainContent.sendMessage('Message for copy Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('copy the message', () => { - mainContent.selectAction('copy'); - }); - - it('send a message to test the permalink', () => { - mainContent.sendMessage('Message for permalink Tests'); - }); - - it('open the message action menu', () => { - mainContent.openMessageActionMenu(); - }); - - it('permalink the message', () => { - mainContent.selectAction('permalink'); - }); - }); - }); - - it('add people to the room', () => { - flexTab.membersTab.click(); - flexTab.addPeopleToChannel(targetUser); - }); - - it('remove people from room', () => { - flexTab.removePeopleFromChannel(targetUser); - flexTab.confirmPopup(); - }); - - it('archive the room', () => { - flexTab.channelTab.click(); - flexTab.archiveChannel(); - flexTab.channelTab.click(); - }); - }); -}); diff --git a/tests/steps/4-resolutions.js b/tests/steps/4-resolutions.js deleted file mode 100644 index 893f4ccaf0fb240520c2fefef1e509e6a0fa7e02..0000000000000000000000000000000000000000 --- a/tests/steps/4-resolutions.js +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-env mocha */ -/* eslint-disable func-names, prefer-arrow-callback */ - -import mainContent from '../pageobjects/main-content.page'; -import sideNav from '../pageobjects/side-nav.page'; - -describe('resolutions tests', ()=> { - describe('mobile render', ()=> { - it('change the resolution', ()=> { - browser.windowHandleSize({ - width: 650, - height: 800 - }); - }); - - it('should close de sidenav', () => { - mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); - }); - - it('press the navbar button', () => { - sideNav.sideNavBtn.click(); - }); - - it('should open de sidenav', () => { - browser.pause(1000); - mainContent.mainContent.getLocation().should.not.deep.equal({x:0, y:0}); - }); - - it('open general channel', () => { - sideNav.openChannel('general'); - }); - - it('should close de sidenav', () => { - browser.pause(1000); - mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); - }); - - it('press the navbar button', () => { - sideNav.sideNavBtn.click(); - }); - - it('opens the user preferences screen', () => { - sideNav.accountBoxUserName.waitForVisible(); - sideNav.accountBoxUserName.click(); - sideNav.account.waitForVisible(); - sideNav.account.click(); - }); - - it('press the preferences link', () => { - sideNav.preferences.waitForVisible(); - sideNav.preferences.click(); - }); - - it('should close de sidenav', () => { - browser.pause(1000); - mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); - }); - - it('press the navbar button', () => { - sideNav.sideNavBtn.click(); - }); - - it('press the profile link', () => { - sideNav.profile.waitForVisible(); - sideNav.profile.click(); - }); - - it('should close de sidenav', () => { - browser.pause(1000); - mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); - }); - - it('press the navbar button', () => { - sideNav.sideNavBtn.click(); - }); - - it('press the avatar link', () => { - sideNav.avatar.waitForVisible(); - sideNav.avatar.click(); - }); - - it('should close de sidenav', () => { - browser.pause(1000); - mainContent.mainContent.getLocation().should.deep.equal({x:0, y:0}); - }); - - it('press the navbar button', () => { - sideNav.sideNavBtn.click(); - }); - - it('change the resolution', ()=> { - browser.windowHandleSize({ - width: 1450, - height: 900 - }); - }); - - it('close the preferences menu', () => { - sideNav.preferencesClose.click(); - }); - }); -}); \ No newline at end of file diff --git a/tests/steps/6-channel.js b/tests/steps/6-channel.js deleted file mode 100644 index 879c6eea2b99e8b1c092d95ae5dbc4254b8f1376..0000000000000000000000000000000000000000 --- a/tests/steps/6-channel.js +++ /dev/null @@ -1,132 +0,0 @@ -/* eslint-env mocha */ -/* eslint-disable func-names, prefer-arrow-callback */ - -import flexTab from '../pageobjects/flex-tab.page'; -import mainContent from '../pageobjects/main-content.page'; -import sideNav from '../pageobjects/side-nav.page'; - -import {username} from '../test-data/user.js'; -import {publicChannelName} from '../test-data/channel.js'; -import {targetUser} from '../test-data/interactions.js'; - -describe('channel settings', ()=> { - - describe('channel info tab', ()=> { - it('open the channel', ()=> { - sideNav.openChannel(publicChannelName); - }); - - it('open the channel info tab', ()=> { - flexTab.channelTab.waitForVisible(); - flexTab.channelTab.click(); - }); - - it('should show the old name', ()=> { - flexTab.firstSetting.waitForVisible(); - flexTab.firstSetting.getText().should.equal(publicChannelName); - }); - - it('click the edit name', ()=> { - flexTab.editNameBtn.waitForVisible(); - flexTab.editNameBtn.click(); - }); - - it('edit the name input', ()=> { - flexTab.editNameTextInput.waitForVisible(); - flexTab.editNameTextInput.setValue('NAME-EDITED-'+publicChannelName); - }); - - it('save the name', ()=> { - flexTab.editNameSave.click(); - - }); - - it('should show the new name', ()=> { - //gives timeout errors - var channelName = sideNav.getChannelFromList('NAME-EDITED-'+publicChannelName); - channelName.getText().should.equal('NAME-EDITED-'+publicChannelName); - }); - - it('click the edit topic', ()=> { - browser.pause(500); - flexTab.editTopicBtn.waitForVisible(5000); - flexTab.editTopicBtn.click(); - }); - - it('edit the topic input', ()=> { - flexTab.editTopicTextInput.waitForVisible(5000); - flexTab.editTopicTextInput.setValue('TOPIC EDITED'); - }); - - it('save the topic', ()=> { - flexTab.editNameSave.click(); - }); - - it('should show the new topic', ()=> { - flexTab.secondSetting.getText().should.equal('TOPIC EDITED'); - }); - - it('click the edit description', ()=> { - flexTab.editDescriptionBtn.waitForVisible(); - flexTab.editDescriptionBtn.click(); - }); - - it('edit the description input', ()=> { - flexTab.editDescriptionTextInput.waitForVisible(); - flexTab.editDescriptionTextInput.setValue('DESCRIPTION EDITED'); - }); - - it('save the description', ()=> { - flexTab.editNameSave.click(); - }); - - it('should show the new description', ()=> { - flexTab.thirdSetting.getText().should.equal('DESCRIPTION EDITED'); - }); - - it('dismiss the toast', ()=> { - flexTab.dismissToast(); - }); - - it('open the users tab', ()=> { - flexTab.membersTab.waitForVisible(); - flexTab.membersTab.click(); - - }); - - it('sets rocket cat as owner', ()=> { - flexTab.setUserOwner(targetUser); - }); - - it('dismiss the toast', ()=> { - flexTab.dismissToast(); - }); - - it('should show the owner add message', ()=> { - mainContent.lastMessage.getText().should.equal(targetUser+' was set owner by '+username); - }); - - it('sets rocket cat as moderator', ()=> { - browser.pause(1000); - flexTab.setUserModerator(targetUser); - }); - - it('should show the moderator add message', ()=> { - mainContent.lastMessage.getText().should.equal(targetUser+' was set moderator by '+username); - }); - - it('mute rocket cat', ()=> { - browser.pause(5000); - flexTab.muteUser(targetUser); - }); - - it('confirms the popup', ()=> { - flexTab.confirmPopup(); - }); - - it('close the user screen', ()=> { - browser.pause(5000); - flexTab.viewAllBtn.click(); - }); - }); -}); \ No newline at end of file diff --git a/tests/steps/API.js b/tests/steps/API.js new file mode 100644 index 0000000000000000000000000000000000000000..c5baf7da129a2c0f8f98b1a559a002c2d037749a --- /dev/null +++ b/tests/steps/API.js @@ -0,0 +1,88 @@ +/* eslint-env mocha */ +/* globals expect */ +/* eslint no-unused-vars: 0 */ + +import supertest from 'supertest'; +const request = supertest('http://localhost:3000'); +const prefix = '/api/v1/'; + +import {adminUsername, adminEmail, adminPassword} from '../data/user.js'; + +function api(path) { + return prefix + path; +} + +function log(res) { + console.log(res.req.path); + console.log({ + body: res.body, + headers: res.headers + }); +} + +const credentials = { + ['X-Auth-Token']: undefined, + ['X-User-Id']: undefined +}; + +const login = { + user: adminUsername, + password: adminPassword +}; + +describe('API default', () => { + // Required by mobile apps + it('/info', (done) => { + request.get('/api/info') + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('version'); + expect(res.body).to.have.deep.property('build.date'); + expect(res.body).to.have.deep.property('build.nodeVersion'); + expect(res.body).to.have.deep.property('build.arch'); + expect(res.body).to.have.deep.property('build.platform'); + expect(res.body).to.have.deep.property('build.osRelease'); + expect(res.body).to.have.deep.property('build.totalMemory'); + expect(res.body).to.have.deep.property('build.freeMemory'); + expect(res.body).to.have.deep.property('build.cpus'); + }) + .end(done); + }); +}); + +describe('API v1', () => { + before((done) => { + request.post(api('login')) + .send(login) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + credentials['X-Auth-Token'] = res.body.data.authToken; + credentials['X-User-Id'] = res.body.data.userId; + }) + .end(done); + }); + + it('/login', () => { + expect(credentials).to.have.property('X-Auth-Token').with.length.at.least(1); + expect(credentials).to.have.property('X-User-Id').with.length.at.least(1); + }); + + it('/me', (done) => { + request.get(api('me')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('_id', credentials['X-User-Id']); + expect(res.body).to.have.property('username', login.user); + expect(res.body).to.have.property('active'); + expect(res.body).to.have.property('name'); + expect(res.body).to.have.deep.property('emails[0].address', adminEmail); + }) + .end(done); + }); + +}); diff --git a/tests/test-data/user.js b/tests/test-data/user.js deleted file mode 100644 index 0b8032fff1831d876ea570f0d1fa79a02efcddc2..0000000000000000000000000000000000000000 --- a/tests/test-data/user.js +++ /dev/null @@ -1,3 +0,0 @@ -export const username = 'user.test.'+Date.now(); -export const email = username+'@rocket.chat'; -export const password = 'rocket.chat'; \ No newline at end of file