diff --git a/.docker/develop/Dockerfile b/.docker/develop/Dockerfile index 7f105bd027e..b14fd89de4d 100644 --- a/.docker/develop/Dockerfile +++ b/.docker/develop/Dockerfile @@ -24,7 +24,9 @@ VOLUME /app/uploads WORKDIR /app/bundle # needs a mongoinstance - defaults to container linking with alias 'mongo' -ENV MONGO_URL=mongodb://mongo:27017/rocketchat \ +ENV DEPLOY_METHOD=docker \ + NODE_ENV=production \ + MONGO_URL=mongodb://mongo:27017/rocketchat \ MONGO_OPLOG_URL=mongodb://mongo:27017/local \ HOME=/tmp \ PORT=3000 \ diff --git a/.docker/latest/Dockerfile b/.docker/latest/Dockerfile index defdc0c2dc8..77b309ea583 100644 --- a/.docker/latest/Dockerfile +++ b/.docker/latest/Dockerfile @@ -24,7 +24,9 @@ VOLUME /app/uploads WORKDIR /app/bundle # needs a mongoinstance - defaults to container linking with alias 'mongo' -ENV MONGO_URL=mongodb://mongo:27017/rocketchat \ +ENV DEPLOY_METHOD=docker \ + NODE_ENV=production \ + MONGO_URL=mongodb://mongo:27017/rocketchat \ HOME=/tmp \ PORT=3000 \ ROOT_URL=http://localhost:3000 \ diff --git a/.docker/release-candidate/Dockerfile b/.docker/release-candidate/Dockerfile new file mode 100644 index 00000000000..192b8e95c01 --- /dev/null +++ b/.docker/release-candidate/Dockerfile @@ -0,0 +1,37 @@ +FROM rocketchat/base:4 + +ENV RC_VERSION release-candidate + +MAINTAINER buildmaster@rocket.chat + +RUN set -x \ + && curl -SLf "https://rocket.chat/releases/${RC_VERSION}/download" -o rocket.chat.tgz \ + && curl -SLf "https://rocket.chat/releases/${RC_VERSION}/asc" -o rocket.chat.tgz.asc \ + && mkdir /app \ + && gpg --verify rocket.chat.tgz.asc \ + && mkdir -p /app \ + && tar -zxf rocket.chat.tgz -C /app \ + && rm rocket.chat.tgz rocket.chat.tgz.asc \ + && cd /app/bundle/programs/server \ + && npm install \ + && npm cache clear \ + && chown -R rocketchat:rocketchat /app + +USER rocketchat + +VOLUME /app/uploads + +WORKDIR /app/bundle + +# needs a mongoinstance - defaults to container linking with alias 'mongo' +ENV DEPLOY_METHOD=docker \ + NODE_ENV=production \ + MONGO_URL=mongodb://mongo:27017/rocketchat \ + HOME=/tmp \ + PORT=3000 \ + ROOT_URL=http://localhost:3000 \ + Accounts_AvatarStorePath=/app/uploads + +EXPOSE 3000 + +CMD ["node", "main.js"] diff --git a/.github/changelog.js b/.github/changelog.js new file mode 100644 index 00000000000..50edbefe4e8 --- /dev/null +++ b/.github/changelog.js @@ -0,0 +1,116 @@ +/* eslint no-var: 0, object-shorthand: 0, prefer-template: 0 */ + +'use strict'; +var readFile = require('fs').readFileSync; +var resolve = require('path').resolve; +var gitUrl = 'https://github.com/RocketChat/Rocket.Chat'; + +var parserOpts = { + headerPattern: /^(\[([A-z]+)\] )?(.*)$/m, + headerCorrespondence: [ + 'stype', + 'type', + 'subject' + ], + mergePattern: /^Merge pull request #(.*) from .*$/, + mergeCorrespondence: ['pr'] + // noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES'], + // revertPattern: /^revert:\s([\s\S]*?)\s*This reverts commit (\w*)\./, + // revertCorrespondence: ['header', 'hash'], + // mergePattern: /^Merge pull request #(\d+) from (.*)$/, + // mergeCorrespondence: ['id', 'source'] +}; + +var LABELS = { + BREAK: { + title: 'BREAKING CHANGES', + collapse: false + }, + NEW: { + title: 'New Features', + collapse: false + }, + FIX: { + title: 'Bug Fixes', + collapse: false + }, + DOC: { + title: 'Documentation', + collapse: true + }, + OTHER: { + title: 'Others', + collapse: true + } +}; + +var sort = Object.keys(LABELS); + +var writerOpts = { + transform: function(commit) { + if (!commit.pr) { + return; + } + + // console.log(commit); + commit.type = (commit.type || 'OTHER').toUpperCase(); + if (LABELS[commit.type] == null) { + return; + } + + commit.pr_url = gitUrl + '/pull/' + commit.pr; + + var issues = []; + + if (typeof commit.hash === 'string') { + commit.hash = commit.hash.substring(0, 7); + } + + if (typeof commit.subject === 'string') { + // GitHub issue URLs. + commit.subject = commit.subject.replace(/#([0-9]+)/g, function(_, issue) { + issues.push(issue); + return '[#' + issue + '](' + gitUrl + '/issue/' + issue + ')'; + }); + // GitHub user URLs. + commit.subject = commit.subject.replace(/@([a-zA-Z0-9_]+)/g, '[@$1](https://github.com/$1)'); + } + + // remove references that already appear in the subject + commit.references = commit.references.filter(function(reference) { + if (issues.indexOf(reference.issue) === -1) { + return true; + } + + return false; + }); + + return commit; + }, + groupBy: 'type', + commitGroupsSort: function(a, b) { + return sort.indexOf(a.title) > sort.indexOf(b.title); + }, + finalizeContext: function(context) { + context.commitGroups.forEach(function(group) { + Object.assign(group, LABELS[group.title.toUpperCase()]); + }); + + // console.log(context); + return context; + }, + commitsSort: ['subject'] +}; + +writerOpts.mainTemplate = readFile(resolve(__dirname, 'templates/template.hbs'), 'utf-8'); +writerOpts.headerPartial = readFile(resolve(__dirname, 'templates/header.hbs'), 'utf-8'); +writerOpts.commitPartial = readFile(resolve(__dirname, 'templates/commit.hbs'), 'utf-8'); +writerOpts.footerPartial = readFile(resolve(__dirname, 'templates/footer.hbs'), 'utf-8'); + +module.exports = { + gitRawCommitsOpts: { + merges: null + }, + parserOpts: parserOpts, + writerOpts: writerOpts +}; diff --git a/.github/templates/commit.hbs b/.github/templates/commit.hbs new file mode 100755 index 00000000000..1781268b277 --- /dev/null +++ b/.github/templates/commit.hbs @@ -0,0 +1,40 @@ +{{!-- pr reference --}}- {{#if pr}}[#{{pr}}]({{pr_url}}){{/if}} + +{{~!-- subject --}} {{subject}} + +{{~!-- commit references --}} +{{~#if references~}} + , closes + {{~#each references}} {{#if @root.linkReferences~}} + [ + {{~#if this.owner}} + {{~this.owner}}/ + {{~/if}} + {{~this.repository}}#{{this.issue}}]( + {{~#if @root.repository}} + {{~#if @root.host}} + {{~@root.host}}/ + {{~/if}} + {{~#if this.repository}} + {{~#if this.owner}} + {{~this.owner}}/ + {{~/if}} + {{~this.repository}} + {{~else}} + {{~#if @root.owner}} + {{~@root.owner}}/ + {{~/if}} + {{~@root.repository}} + {{~/if}} + {{~else}} + {{~@root.repoUrl}} + {{~/if}}/ + {{~@root.issue}}/{{this.issue}}) + {{~else}} + {{~#if this.owner}} + {{~this.owner}}/ + {{~/if}} + {{~this.repository}}#{{this.issue}} + {{~/if}}{{/each}} +{{~/if}} + diff --git a/.github/templates/footer.hbs b/.github/templates/footer.hbs new file mode 100755 index 00000000000..2aa774f5367 --- /dev/null +++ b/.github/templates/footer.hbs @@ -0,0 +1,11 @@ +{{#if noteGroups}} +{{#each noteGroups}} + +### {{title}} + +{{#each notes}} +* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} +{{/each}} +{{/each}} + +{{/if}} diff --git a/.github/templates/header.hbs b/.github/templates/header.hbs new file mode 100755 index 00000000000..313fd652804 --- /dev/null +++ b/.github/templates/header.hbs @@ -0,0 +1,26 @@ + +{{#if isPatch~}} + ## +{{~else~}} + # +{{~/if}} {{#if @root.linkCompare~}} + [{{version}}]( + {{~#if @root.repository~}} + {{~#if @root.host}} + {{~@root.host}}/ + {{~/if}} + {{~#if @root.owner}} + {{~@root.owner}}/ + {{~/if}} + {{~@root.repository}} + {{~else}} + {{~@root.repoUrl}} + {{~/if~}} + /compare/{{previousTag}}...{{currentTag}}) +{{~else}} + {{~version}} +{{~/if}} +{{~#if title}} "{{title}}" +{{~/if}} +{{~#if date}} ({{date}}) +{{/if}} diff --git a/.github/templates/template.hbs b/.github/templates/template.hbs new file mode 100755 index 00000000000..0705b78e3a3 --- /dev/null +++ b/.github/templates/template.hbs @@ -0,0 +1,22 @@ +{{> header}} + +{{#each commitGroups}} + +{{#if collapse}} +
+{{title}} +{{else}} +### {{title}} +{{/if}} + +{{#each commits}} +{{> commit root=@root}} +{{/each}} +{{#if collapse}} +
+{{/if}} + +{{/each}} +{{> footer}} + + diff --git a/.meteor/packages b/.meteor/packages index 1cb892ab586..9c3ec103173 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -4,39 +4,39 @@ # but you can also edit it by hand. rocketchat:cors -accounts-facebook@1.1.0 -accounts-github@1.2.0 -accounts-google@1.1.0 -accounts-meteor-developer@1.2.0 -accounts-password@1.3.4 -accounts-twitter@1.2.0 +accounts-facebook@1.1.1 +accounts-github@1.2.1 +accounts-google@1.1.2 +accounts-meteor-developer@1.2.1 +accounts-password@1.3.6 +accounts-twitter@1.2.1 blaze-html-templates -check@1.2.4 +check@1.2.5 coffeescript@1.11.1_4 -ddp-rate-limiter@1.0.6 -ecmascript@0.6.3 +ddp-rate-limiter@1.0.7 +ecmascript@0.7.3 ejson@1.0.13 -email@1.1.18 +email@1.2.1 fastclick@1.0.13 -http@1.2.11 +http@1.2.12 jquery@1.11.10 less@2.7.9 logging@1.1.17 meteor-base@1.0.4 mobile-experience@1.0.4 -mongo@1.1.15 +mongo@1.1.17 random@1.0.10 -rate-limit@1.0.6 +rate-limit@1.0.8 reactive-dict@1.1.8 reactive-var@1.0.11 reload@1.1.11 service-configuration@1.0.11 session@1.1.7 -shell-server@0.2.2 +shell-server@0.2.3 spacebars -standard-minifier-css@1.3.3 -standard-minifier-js@1.2.2 -tracker@1.1.2 +standard-minifier-css@1.3.4 +standard-minifier-js@2.0.0 +tracker@1.1.3 rocketchat:2fa rocketchat:action-links @@ -132,6 +132,9 @@ rocketchat:videobridge rocketchat:webrtc rocketchat:wordpress rocketchat:message-snippet +rocketchat:google-natural-language +rocketchat:drupal +rocketchat:monitoring #rocketchat:chatops konecty:change-case @@ -169,4 +172,3 @@ underscorestring:underscore.string yasaricli:slugify yasinuslu:blaze-meta deepwell:bootstrap-datepicker2 -rocketchat:google-natural-language diff --git a/.meteor/release b/.meteor/release index e6940fd82f2..fb6f3bc15e2 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.4.3.1 +METEOR@1.4.4.2 diff --git a/.meteor/versions b/.meteor/versions index cf1ff998c69..67e75bb0fc3 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,58 +1,58 @@ -accounts-base@1.2.14 -accounts-facebook@1.1.0 -accounts-github@1.2.0 -accounts-google@1.1.0 -accounts-meteor-developer@1.2.0 +accounts-base@1.2.17 +accounts-facebook@1.1.1 +accounts-github@1.2.1 +accounts-google@1.1.2 +accounts-meteor-developer@1.2.1 accounts-oauth@1.1.15 -accounts-password@1.3.4 -accounts-twitter@1.2.0 +accounts-password@1.3.6 +accounts-twitter@1.2.1 aldeed:simple-schema@1.5.3 allow-deny@1.0.5 autoupdate@1.3.12 -babel-compiler@6.14.1 +babel-compiler@6.18.2 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 -blaze@2.3.0 -blaze-html-templates@1.1.0 +blaze@2.3.2 +blaze-html-templates@1.1.2 blaze-tools@1.0.10 boilerplate-generator@1.0.11 caching-compiler@1.1.9 -caching-html-compiler@1.1.0 +caching-html-compiler@1.1.2 callback-hook@1.0.10 cfs:http-methods@0.0.32 check@1.2.5 coffeescript@1.12.3_1 dandv:caret-position@2.1.1 ddp@1.2.5 -ddp-client@1.3.3 +ddp-client@1.3.4 ddp-common@1.2.8 -ddp-rate-limiter@1.0.6 -ddp-server@1.3.13 +ddp-rate-limiter@1.0.7 +ddp-server@1.3.14 deepwell:bootstrap-datepicker2@1.3.0 deps@1.0.12 diff-sequence@1.0.7 dispatch:run-as-user@1.1.1 -ecmascript@0.6.3 +ecmascript@0.7.3 ecmascript-runtime@0.3.15 edgee:slingshot@0.7.1 ejson@1.0.13 -email@1.1.18 +email@1.2.1 emojione:emojione@2.2.6 facebook-oauth@1.3.0 fastclick@1.0.13 francocatena:status@1.5.3 geojson-utils@1.0.10 github-oauth@1.2.0 -google-oauth@1.2.0 +google-oauth@1.2.4 hot-code-push@1.0.4 html-tools@1.0.11 htmljs@1.0.11 -http@1.2.11 +http@1.2.12 id-map@1.0.9 jalik:ufs@0.7.4_1 -jalik:ufs-gridfs@0.1.4 -jalik:ufs-local@0.2.8 +jalik:ufs-gridfs@0.2.1 +jalik:ufs-local@0.2.9 jparker:crypto-core@0.1.0 jparker:crypto-md5@0.1.1 jparker:gravatar@0.5.1 @@ -77,17 +77,18 @@ meteor@1.6.1 meteor-base@1.0.4 meteor-developer-oauth@1.2.0 meteorhacks:inject-initial@1.0.4 +meteorhacks:meteorx@1.4.1 meteorspark:util@0.2.0 minifier-css@1.2.16 -minifier-js@1.2.18 -minimongo@1.0.20 +minifier-js@2.0.0 +minimongo@1.0.23 mizzao:autocomplete@0.5.1 mizzao:timesync@0.3.4 mobile-experience@1.0.4 mobile-status-bar@1.0.14 -modules@0.7.9 -modules-runtime@0.7.9 -mongo@1.1.15 +modules@0.8.2 +modules-runtime@0.7.10 +mongo@1.1.17 mongo-id@1.0.6 mongo-livedata@1.0.12 mrt:reactive-store@0.0.1 @@ -95,11 +96,11 @@ mystor:device-detection@0.2.0 nimble:restivus@0.8.12 nooitaf:colors@1.1.2_1 npm-bcrypt@0.9.2 -npm-mongo@2.2.16_1 +npm-mongo@2.2.24 oauth@1.1.13 oauth1@1.1.11 oauth2@1.1.11 -observe-sequence@1.0.15 +observe-sequence@1.0.16 ordered-dict@1.0.9 ostrio:cookies@2.2.0 pauli:accounts-linkedin@2.1.2 @@ -114,7 +115,7 @@ raix:handlebar-helpers@0.2.5 raix:push@3.0.3-rc.7 raix:ui-dropped-event@0.0.7 random@1.0.10 -rate-limit@1.0.6 +rate-limit@1.0.8 reactive-dict@1.1.8 reactive-var@1.0.11 reload@1.1.11 @@ -137,6 +138,7 @@ rocketchat:crowd@1.0.0 rocketchat:custom-oauth@1.0.0 rocketchat:custom-sounds@1.0.0 rocketchat:dolphin@0.0.2 +rocketchat:drupal@0.0.1 rocketchat:emoji@1.0.0 rocketchat:emoji-custom@1.0.0 rocketchat:emoji-emojione@0.0.1 @@ -176,6 +178,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:monitoring@2.30.2_3 rocketchat:oauth2-server@2.0.0 rocketchat:oauth2-server-config@1.0.0 rocketchat:oembed@0.0.1 @@ -223,30 +226,30 @@ routepolicy@1.0.12 service-configuration@1.0.11 session@1.1.7 sha@1.0.9 -shell-server@0.2.2 +shell-server@0.2.3 simple:json-routes@2.1.0 smoral:sweetalert@1.1.1 -spacebars@1.0.13 -spacebars-compiler@1.1.0 +spacebars@1.0.15 +spacebars-compiler@1.1.2 srp@1.0.10 standard-minifier-css@1.3.4 -standard-minifier-js@1.2.3 +standard-minifier-js@2.0.0 steffo:meteor-accounts-saml@0.0.1 tap:i18n@1.8.2 -templating@1.3.0 -templating-compiler@1.3.0 -templating-runtime@1.3.0 -templating-tools@1.1.0 +templating@1.3.2 +templating-compiler@1.3.2 +templating-runtime@1.3.2 +templating-tools@1.1.2 tmeasday:crypto-base@3.1.2 tmeasday:crypto-md5@3.1.2 todda00:friendly-slugs@0.6.0 -tracker@1.1.2 +tracker@1.1.3 twitter-oauth@1.2.0 -ui@1.0.12 +ui@1.0.13 underscore@1.0.10 underscorestring:underscore.string@3.3.4 url@1.1.0 -webapp@1.3.13 +webapp@1.3.15 webapp-hashing@1.0.9 yasaricli:slugify@0.0.7 yasinuslu:blaze-meta@0.3.3 diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index 4477b9915af..14ee2003b17 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -21,7 +21,7 @@ const pkgdef :Spk.PackageDefinition = ( appVersion = 62, # Increment this for every release. - appMarketingVersion = (defaultText = "0.55.0-develop"), + appMarketingVersion = (defaultText = "0.56.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/version.js b/.scripts/version.js new file mode 100644 index 00000000000..a99cf279946 --- /dev/null +++ b/.scripts/version.js @@ -0,0 +1,47 @@ +/* eslint object-shorthand: 0, prefer-template: 0 */ + +const path = require('path'); +const fs = require('fs'); +let pkgJson = {}; + +try { + pkgJson = require(path.resolve( + process.cwd(), + './package.json' + )); +} catch (err) { + console.error('no root package.json found'); +} + +const readline = require('readline'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +const files = [ + './package.json', + './.sandstorm/sandstorm-pkgdef.capnp', + './.travis/snap.sh', + './packages/rocketchat-lib/rocketchat.info' +]; + +console.log('Current version:', pkgJson.version); +rl.question('New version: ', function(version) { + rl.close(); + + version = version.trim(); + + if (version === '') { + return; + } + + console.log('Updating files to version ' + version); + + files.forEach(function(file) { + const data = fs.readFileSync(file, 'utf8'); + + fs.writeFileSync(file, data.replace(pkgJson.version, version), 'utf8'); + }); +}); diff --git a/.snapcraft/README.md b/.snapcraft/README.md new file mode 100644 index 00000000000..f0197f2dc65 --- /dev/null +++ b/.snapcraft/README.md @@ -0,0 +1,61 @@ +![Rocket.Chat logo](https://rocket.chat/images/logo/logo-dark.svg?v3) + +# rocketchat-server snap for Ubuntu Core (all arch) + +Features: +* bundles ubuntu distribution specific and RC compatible mongodb version +* oplog tailing for mongo by default +* mongodb backup command +* mongodb restore command +* caddy reverse proxy built-in - capable of handling free lestencrypt ssl + +Note: + +Currently, this repository is mirrored on launchpad, and used to build latest ARMHF and i386 snaps. + +You can download recent builds here: +https://code.launchpad.net/~sing-li/+snap/rocketchat-server + +Due an issue with the existing installed base of amd64 users (existing snap always installed mongodb 3.2 [#issue](https://github.com/RocketChat/rocketchat-server-snap/issues/3)), this snap is not currently used for amd64 builds. + +### Test installation + +Download the latest snap file of the corresponding architecture to your Ubuntu Core 16 or 16.04LTS server. + +`sudo snap install ./rocketchat-server-xxxxxxxx.snap --dangerous` + + +### Development or compile your own snap + +Make sure you have `snapcraft` installed. + +``` +git clone https://github.com/RocketChat/rocketchat-server-snap +cd rocketchat-server-snap +snapcraft snap +``` + +### Regression tests (run for amd64, i386 and armhf): +- snapcraft runs properly +- snap installs properly +- all services start automatically +- rc service shows a 5-second restart delay while waiting for mongo + - to test manually, stop rc, stop mongo, start rc, wait 20s or so, start mongo +- rc can be successfully restarted via the "Restart the server" button under Admin > Settings > General +- rc service shows a 5-second delay when restarted via this button +- all commands execute successfully: + - initcaddy + - modify the Caddyfile to test: + - self-signed TLS certificate (use the "tls self_signed" caddy directive) + - changing ports (with and without TLS) + - using IP address (only works without TLS) + - successfully acquiring a Let's Encrypt certificate (requires a registered domain) + - backupdb + - should run only with sudo + - restoredb + - ideally, stop rc service prior to running this (mongo must be running) + - should run only with sudo + - use any file outside of $snap_common (should fail) + - use the file created with backupdb + - use a dummy .tgz file without actual data + - with and without a "parties" directory in the archive diff --git a/.snapcraft/candidate/snapcraft.yaml b/.snapcraft/candidate/snapcraft.yaml new file mode 100644 index 00000000000..567d2e47900 --- /dev/null +++ b/.snapcraft/candidate/snapcraft.yaml @@ -0,0 +1,102 @@ +# +# Easiest way to work with this file, from an updated Ubuntu 16.04 LTS image +# 1. create a non-root user with sudo priv and perform following steps as non-root +# 2. `sudo apt-get update` +# 3. `sudo apt-get install snapcraft python build-essential` +# 4. `snapcraft stage` +# 5. `snapcraft snap` + +name: rocketchat-server +version: #{RC_VERSION} +summary: Rocket.Chat server +description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ +confinement: strict +assumes: [snapd2.21] +apps: + rocketchat-server: + command: startRocketChat + daemon: simple + plugs: [network, network-bind] + rocketchat-mongo: + command: startmongo + daemon: simple + plugs: [network, network-bind] + rocketchat-caddy: + command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile -host=localhost:8080 + daemon: simple + plugs: [network, network-bind] + mongo: + command: env LC_ALL=C mongo + plugs: [network] + restoredb: + command: env LC_ALL=C restoredb + plugs: [network] + backupdb: + command: env LC_ALL=c rcbackup + plugs: [network] + initcaddy: + command: env LC_ALL=c initcaddy +parts: + node: + plugin: nodejs + node-engine: 4.8.1 + node-packages: + - promise + - fibers + - underscore + - source-map-support + - semver + build-packages: + # For fibers + - python + - build-essential + - nodejs + organize: + lib/node_modules: node_modules + rocketchat-server: + plugin: dump + after: [node] + source: https://rocket.chat/releases/release-candidate/download + source-type: tar + stage-packages: + - graphicsmagick + stage: + - programs + - main.js + - .node_version.txt + - usr + - lib + mongodb: + build-packages: + - wget + source: ./ + prepare: ./resources/preparemongo + plugin: dump + stage-packages: + - libssl1.0.0 + prime: + - usr + - bin + - lib + scripts: + plugin: dump + source: resources/ + organize: + rcbackup: bin/rcbackup + restoredb: bin/restoredb + startmongo: bin/startmongo + startRocketChat: bin/startRocketChat + initreplset.js: bin/initreplset.js + Caddyfile: bin/Caddyfile + initcaddy: bin/initcaddy + prime: + - bin + caddy: + prepare: ./resources/preparecaddy + plugin: dump + source: ./ + prime: + - bin + organize: + caddy: bin/caddy + after: [mongodb] diff --git a/.snapcraft/edge/snapcraft.yaml b/.snapcraft/edge/snapcraft.yaml index 218104482cf..2934d80e590 100644 --- a/.snapcraft/edge/snapcraft.yaml +++ b/.snapcraft/edge/snapcraft.yaml @@ -7,23 +7,30 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 0.55.0-develop +version: #{RC_VERSION} summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict +assumes: [snapd2.21] 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 + command: startRocketChat daemon: simple plugs: [network, network-bind] rocketchat-mongo: - command: env LC_ALL=C mongod --bind_ip 127.0.0.1 --smallfiles --dbpath=$SNAP_COMMON + command: startmongo daemon: simple plugs: [network, network-bind] rocketchat-caddy: command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile -host=localhost:8080 daemon: simple plugs: [network, network-bind] + mongo: + command: env LC_ALL=C mongo + plugs: [network] + restoredb: + command: env LC_ALL=C restoredb + plugs: [network] backupdb: command: env LC_ALL=c rcbackup plugs: [network] @@ -48,7 +55,7 @@ parts: lib/node_modules: node_modules rocketchat-server: plugin: dump - after: [mongodb] + after: [node] source: https://rocket.chat/releases/develop/download source-type: tar stage-packages: @@ -59,22 +66,15 @@ parts: - .node_version.txt - usr - lib - snap: - - programs - - main.js - - .node_version.txt - - usr - - lib mongodb: - source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz + build-packages: + - wget + source: ./ + prepare: ./resources/preparemongo plugin: dump stage-packages: - libssl1.0.0 - stage: - - usr - - bin - - lib - snap: + prime: - usr - bin - lib @@ -83,18 +83,20 @@ parts: source: resources/ organize: rcbackup: bin/rcbackup + restoredb: bin/restoredb startmongo: bin/startmongo - initmongo: bin/initmongo + startRocketChat: bin/startRocketChat + initreplset.js: bin/initreplset.js Caddyfile: bin/Caddyfile initcaddy: bin/initcaddy - initmongoreplset.js: bin/initmongoreplset.js - snap: + prime: - bin caddy: - plugin: go - go-importpath: github.com/mholt/caddy - source: https://github.com/mholt/caddy - source-type: git - source-commit: 53e117802fedd5915eeb32907873d8786a4b2936 - snap: - - bin/caddy + prepare: ./resources/preparecaddy + plugin: dump + source: ./ + prime: + - bin + organize: + caddy: bin/caddy + after: [mongodb] diff --git a/.snapcraft/resources/initmongo b/.snapcraft/resources/initmongo deleted file mode 100755 index 176f208ba80..00000000000 --- a/.snapcraft/resources/initmongo +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -echo "initializing replset if necessary... in 60 seconds" -sleep 60 -echo "after 60 seconds, checking for replset..." -mongo $SNAP/bin/initmongoreplset.js - diff --git a/.snapcraft/resources/initmongoreplset.js b/.snapcraft/resources/initmongoreplset.js deleted file mode 100644 index 5ef3b0e0caa..00000000000 --- a/.snapcraft/resources/initmongoreplset.js +++ /dev/null @@ -1,8 +0,0 @@ -var ism = db.isMaster(); -printjson(ism); -if (ism.ismaster) { - } else -{ - var msg = rs.initiate(); - printjson(msg); -} diff --git a/.snapcraft/resources/initreplset.js b/.snapcraft/resources/initreplset.js new file mode 100644 index 00000000000..6883e248ebd --- /dev/null +++ b/.snapcraft/resources/initreplset.js @@ -0,0 +1,13 @@ +var ism = db.isMaster(); +if (!ism.ismaster) { + rs.initiate( + { + _id: 'rs0', + members: [ + { + _id: 0, + host: 'localhost:27017' + } + ] + }); +} diff --git a/.snapcraft/resources/preparecaddy b/.snapcraft/resources/preparecaddy new file mode 100755 index 00000000000..86d530554c0 --- /dev/null +++ b/.snapcraft/resources/preparecaddy @@ -0,0 +1,36 @@ +#! /bin/bash + +caddy_bin="caddy" +caddy_dl_ext=".tar.gz" + +# NOTE: `uname -m` is more accurate and universal than `arch` +# See https://en.wikipedia.org/wiki/Uname +unamem="$(uname -m)" +if [[ $unamem == *aarch64* ]]; then + caddy_arch="arm64" +elif [[ $unamem == *64* ]]; then + caddy_arch="amd64" +elif [[ $unamem == *86* ]]; then + caddy_arch="386" +elif [[ $unamem == *armv5* ]]; then + caddy_arch="arm" + caddy_arm="5" +elif [[ $unamem == *armv6l* ]]; then + caddy_arch="arm" + caddy_arm="6" +elif [[ $unamem == *armv7l* ]]; then + caddy_arch="arm" + caddy_arm="7" +else + echo "Aborted, unsupported or unknown architecture: $unamem" + return 2 +fi + +echo "Downloading Caddy for $caddy_os/$caddy_arch$caddy_arm..." +caddy_file="caddy_linux_$caddy_arch${caddy_arm}_custom$caddy_dl_ext" +caddy_url="https://caddyserver.com/download/linux/$caddy_arch$caddy_arm?plugins=$caddy_plugins" +echo "$caddy_url" + +wget --quiet "$caddy_url" -O "$caddy_file" +tar -xzf $caddy_file -C . "$caddy_bin" +chmod +x $caddy_bin diff --git a/.snapcraft/resources/preparemongo b/.snapcraft/resources/preparemongo new file mode 100755 index 00000000000..332dd7d4684 --- /dev/null +++ b/.snapcraft/resources/preparemongo @@ -0,0 +1,22 @@ +#! /bin/bash + +if [[ $(uname -m) == "x86_64" ]] +then + wget --backups=0 "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz" + tar -zxf ./mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz --strip-components=1 +else + IFS=" " read -a links <<< $(apt-get -y --print-uris install mongodb | egrep -o "https?://[^']+") + for link in ${links[@]} + do + wget --backups=0 ${link} + done + + IFS=" " read -a deb_pkgs <<< $(ls ./ | egrep "\.deb") + for pkg in ${deb_pkgs[@]} + do + echo "Extracting ${pkg}..." + dpkg-deb -R ${pkg} ./ + done + + mv usr/bin bin +fi diff --git a/.snapcraft/resources/restoredb b/.snapcraft/resources/restoredb new file mode 100755 index 00000000000..0a204e83770 --- /dev/null +++ b/.snapcraft/resources/restoredb @@ -0,0 +1,72 @@ +#! /bin/bash + +if [[ ${EUID} != 0 ]] +then + echo "[-] This task must be run with 'sudo'." + exit +fi + +backup_file=${1} +if [[ ! -f ${backup_file} ]] +then + echo "[-] Usage: snap run rocketchat-server.restoredb ${SNAP_COMMON}/backup_file.tgz" + exit +fi + +cd ${backup_file%/*} +if [[ -z $(pwd | grep "${SNAP_COMMON}") ]] +then + echo "[-] Backup file must be within ${SNAP_COMMON}." + exit +fi + +function ask_backup { + echo -n "\ +*** ATTENTION *** +* Your current database WILL BE DROPPED prior to the restore! +* Would you like to make a backup of the current database before proceeding? +* (y/n/Q)> " + + read choice + [[ "${choice,,}" = n* ]] && return + [[ "${choice,,}" = y* ]] && backupdb && return + exit +} + +function warn { + echo "[!] ${1}" + echo "[*] Check ${restore_dir}/${log_name} for details." +} + +function abort { + echo "[!] ${1}" + echo "[*] Check ${restore_dir}/${log_name} for details." + echo "[-] Restore aborted!" + exit +} + +mongo parties --eval "db.getCollectionNames()" | grep "\[ \]" >> /dev/null || ask_backup +echo "[*] Extracting backup file..." +restore_dir="${SNAP_COMMON}/restore" +log_name="extraction.log" +mkdir -p ${restore_dir} +cd ${restore_dir} +tar --no-same-owner --overwrite -zxvf ${backup_file} &> "${restore_dir}/${log_name}" +[[ $? != 0 ]] && abort "Failed to extract backup files to ${restore_dir}!" +echo "[*] Restoring data..." +data_dir=$(tail "${restore_dir}/${log_name}" | grep parties/. | head -n 1) +[[ -z ${data_dir} ]] && abort "Restore data not found within ${backup_file}! + Please check that your backup file contains the backup data within the \"parties\" directory." +data_dir=$(dirname ${data_dir}) +log_name="mongorestore.log" +mongorestore --db parties --noIndexRestore --drop ${data_dir} &> "${restore_dir}/${log_name}" +[[ $? != 0 ]] && abort "Failed to execute mongorestore from ${data_dir}!" +# If mongorestore.log only has a few lines, it likely didn't find the dump files +log_lines=$(wc -l < "${restore_dir}/${log_name}") +[[ ${log_lines} -lt 24 ]] && warn "Little or no restore data found within ${backup_file}! + Please check that your backup file contains all the backup data within the \"parties\" directory." +echo "[*] Preparing database..." +log_name="mongoprepare.log" +mongo parties --eval "db.repairDatabase()" --verbose &> "${restore_dir}/${log_name}" +[[ $? != 0 ]] && abort "Failed to prepare database for usage!" +echo "[+] Restore completed! Please restart the snap.rocketchat services to verify." diff --git a/.snapcraft/resources/startRocketChat b/.snapcraft/resources/startRocketChat new file mode 100755 index 00000000000..9963948d50b --- /dev/null +++ b/.snapcraft/resources/startRocketChat @@ -0,0 +1,43 @@ +#!/bin/bash + +function start_rocketchat { + echo "Checking if oplog has been enabled, and enabling if not" + LC_ALL=C mongo $SNAP/bin/initreplset.js + + export DEPLOY_METHOD=snap + export NODE_ENV=production + export BABEL_CACHE_DIR=/tmp + export ROOT_URL=http://localhost + export PORT=3000 + export MONGO_URL=mongodb://localhost:27017/parties + export MONGO_OPLOG_URL=mongodb://localhost:27017/local + export Accounts_AvatarStorePath=$SNAP_COMMON/uploads + + node $SNAP/main.js +} + +sleep_time=5 +try_times=0 + +function try_start { + + search=$(ps --pid $(cat $SNAP_COMMON/mongod.pid) -o comm=) + + if [ $search ] + then + start_rocketchat + else + if [[ "$try_times" == 5 || "$try_times" > 5 ]]; then + echo "Was unable to connect to Mongo. Please make sure Mongo has started successfully: sudo systemctl status snap.rocketchat-server.rocketchat-mongo to view logs: sudo journalctl -u snap.rocketchat-server.rocketchat-mongo" + exit 1; + fi + + ((try_times += 1)) + ((sleep_time += 5)) + echo "Mongo is not available, can't start. Waiting ${sleep_time} seconds and trying again" + sleep $sleep_time + try_start + fi +} + +try_start diff --git a/.snapcraft/resources/startmongo b/.snapcraft/resources/startmongo index 9df9df32560..5697c6df6db 100755 --- a/.snapcraft/resources/startmongo +++ b/.snapcraft/resources/startmongo @@ -1,7 +1 @@ -#!/bin/sh -echo "initializing replset backgrounded..." -$SNAP/bin/initmongo & - -echo "Starting mongodb server in replicaset standalone mode..." -mongod --bind_ip 127.0.0.1 --smallfiles --dbpath=$SNAP_COMMON --journal --replSet rcreplset - +env LC_ALL=C mongod --bind_ip 127.0.0.1 --pidfilepath $SNAP_COMMON/mongod.pid --smallfiles --journal --dbpath=$SNAP_COMMON --replSet rs0 diff --git a/.snapcraft/stable/snapcraft.yaml b/.snapcraft/stable/snapcraft.yaml index 96d3b44d9a2..e548ef967a8 100644 --- a/.snapcraft/stable/snapcraft.yaml +++ b/.snapcraft/stable/snapcraft.yaml @@ -7,23 +7,30 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 0.55.0-develop +version: #{RC_VERSION} summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict +assumes: [snapd2.21] 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 + command: startRocketChat daemon: simple plugs: [network, network-bind] rocketchat-mongo: - command: env LC_ALL=C mongod --bind_ip 127.0.0.1 --smallfiles --dbpath=$SNAP_COMMON + command: startmongo daemon: simple plugs: [network, network-bind] rocketchat-caddy: command: env LC_ALL=C caddy -conf=$SNAP_DATA/Caddyfile -host=localhost:8080 daemon: simple plugs: [network, network-bind] + mongo: + command: env LC_ALL=C mongo + plugs: [network] + restoredb: + command: env LC_ALL=C restoredb + plugs: [network] backupdb: command: env LC_ALL=c rcbackup plugs: [network] @@ -48,7 +55,7 @@ parts: lib/node_modules: node_modules rocketchat-server: plugin: dump - after: [mongodb] + after: [node] source: https://rocket.chat/releases/latest/download source-type: tar stage-packages: @@ -59,22 +66,15 @@ parts: - .node_version.txt - usr - lib - snap: - - programs - - main.js - - .node_version.txt - - usr - - lib mongodb: - source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.7.tgz + build-packages: + - wget + source: ./ + prepare: ./resources/preparemongo plugin: dump stage-packages: - libssl1.0.0 - stage: - - usr - - bin - - lib - snap: + prime: - usr - bin - lib @@ -83,18 +83,20 @@ parts: source: resources/ organize: rcbackup: bin/rcbackup + restoredb: bin/restoredb startmongo: bin/startmongo - initmongo: bin/initmongo + startRocketChat: bin/startRocketChat + initreplset.js: bin/initreplset.js Caddyfile: bin/Caddyfile initcaddy: bin/initcaddy - initmongoreplset.js: bin/initmongoreplset.js - snap: + prime: - bin caddy: - plugin: go - go-importpath: github.com/mholt/caddy - source: https://github.com/mholt/caddy - source-type: git - source-commit: 53e117802fedd5915eeb32907873d8786a4b2936 - snap: - - bin/caddy + prepare: ./resources/preparecaddy + plugin: dump + source: ./ + prime: + - bin + organize: + caddy: bin/caddy + after: [mongodb] diff --git a/.stylelintignore b/.stylelintignore index 5baac3a35e9..e69de29bb2d 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,2 +0,0 @@ -**/lesshat.less -**/_lesshat.import.less \ No newline at end of file diff --git a/.stylelintrc b/.stylelintrc index 1015f7d91ee..2bbb45133d8 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -107,6 +107,5 @@ "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 44618140f36..09bfc03484b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ services: branches: only: - develop - - experimental - - "/^(\\d+\\.)?(\\d+\\.)?(\\*|\\d+)$/" + - release-candidate + - "/^\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$/" git: depth: 1 node_js: diff --git a/.travis/snap.sh b/.travis/snap.sh index 10e8ec20be5..d5ad25ba5c6 100755 --- a/.travis/snap.sh +++ b/.travis/snap.sh @@ -9,11 +9,15 @@ git config user.name "CI Bot" git config user.email "rocketchat.buildmaster@git.launchpad.net" # Determine the channel to push snap to. -if [[ $TRAVIS_TAG ]] - then +if [[ $TRAVIS_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+ ]]; then + CHANNEL=candidate + RC_VERSION=$TRAVIS_TAG +elif [[ $TRAVIS_TAG ]]; then CHANNEL=stable + RC_VERSION=$TRAVIS_TAG else CHANNEL=edge + RC_VERSION=0.56.0-develop fi echo "Preparing to trigger a snap release for $CHANNEL channel" @@ -33,7 +37,9 @@ echo "Tag: $TRAVIS_TAG \r\nBranch: $TRAVIS_BRANCH\r\nBuild: $TRAVIS_BUILD_NUMBER GIT_SSH_COMMAND="ssh -i launchpadkey" git clone -b $CHANNEL git+ssh://rocket.chat.buildmaster@git.launchpad.net/rocket.chat launchpad # Rarely will change, but just incase we copy it all -cp -r resources $CHANNEL/snapcraft.yaml buildinfo launchpad/ +cp -r resources buildinfo launchpad/ +sed s/#{RC_VERSION}/$RC_VERSION/ $CHANNEL/snapcraft.yaml > launchpad/snapcraft.yaml + cd launchpad git add resources snapcraft.yaml buildinfo diff --git a/HISTORY.md b/HISTORY.md index 1d3d8139d3d..a286b0d5590 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,14 +1,211 @@ -# History + +# 0.56.0-develop (2017-04-18) -## NEXT -- [NEW] Permission `join-without-join-code` assigned to admins and bots by default (#6139) -- [NEW] Integrations, both incoming and outgoing, now have access to the models. Example: `Users.findOneById(id)` (#6336) -- [NEW] Option to enable `Two Factor Authentication` in user's account preference -- [FIX] Incoming integrations would break when trying to use the `Store` feature. -- [FIX] Outgoing webhooks which have an error and they're retrying would still retry even if the integration was disabled. (#4835) -- [FIX] Removed Deprecated Package rocketchat:sharedsecret. -- [BREAK] `getUsersOfRoom` API to return array of objects with user and username, instead of array of strings +### New Features + +- [#6615](https://github.com/RocketChat/Rocket.Chat/pull/6615) Add a setting to not run outgoing integrations on message edits +- [#6692](https://github.com/RocketChat/Rocket.Chat/pull/6692) Use tokenSentVia parameter for clientid/secret to token endpoint + + +### Bug Fixes + +- [#6709](https://github.com/RocketChat/Rocket.Chat/pull/6709) emoji picker exception +- [#6709](https://github.com/RocketChat/Rocket.Chat/pull/6709) emoji picker exception +- [#6704](https://github.com/RocketChat/Rocket.Chat/pull/6704) Fix message types + + +
+Others + +- [#6703](https://github.com/RocketChat/Rocket.Chat/pull/6703) LingoHub based on develop +- [#6706](https://github.com/RocketChat/Rocket.Chat/pull/6706) meteor update to 1.4.4 +
+ + + + +# 0.55.0 (2017-04-18) + + +### Bug Fixes + +- [#6709](https://github.com/RocketChat/Rocket.Chat/pull/6709) emoji picker exception + + + + +# 0.55.0-rc.6 (2017-04-17) + + +### Bug Fixes + +- [#6704](https://github.com/RocketChat/Rocket.Chat/pull/6704) Fix message types + + + + +# 0.55.0-rc.5 (2017-04-13) + + +### Bug Fixes + +- [#6684](https://github.com/RocketChat/Rocket.Chat/pull/6684) Allow question on OAuth token path +- [#6683](https://github.com/RocketChat/Rocket.Chat/pull/6683) Error when returning undefined from incoming intergation’s script +- [#6686](https://github.com/RocketChat/Rocket.Chat/pull/6686) Update server cache indexes on record updates + + + + +# 0.55.0-rc.4 (2017-04-13) + + +### New Features + +- [#6681](https://github.com/RocketChat/Rocket.Chat/pull/6681) Expose Livechat to Incoming Integrations and allow response + + +### Bug Fixes + +- [#6659](https://github.com/RocketChat/Rocket.Chat/pull/6659) Administrators being rate limited when editing users data +- [#6680](https://github.com/RocketChat/Rocket.Chat/pull/6680) Downgrade email package to from 1.2.0 to 1.1.18 +- [#6682](https://github.com/RocketChat/Rocket.Chat/pull/6682) Fix Logger stdout publication + + + + +# 0.55.0-rc.3 (2017-04-11) + + +### Bug Fixes + +- [#6658](https://github.com/RocketChat/Rocket.Chat/pull/6658) Revert unwanted UI changes + + + + +# 0.55.0-rc.2 (2017-04-10) + + +### New Features + +- [#6634](https://github.com/RocketChat/Rocket.Chat/pull/6634) Add monitoring package +- [#6632](https://github.com/RocketChat/Rocket.Chat/pull/6632) Drupal oAuth Integration for Rocketchat + + +### Bug Fixes + +- [#6648](https://github.com/RocketChat/Rocket.Chat/pull/6648) Do not escaping markdown on message attachments +- [#6651](https://github.com/RocketChat/Rocket.Chat/pull/6651) Encode avatar url to prevent CSS injection +- [#6650](https://github.com/RocketChat/Rocket.Chat/pull/6650) Improve markdown code + + +
+Others + +- [#6649](https://github.com/RocketChat/Rocket.Chat/pull/6649) Added Deploy method and platform to stats +- [#6647](https://github.com/RocketChat/Rocket.Chat/pull/6647) LingoHub based on develop +- [#6631](https://github.com/RocketChat/Rocket.Chat/pull/6631) meteor update +
+ + + + +# 0.55.0-rc.1 (2017-04-07) + + +### New Features + +- [#6616](https://github.com/RocketChat/Rocket.Chat/pull/6616) 'users.resetAvatar' rest api endpoint + + +### Bug Fixes + +- [#6617](https://github.com/RocketChat/Rocket.Chat/pull/6617) arguments logger +- [#6620](https://github.com/RocketChat/Rocket.Chat/pull/6620) Incorrect curl command being generated on incoming integrations + + + + +# 0.55.0-rc.0 (2017-04-06) + + +### BREACKING CHANGES + - :hand: `getUsersOfRoom` API to return array of objects with user and username, instead of array of strings + + +### New Features +- :hand: Permission `join-without-join-code` assigned to admins and bots by default (#6139) +- :hand: Integrations, both incoming and outgoing, now have access to the models. Example: `Users.findOneById(id)` (#6336) + +- [#6565](https://github.com/RocketChat/Rocket.Chat/pull/6565) Add shield.svg api route to generate custom shields/badges +- [#6554](https://github.com/RocketChat/Rocket.Chat/pull/6554) Added oauth2 userinfo endpoint +- [#6577](https://github.com/RocketChat/Rocket.Chat/pull/6577) resolve merge share function +- [#6608](https://github.com/RocketChat/Rocket.Chat/pull/6608) Switch Snaps to use oplog +- [#6476](https://github.com/RocketChat/Rocket.Chat/pull/6476) Two Factor Auth + + +### Bug Fixes +- :hand: Incoming integrations would break when trying to use the `Store` feature. +- :hand: Outgoing webhooks which have an error and they're retrying would still retry even if the integration was disabled. (#4835) +- :hand: Removed Deprecated Package rocketchat:sharedsecret. + +- [#6590](https://github.com/RocketChat/Rocket.Chat/pull/6590) Accounts from LinkedIn OAuth without name +- [#6531](https://github.com/RocketChat/Rocket.Chat/pull/6531) can not get access_token when using custom oauth +- [#6594](https://github.com/RocketChat/Rocket.Chat/pull/6594) Do not add default roles for users without services field +- [#6598](https://github.com/RocketChat/Rocket.Chat/pull/6598) Large files crashed browser when trying to show preview +- [#6600](https://github.com/RocketChat/Rocket.Chat/pull/6600) messageBox: put "joinCodeRequired" back +- [#6575](https://github.com/RocketChat/Rocket.Chat/pull/6575) Usage of subtagged languages +- [#6562](https://github.com/RocketChat/Rocket.Chat/pull/6562) UTC offset missing UTC text when positive + + +
+Others + +- [#6597](https://github.com/RocketChat/Rocket.Chat/pull/6597) Add `fname` to subscriptions in memory +- [#6614](https://github.com/RocketChat/Rocket.Chat/pull/6614) Add candidate snap channel +- [#6458](https://github.com/RocketChat/Rocket.Chat/pull/6458) Add ESLint rule `one-var` +- [#6280](https://github.com/RocketChat/Rocket.Chat/pull/6280) Clipboard [Firefox version < 50] +- [#6503](https://github.com/RocketChat/Rocket.Chat/pull/6503) Convert File Package to js +- [#6471](https://github.com/RocketChat/Rocket.Chat/pull/6471) convert mapview package to js +- [#6576](https://github.com/RocketChat/Rocket.Chat/pull/6576) Convert Message Pin Package to JS +- [#6539](https://github.com/RocketChat/Rocket.Chat/pull/6539) convert rocketchat-ui part 2 +- [#6446](https://github.com/RocketChat/Rocket.Chat/pull/6446) Convert Tutum Package to JS +- [#6561](https://github.com/RocketChat/Rocket.Chat/pull/6561) Convert Ui-Login Package to Js +- [#6498](https://github.com/RocketChat/Rocket.Chat/pull/6498) Convert Ui-Master Package to Js +- [#6473](https://github.com/RocketChat/Rocket.Chat/pull/6473) Convert ui-vrecord Package to JS +- [#6494](https://github.com/RocketChat/Rocket.Chat/pull/6494) Convert Version Package to JS +- [#6499](https://github.com/RocketChat/Rocket.Chat/pull/6499) Convert Wordpress Package to js +- [#6496](https://github.com/RocketChat/Rocket.Chat/pull/6496) converted getAvatarUrlFromUsername +- [#6500](https://github.com/RocketChat/Rocket.Chat/pull/6500) converted messageAttachment coffee to js +- [#6467](https://github.com/RocketChat/Rocket.Chat/pull/6467) converted rocketchat-mentions coffee to js +- [#6497](https://github.com/RocketChat/Rocket.Chat/pull/6497) converted slashcommand-invite coffee to js +- [#6469](https://github.com/RocketChat/Rocket.Chat/pull/6469) converted slashcommand-join coffee to js +- [#6470](https://github.com/RocketChat/Rocket.Chat/pull/6470) converted slashcommand-leave coffee to js +- [#6468](https://github.com/RocketChat/Rocket.Chat/pull/6468) converted slashcommand-me coffee to js +- [#6501](https://github.com/RocketChat/Rocket.Chat/pull/6501) converted slashcommand-msg coffee to js +- [#6474](https://github.com/RocketChat/Rocket.Chat/pull/6474) converted slashcommands-mute coffee to js +- [#6505](https://github.com/RocketChat/Rocket.Chat/pull/6505) Create groups.addAll endpoint and add activeUsersOnly param. +- [#6584](https://github.com/RocketChat/Rocket.Chat/pull/6584) dependencies upgrade +- [#6479](https://github.com/RocketChat/Rocket.Chat/pull/6479) ESLint add rule `no-void` +- [#6591](https://github.com/RocketChat/Rocket.Chat/pull/6591) Fix recently introduced bug: OnePassword not defined +- [#6574](https://github.com/RocketChat/Rocket.Chat/pull/6574) LingoHub based on develop +- [#6567](https://github.com/RocketChat/Rocket.Chat/pull/6567) LingoHub based on develop +- [#6585](https://github.com/RocketChat/Rocket.Chat/pull/6585) Move room display name logic to roomType definition +- [#6571](https://github.com/RocketChat/Rocket.Chat/pull/6571) Move wordpress packages client files to client folder +- [#6351](https://github.com/RocketChat/Rocket.Chat/pull/6351) New feature: Room announcement +- [#6596](https://github.com/RocketChat/Rocket.Chat/pull/6596) Only configure LoggerManager on server +- [#6298](https://github.com/RocketChat/Rocket.Chat/pull/6298) POC Google Natural Language integration +- [#6543](https://github.com/RocketChat/Rocket.Chat/pull/6543) Remove coffeescript package from ui-flextab +- [#6542](https://github.com/RocketChat/Rocket.Chat/pull/6542) Remove coffeescript package from ui-sidenav +- [#6540](https://github.com/RocketChat/Rocket.Chat/pull/6540) Remove Deprecated Shared Secret Package +- [#6551](https://github.com/RocketChat/Rocket.Chat/pull/6551) rocketchat-channel-settings coffee to js +- [#6541](https://github.com/RocketChat/Rocket.Chat/pull/6541) rocketchat-channel-settings-mail-messages coffee to js +- [#6553](https://github.com/RocketChat/Rocket.Chat/pull/6553) rocketchat-lib part1 +- [#6504](https://github.com/RocketChat/Rocket.Chat/pull/6504) rocketchat-ui coffee to js part1 +- [#3851](https://github.com/RocketChat/Rocket.Chat/pull/3851) Use real name instead of username for messages and direct messages list +
+ ## 0.54.2 - 2017-Mar-24 diff --git a/README.md b/README.md index 8bca50b140a..1ef374f3383 100644 --- a/README.md +++ b/README.md @@ -417,11 +417,15 @@ Thanks to our core team [Sing Li](https://github.com/Sing-Li), and to hundreds of awesome [contributors](https://github.com/RocketChat/Rocket.Chat/graphs/contributors). +![Emoji One](https://cloud.githubusercontent.com/assets/1986378/24772858/47290a70-1ae9-11e7-9a5a-2913d0002c94.png) + Emoji provided free by [Emoji One](http://emojione.com) -Performance monitoring provided by [Kadira](https://kadira.io) +![BrowserStack](https://cloud.githubusercontent.com/assets/1986378/24772879/57d57b88-1ae9-11e7-98b4-4af824b47933.png) + +Testing with [BrowserStack](https://www.browserstack.com) + -Hosting powered by [Rackspace](https://rackspace.com) # Donate diff --git a/app.json b/app.json index e4d3a3c577c..1fe42a0e97e 100644 --- a/app.json +++ b/app.json @@ -6,11 +6,13 @@ "keywords": ["meteor", "social", "community", "chat"], "website": "https://rocket.chat", "env": { + "NODE_ENV": "production", "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 - } + }, + "DEPLOY_PLATFORM": "heroku" }, "addons": [ "mongolab", diff --git a/client/notifications/UsersNameChanged.js b/client/notifications/UsersNameChanged.js new file mode 100644 index 00000000000..77d153ba708 --- /dev/null +++ b/client/notifications/UsersNameChanged.js @@ -0,0 +1,22 @@ +Meteor.startup(function() { + RocketChat.Notifications.onLogged('Users:NameChanged', function({_id, name, username}) { + RocketChat.models.Messages.update({ + 'u._id': _id + }, { + $set: { + 'u.name': name + } + }, { + multi: true + }); + + RocketChat.models.Subscriptions.update({ + name: username, + t: 'd' + }, { + $set: { + fname: name + } + }); + }); +}); diff --git a/client/routes/router.js b/client/routes/router.js index 9d5c7e6eb9d..f70a5ff6eba 100644 --- a/client/routes/router.js +++ b/client/routes/router.js @@ -27,7 +27,7 @@ FlowRouter.route('/', { Tracker.autorun(function(c) { if (FlowRouter.subsReady() === true) { Meteor.defer(function() { - if (Meteor.user().defaultRoom) { + if (Meteor.user() && Meteor.user().defaultRoom) { const room = Meteor.user().defaultRoom.split('/'); FlowRouter.go(room[0], { name: room[1] }, FlowRouter.current().queryParams); } else { diff --git a/client/startup/unread.js b/client/startup/unread.js index d624f08c0ed..634075c748d 100644 --- a/client/startup/unread.js +++ b/client/startup/unread.js @@ -5,7 +5,7 @@ Meteor.startup(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 } }); + const subscriptions = ChatSubscription.find({open: true, hideUnreadStatus: { $ne: true }}, { fields: { unread: 1, alert: 1, rid: 1, t: 1, name: 1, ls: 1, unreadAlert: 1 } }); let openedRoomId = undefined; Tracker.nonreactive(function() { diff --git a/docker-compose.yml b/docker-compose.yml index 44c6f8384c0..d1cfa596080 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,8 @@ services: - 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 +# - HTTP_PROXY=http://proxy.domain.com +# - HTTPS_PROXY=http://proxy.domain.com depends_on: - mongo ports: diff --git a/package.json b/package.json index 3ba2d7eb525..5238246908b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "0.55.0-develop", + "version": "0.56.0-develop", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" @@ -42,7 +42,9 @@ "deploy": "npm run build && pm2 startOrRestart pm2.json", "chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests/end-to-end", "chimp-test": "chimp tests/chimp-config.js", - "postinstall": "cd packages/rocketchat-katex && npm i" + "postinstall": "cd packages/rocketchat-katex && npm i", + "version": "node .scripts/version.js", + "release": "npm run version && conventional-changelog --config .github/changelog.js -i HISTORY.md -s" }, "license": "MIT", "repository": { @@ -54,24 +56,25 @@ "email": "support@rocket.chat" }, "devDependencies": { - "chimp": "^0.47.2", + "chimp": "^0.48.0", "eslint": "^3.19.0", "stylelint": "^7.10.1", - "supertest": "^3.0.0" + "supertest": "^3.0.0", + "conventional-changelog": "^1.1.3" }, "dependencies": { "babel-runtime": "^6.23.0", "bcrypt": "^1.0.2", - "codemirror": "^5.25.0", - "file-type": "^4.1.0", - "highlight.js": "^9.10.0", + "codemirror": "^5.25.2", + "file-type": "^4.2.0", + "highlight.js": "^9.11.0", "jquery": "^3.2.1", "mime-db": "^1.27.0", "mime-type": "^3.0.4", "moment": "^2.18.1", - "moment-timezone": "^0.5.12", - "photoswipe": "^4.1.1", - "prom-client": "^8.0.0", + "moment-timezone": "^0.5.13", + "photoswipe": "^4.1.2", + "prom-client": "^8.1.1", "semver": "^5.3.0", "toastr": "^2.1.2" } diff --git a/packages/rocketchat-api/package.js b/packages/rocketchat-api/package.js index 6918b5f1da8..ef8aaf15932 100644 --- a/packages/rocketchat-api/package.js +++ b/packages/rocketchat-api/package.js @@ -19,6 +19,7 @@ Package.onUse(function(api) { //Register v1 helpers api.addFiles('server/v1/helpers/getPaginationItems.js', 'server'); api.addFiles('server/v1/helpers/getUserFromParams.js', 'server'); + api.addFiles('server/v1/helpers/isUserFromParams.js', 'server'); api.addFiles('server/v1/helpers/parseJsonQuery.js', 'server'); api.addFiles('server/v1/helpers/getLoggedInUser.js', 'server'); diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js index e145a84f0e1..1de2d82d724 100644 --- a/packages/rocketchat-api/server/v1/channels.js +++ b/packages/rocketchat-api/server/v1/channels.js @@ -1,10 +1,15 @@ //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'); +function findChannelByIdOrName({ roomId, roomName, checkedArchived = true }) { + if ((!roomId || !roomId.trim()) && (!roomName || !roomName.trim())) { + throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" or "roomName" is required'); } - const room = RocketChat.models.Rooms.findOneById(roomId, { fields: RocketChat.API.v1.defaultFieldsToExclude }); + let room; + if (roomId) { + room = RocketChat.models.Rooms.findOneById(roomId, { fields: RocketChat.API.v1.defaultFieldsToExclude }); + } else if (roomName) { + room = RocketChat.models.Rooms.findOneByName(roomName, { 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 }`); @@ -19,7 +24,7 @@ function findChannelById({ roomId, checkedArchived = true }) { RocketChat.API.v1.addRoute('channels.addAll', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); Meteor.runAsUser(this.userId, () => { Meteor.call('addAllUserToRoom', findResult._id, this.bodyParams.activeUsersOnly); @@ -33,7 +38,7 @@ RocketChat.API.v1.addRoute('channels.addAll', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.addModerator', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); const user = this.getUserFromParams(); @@ -47,7 +52,7 @@ RocketChat.API.v1.addRoute('channels.addModerator', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.addOwner', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); const user = this.getUserFromParams(); @@ -61,7 +66,7 @@ RocketChat.API.v1.addRoute('channels.addOwner', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.archive', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); Meteor.runAsUser(this.userId, () => { Meteor.call('archiveRoom', findResult._id); @@ -73,7 +78,7 @@ RocketChat.API.v1.addRoute('channels.archive', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.cleanHistory', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); if (!this.bodyParams.latest) { return RocketChat.API.v1.failure('Body parameter "latest" is required.'); @@ -101,7 +106,7 @@ RocketChat.API.v1.addRoute('channels.cleanHistory', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.close', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false }); const sub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId); @@ -157,7 +162,7 @@ RocketChat.API.v1.addRoute('channels.create', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.delete', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false }); //The find method returns either with the group or the failur @@ -177,7 +182,7 @@ RocketChat.API.v1.addRoute('channels.getIntegrations', { authRequired: true }, { return RocketChat.API.v1.unauthorized(); } - const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.queryParams.roomId, checkedArchived: false }); let includeAllPublicChannels = true; if (typeof this.queryParams.includeAllPublicChannels !== 'undefined') { @@ -217,7 +222,7 @@ RocketChat.API.v1.addRoute('channels.getIntegrations', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.history', { authRequired: true }, { get() { - const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.queryParams.roomId, checkedArchived: false }); let latestDate = new Date(); if (this.queryParams.latest) { @@ -257,7 +262,7 @@ RocketChat.API.v1.addRoute('channels.history', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.info', { authRequired: true }, { get() { - const findResult = findChannelById({ roomId: this.queryParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.queryParams.roomId, roomName: this.queryParams.roomName, checkedArchived: false }); return RocketChat.API.v1.success({ channel: RocketChat.models.Rooms.findOneById(findResult._id, { fields: RocketChat.API.v1.defaultFieldsToExclude }) @@ -267,7 +272,7 @@ RocketChat.API.v1.addRoute('channels.info', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.invite', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); const user = this.getUserFromParams(); @@ -283,7 +288,7 @@ RocketChat.API.v1.addRoute('channels.invite', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.join', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); Meteor.runAsUser(this.userId, () => { Meteor.call('joinRoom', findResult._id, this.bodyParams.joinCode); @@ -297,7 +302,7 @@ RocketChat.API.v1.addRoute('channels.join', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.kick', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); const user = this.getUserFromParams(); @@ -313,7 +318,7 @@ RocketChat.API.v1.addRoute('channels.kick', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.leave', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); Meteor.runAsUser(this.userId, () => { Meteor.call('leaveRoom', findResult._id); @@ -409,7 +414,7 @@ RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.open', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false }); const sub = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId); @@ -431,7 +436,7 @@ RocketChat.API.v1.addRoute('channels.open', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.removeModerator', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); const user = this.getUserFromParams(); @@ -445,7 +450,7 @@ RocketChat.API.v1.addRoute('channels.removeModerator', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.removeOwner', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); const user = this.getUserFromParams(); @@ -463,7 +468,7 @@ RocketChat.API.v1.addRoute('channels.rename', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "name" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ 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.'); @@ -485,7 +490,7 @@ RocketChat.API.v1.addRoute('channels.setDescription', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "description" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ 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.'); @@ -507,7 +512,7 @@ RocketChat.API.v1.addRoute('channels.setJoinCode', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "joinCode" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId }); Meteor.runAsUser(this.userId, () => { Meteor.call('saveRoomSettings', findResult._id, 'joinCode', this.bodyParams.joinCode); @@ -525,7 +530,7 @@ RocketChat.API.v1.addRoute('channels.setPurpose', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "purpose" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ 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.'); @@ -547,7 +552,7 @@ RocketChat.API.v1.addRoute('channels.setReadOnly', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "readOnly" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ 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.'); @@ -569,7 +574,7 @@ RocketChat.API.v1.addRoute('channels.setTopic', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "topic" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ 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.'); @@ -591,7 +596,7 @@ RocketChat.API.v1.addRoute('channels.setType', { authRequired: true }, { return RocketChat.API.v1.failure('The bodyParam "type" is required'); } - const findResult = findChannelById({ roomId: this.bodyParams.roomId }); + const findResult = findChannelByIdOrName({ 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.'); @@ -609,7 +614,7 @@ RocketChat.API.v1.addRoute('channels.setType', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.unarchive', { authRequired: true }, { post() { - const findResult = findChannelById({ roomId: this.bodyParams.roomId, checkedArchived: false }); + const findResult = findChannelByIdOrName({ roomId: this.bodyParams.roomId, checkedArchived: false }); if (!findResult.archived) { return RocketChat.API.v1.failure(`The channel, ${ findResult.name }, is not archived`); diff --git a/packages/rocketchat-api/server/v1/groups.js b/packages/rocketchat-api/server/v1/groups.js index f05538c4d6d..10bef449b9c 100644 --- a/packages/rocketchat-api/server/v1/groups.js +++ b/packages/rocketchat-api/server/v1/groups.js @@ -1,4 +1,4 @@ -//Returns the private group subscription IF found otherwise it will reutrn the failure of why it didn't. Check the `statusCode` property +//Returns the private group subscription IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property function findPrivateGroupByIdOrName({ roomId, roomName, userId, checkedArchived = true }) { if ((!roomId || !roomId.trim()) && (!roomName || !roomName.trim())) { throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" or "roomName" is required'); diff --git a/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js b/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js index 99976b38990..01c075ea0ab 100644 --- a/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js +++ b/packages/rocketchat-api/server/v1/helpers/getUserFromParams.js @@ -1,4 +1,4 @@ -//Convience method, almost need to turn it into a middleware of sorts +//Convenience 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; diff --git a/packages/rocketchat-api/server/v1/helpers/isUserFromParams.js b/packages/rocketchat-api/server/v1/helpers/isUserFromParams.js new file mode 100644 index 00000000000..f0b24a78096 --- /dev/null +++ b/packages/rocketchat-api/server/v1/helpers/isUserFromParams.js @@ -0,0 +1,5 @@ +RocketChat.API.v1.helperMethods.set('isUserFromParams', function _isUserFromParams() { + return (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); +}); diff --git a/packages/rocketchat-api/server/v1/integrations.js b/packages/rocketchat-api/server/v1/integrations.js index de848737ba8..bd82fc250c5 100644 --- a/packages/rocketchat-api/server/v1/integrations.js +++ b/packages/rocketchat-api/server/v1/integrations.js @@ -5,9 +5,9 @@ RocketChat.API.v1.addRoute('integrations.create', { authRequired: true }, { name: String, enabled: Boolean, username: String, - urls: [String], + urls: Match.Maybe([String]), channel: String, - event: String, + event: Match.Maybe(String), triggerWords: Match.Maybe([String]), alias: Match.Maybe(String), avatar: Match.Maybe(String), @@ -26,6 +26,11 @@ RocketChat.API.v1.addRoute('integrations.create', { authRequired: true }, { integration = Meteor.call('addOutgoingIntegration', this.bodyParams); }); break; + case 'webhook-incoming': + Meteor.runAsUser(this.userId, () => { + integration = Meteor.call('addIncomingIntegration', this.bodyParams); + }); + break; default: return RocketChat.API.v1.failure('Invalid integration type.'); } @@ -121,6 +126,20 @@ RocketChat.API.v1.addRoute('integrations.remove', { authRequired: true }, { Meteor.call('deleteOutgoingIntegration', integration._id); }); + return RocketChat.API.v1.success({ + integration + }); + case 'webhook-incoming': + 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('deleteIncomingIntegration', integration._id); + }); + return RocketChat.API.v1.success({ integration }); diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js index b1b002afca3..aa66fab610f 100644 --- a/packages/rocketchat-api/server/v1/users.js +++ b/packages/rocketchat-api/server/v1/users.js @@ -67,20 +67,19 @@ RocketChat.API.v1.addRoute('users.getAvatar', { authRequired: false }, { RocketChat.API.v1.addRoute('users.getPresence', { authRequired: true }, { get() { - //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(); - + if (this.isUserFromParams()) { + const user = RocketChat.models.Users.findOneById(this.userId); return RocketChat.API.v1.success({ - presence: user.status + presence: user.status, + connectionStatus: user.statusConnection, + lastLogin: user.lastLogin }); } - const user = RocketChat.models.Users.findOneById(this.userId); + const user = this.getUserFromParams(); + return RocketChat.API.v1.success({ - presence: user.status, - connectionStatus: user.statusConnection, - lastLogin: user.lastLogin + presence: user.status }); } }); @@ -169,17 +168,35 @@ RocketChat.API.v1.addRoute('users.register', { authRequired: false }, { } }); -//TODO: Make this route work with support for usernames +RocketChat.API.v1.addRoute('users.resetAvatar', { authRequired: true }, { + post() { + const user = this.getUserFromParams(); + + if (user._id === this.userId) { + Meteor.runAsUser(this.userId, () => Meteor.call('resetAvatar')); + } else if (RocketChat.authz.hasPermission(this.userId, 'edit-other-user-info')) { + Meteor.runAsUser(user._id, () => Meteor.call('resetAvatar')); + } else { + return RocketChat.API.v1.unauthorized(); + } + + return RocketChat.API.v1.success(); + } +}); + RocketChat.API.v1.addRoute('users.setAvatar', { authRequired: true }, { post() { 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')) { + let user; + if (this.isUserFromParams()) { + user = Meteor.users.findOne(this.userId); + } else if (RocketChat.authz.hasPermission(this.userId, 'edit-other-user-info')) { + user = this.getUserFromParams(); + } else { 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 { @@ -232,7 +249,7 @@ RocketChat.API.v1.addRoute('users.update', { authRequired: true }, { const userData = _.extend({ _id: this.bodyParams.userId }, this.bodyParams.data); - RocketChat.saveUser(this.userId, userData); + Meteor.runAsUser(this.userId, () => RocketChat.saveUser(this.userId, userData)); if (this.bodyParams.data.customFields) { RocketChat.saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields); @@ -247,3 +264,14 @@ RocketChat.API.v1.addRoute('users.update', { authRequired: true }, { return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.bodyParams.userId, { fields: RocketChat.API.v1.defaultFieldsToExclude }) }); } }); + +RocketChat.API.v1.addRoute('users.createToken', { authRequired: true }, { + post() { + const user = this.getUserFromParams(); + let data; + Meteor.runAsUser(this.userId, () => { + data = Meteor.call('createToken', user._id); + }); + return data ? RocketChat.API.v1.success({data}) : RocketChat.API.v1.unauthorized(); + } +}); diff --git a/packages/rocketchat-authorization/client/lib/ChatPermissions.js b/packages/rocketchat-authorization/client/lib/ChatPermissions.js index d5eb40d050a..15646128110 100644 --- a/packages/rocketchat-authorization/client/lib/ChatPermissions.js +++ b/packages/rocketchat-authorization/client/lib/ChatPermissions.js @@ -1,6 +1,8 @@ RocketChat.authz.cachedCollection = new RocketChat.CachedCollection({ name: 'permissions', - eventType: 'onLogged' + eventType: 'onLogged', + userRelated: false }); +RocketChat.authz.cachedCollection.init(); this.ChatPermissions = RocketChat.authz.cachedCollection.collection; diff --git a/packages/rocketchat-authorization/server/functions/canAccessRoom.js b/packages/rocketchat-authorization/server/functions/canAccessRoom.js index 5e41811e28f..82a6b761c1b 100644 --- a/packages/rocketchat-authorization/server/functions/canAccessRoom.js +++ b/packages/rocketchat-authorization/server/functions/canAccessRoom.js @@ -1,15 +1,19 @@ /* globals RocketChat */ RocketChat.authz.roomAccessValidators = [ - function(room, user) { + function(room, user = {}) { + if (room.t === 'c') { + if (!user._id && RocketChat.settings.get('Accounts_AllowAnonymousRead') === true) { + return true; + } + + return RocketChat.authz.hasPermission(user._id, 'view-c-room'); + } + }, + function(room, user = {}) { const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); if (subscription) { return subscription._room; } - }, - function(room, user) { - if (room.t === 'c') { - return RocketChat.authz.hasPermission(user._id, 'view-c-room'); - } } ]; diff --git a/packages/rocketchat-authorization/server/methods/addUserToRole.js b/packages/rocketchat-authorization/server/methods/addUserToRole.js index e5dd7f2f57d..80305ee3cc0 100644 --- a/packages/rocketchat-authorization/server/methods/addUserToRole.js +++ b/packages/rocketchat-authorization/server/methods/addUserToRole.js @@ -15,7 +15,7 @@ Meteor.methods({ if (roleName === 'admin' && !RocketChat.authz.hasPermission(Meteor.userId(), 'assign-admin-role')) { throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', { - method: 'insertOrUpdateUser', + method: 'authorization:addUserToRole', action: 'Assign_admin' }); } diff --git a/packages/rocketchat-authorization/server/startup.js b/packages/rocketchat-authorization/server/startup.js index cf4ac5c7bd7..6aeb68290d7 100644 --- a/packages/rocketchat-authorization/server/startup.js +++ b/packages/rocketchat-authorization/server/startup.js @@ -46,20 +46,21 @@ Meteor.startup(function() { { _id: 'set-moderator', roles : ['admin', 'owner'] }, { _id: 'set-owner', roles : ['admin', 'owner'] }, { _id: 'unarchive-room', roles : ['admin'] }, - { _id: 'view-c-room', roles : ['admin', 'user', 'bot'] }, + { _id: 'view-c-room', roles : ['admin', 'user', 'bot', 'anonymous'] }, + { _id: 'user-generate-access-token', roles : ['admin'] }, { _id: 'view-d-room', roles : ['admin', 'user', 'bot'] }, { _id: 'view-full-other-user-info', roles : ['admin'] }, - { _id: 'view-history', roles : ['admin', 'user'] }, - { _id: 'view-joined-room', roles : ['guest', 'bot'] }, + { _id: 'view-history', roles : ['admin', 'user', 'anonymous'] }, + { _id: 'view-joined-room', roles : ['guest', 'bot', 'anonymous'] }, { _id: 'view-join-code', roles : ['admin'] }, { _id: 'view-logs', roles : ['admin'] }, { _id: 'view-other-user-channels', roles : ['admin'] }, - { _id: 'view-p-room', roles : ['admin', 'user'] }, + { _id: 'view-p-room', roles : ['admin', 'user', 'anonymous'] }, { _id: 'view-privileged-setting', roles : ['admin'] }, { _id: 'view-room-administration', roles : ['admin'] }, { _id: 'view-statistics', roles : ['admin'] }, { _id: 'view-user-administration', roles : ['admin'] }, - { _id: 'preview-c-room', roles : ['admin', 'user'] } + { _id: 'preview-c-room', roles : ['admin', 'user', 'anonymous'] } ]; for (const permission of permissions) { @@ -74,7 +75,8 @@ Meteor.startup(function() { { name: 'owner', scope: 'Subscriptions', description: 'Owner' }, { name: 'user', scope: 'Users', description: '' }, { name: 'bot', scope: 'Users', description: '' }, - { name: 'guest', scope: 'Users', description: '' } + { name: 'guest', scope: 'Users', description: '' }, + { name: 'anonymous', scope: 'Users', description: '' } ]; for (const role of defaultRoles) { diff --git a/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js b/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js index 947c32b10ac..1b222ab3935 100644 --- a/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js +++ b/packages/rocketchat-channel-settings/client/lib/ChannelSettings.js @@ -25,7 +25,7 @@ RocketChat.ChannelSettings = new class { const allOptions = _.toArray(this.options.get()); const allowedOptions = _.compact(_.map(allOptions, function(option) { if (option.validation == null || option.validation()) { - option.data = Object.assign({}, option.data, currentData); + option.data = Object.assign({}, typeof option.data === 'function' ? option.data() : option.data, currentData); return option; } })).filter(function(option) { diff --git a/packages/rocketchat-channel-settings/client/startup/messageTypes.js b/packages/rocketchat-channel-settings/client/startup/messageTypes.js index f9d76ce1bcd..b7cd5ce85a4 100644 --- a/packages/rocketchat-channel-settings/client/startup/messageTypes.js +++ b/packages/rocketchat-channel-settings/client/startup/messageTypes.js @@ -1,16 +1,49 @@ -function data(message) { - return { - user_by: message.u && message.u.username, - room_type: message.msg - }; -} Meteor.startup(function() { - ['room_changed_privacy', 'room_changed_topic', 'room_changed_announcement', 'room_changed_description'].forEach(id => { - RocketChat.MessageTypes.registerType({ - id, - system: true, - message: id, - data - }); + RocketChat.MessageTypes.registerType({ + id: 'room_changed_privacy', + system: true, + message: 'room_changed_privacy', + data(message) { + return { + user_by: message.u && message.u.username, + room_type: message.msg + }; + } + }); + + RocketChat.MessageTypes.registerType({ + id: 'room_changed_topic', + system: true, + message: 'room_changed_topic', + data(message) { + return { + user_by: message.u && message.u.username, + room_topic: message.msg + }; + } + }); + + RocketChat.MessageTypes.registerType({ + id: 'room_changed_announcement', + system: true, + message: 'room_changed_announcement', + data(message) { + return { + user_by: message.u && message.u.username, + room_announcement: message.msg + }; + } + }); + + RocketChat.MessageTypes.registerType({ + id: 'room_changed_description', + system: true, + message: 'room_changed_description', + data(message) { + return { + user_by: message.u && message.u.username, + room_description: message.msg + }; + } }); }); diff --git a/packages/rocketchat-channel-settings/client/startup/tabBar.js b/packages/rocketchat-channel-settings/client/startup/tabBar.js index 8106391c369..8728bc01ddd 100644 --- a/packages/rocketchat-channel-settings/client/startup/tabBar.js +++ b/packages/rocketchat-channel-settings/client/startup/tabBar.js @@ -2,6 +2,7 @@ Meteor.startup(() => { RocketChat.TabBar.addButton({ groups: ['channel', 'group', 'direct'], id: 'channel-settings', + anonymous: true, i18nTitle: 'Room_Info', icon: 'icon-info-circled', template: 'channelSettings', diff --git a/packages/rocketchat-custom-oauth/custom_oauth_client.js b/packages/rocketchat-custom-oauth/custom_oauth_client.js index 191706d9a8d..6db9f7bc09d 100644 --- a/packages/rocketchat-custom-oauth/custom_oauth_client.js +++ b/packages/rocketchat-custom-oauth/custom_oauth_client.js @@ -78,8 +78,10 @@ export class CustomOAuth { const credentialToken = Random.secret(); const loginStyle = OAuth._loginStyle(this.name, config, options); + const separator = this.authorizePath.indexOf('?') !== -1 ? '&' : '?'; + const loginUrl = `${ this.authorizePath - }?client_id=${ config.clientId + }${ separator }client_id=${ config.clientId }&redirect_uri=${ OAuth._redirectUri(this.name, config) }&response_type=code` + `&state=${ OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl) diff --git a/packages/rocketchat-custom-oauth/custom_oauth_server.js b/packages/rocketchat-custom-oauth/custom_oauth_server.js index dfca26c9fa8..9a61abe56d4 100644 --- a/packages/rocketchat-custom-oauth/custom_oauth_server.js +++ b/packages/rocketchat-custom-oauth/custom_oauth_server.js @@ -77,22 +77,30 @@ export class CustomOAuth { } let response = undefined; + + const allOptions = { + headers: { + 'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user + Accept: 'application/json' + }, + params: { + code: query.code, + redirect_uri: OAuth._redirectUri(this.name, config), + grant_type: 'authorization_code', + state: query.state + } + }; + + // Only send clientID / secret once on header or payload. + if (this.tokenSentVia === 'header') { + allOptions['auth'] = `${ config.clientId }:${ OAuth.openSecret(config.secret) }`; + } else { + allOptions['params']['client_secret'] = OAuth.openSecret(config.secret); + allOptions['params']['client_id'] = config.clientId; + } + 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 - } - }); + response = HTTP.post(this.tokenPath, allOptions); } 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}); diff --git a/packages/rocketchat-drupal/README.md b/packages/rocketchat-drupal/README.md new file mode 100644 index 00000000000..be85fc7ae2f --- /dev/null +++ b/packages/rocketchat-drupal/README.md @@ -0,0 +1,23 @@ +#Drupal oAuth Integration module. +This module works in conjunction with the [Rocket.Chat+ Module for Drupal](https://www.drupal.org/project/rocket_chat) +Version 7.x-1.1 or later. + +A full set of instructions for how to connect the 2 are present in the drupal module's documentation. + +Basically to connect the 2 you first setup the oAuth server connection in your drupal, with the proper permissions +("Use OAuth2 Server" => "Anonymous User" = Checked). + +In the Rocket chat you have to do the following: +- fill in the 'Client ID'. + Bear in mind that the Client ID should not be guessable,but is seen in the URL when doing the login. +- fill in the 'Client Secret'. + This should be treated as a Secret Key (like the Secret Key of a TLS certificate). it __must not__ be guesable or + derivable, and is best a Alphanumerical sequence between 16 and 48 cahracters long (longer would be better but longer + than 48 characters can be problem with long URI's) +- fill in the Drupal's BaseURL. +- on the Drupal use the "Restrict redirect URIs" Setting to limit possible exploits. and set the Redirect URI's to + whatever is in the Callback URL (like `https://Rocketchat.example.com/_oauth/drupal` and possibly also the + `https://Rocketchat.example.com/_oauth/drupal?close` URI.). +- Lastly do not forget to Enable the Drupal OAuth and `SAVE CHANGES`. + +When all is a Blue Button with a drupal like logo will apear on the login page of Rocket.Chat+ diff --git a/packages/rocketchat-drupal/common.js b/packages/rocketchat-drupal/common.js new file mode 100644 index 00000000000..a7a7d9e67a5 --- /dev/null +++ b/packages/rocketchat-drupal/common.js @@ -0,0 +1,39 @@ +/* global CustomOAuth */ + +// Drupal Server CallBack URL needs to be http(s)://{rocketchat.server}[:port]/_oauth/drupal +// In RocketChat -> Administration the URL needs to be http(s)://{drupal.server}/ + +const config = { + serverURL: '', + identityPath: '/oauth2/UserInfo', + authorizePath: '/oauth2/authorize', + tokenPath: '/oauth2/token', + scope: 'openid email profile offline_access', + tokenSentVia: 'payload', + usernameField: 'preferred_username', + mergeUsers: true, + addAutopublishFields: { + forLoggedInUser: ['services.drupal'], + forOtherUsers: ['services.drupal.name'] + } +}; + +const Drupal = new CustomOAuth('drupal', config); + +if (Meteor.isServer) { + Meteor.startup(function() { + RocketChat.settings.get('API_Drupal_URL', function(key, value) { + config.serverURL = value; + Drupal.configure(config); + }); + }); +} else { + Meteor.startup(function() { + Tracker.autorun(function() { + if (RocketChat.settings.get('API_Drupal_URL')) { + config.serverURL = RocketChat.settings.get('API_Drupal_URL'); + Drupal.configure(config); + } + }); + }); +} diff --git a/packages/rocketchat-drupal/login-button.css b/packages/rocketchat-drupal/login-button.css new file mode 100644 index 00000000000..e10c9d9c58a --- /dev/null +++ b/packages/rocketchat-drupal/login-button.css @@ -0,0 +1,11 @@ +.icon-drupal.service-icon { + display: inline-block; + width: 21px; + height: 28px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAcNJREFUSMetlbFrFFEQh7/dJnhpDHcx6SyiRLhLkUKDsRZB8S9IWi2Syj8iVooQ0FLBNlqJRpNO8Br7FLkoRDBooQHhNCr6WTjB48zd7d7uNI+d+c333pt9bx5kNPVGVm1WYF39rTbKhD7zr62XBVwI4MsYF4sCZ9S2uq6mMbbVmWGBx9WW+kGdCN+J+G6pY3mBo2pT/ape6IrNx2qb6mhW4Ii6qf5Ur/TQXI74pjoyCJioa3F8FgdoF0K3pib9hLfiDy9l3NVS6G/3EiyH4GbO+q9E3nJ3oKF+Vx/33Urvkj2K/EZn4JW6l/uY/MsfU9+rzUPHXCz/+jDADvC14Myhrqpf1EpB6LHgrCbqa+BzkiSXikAD/ByopcAUsFUUGLYFTKVABWiXBG0DlRTYB8ZLgo4D+ynQAuolQevAdgq8AM6rk0Vo0R7ngQ3UU+qvvNfzCOhKcE4fOu6r39TpIYHT0XsfdDon45puq7WcwFrk7f1XQvVsdPS3Wd+geMPeRN65XqJZdVf9od5Tz/TZ7t3Q7aqzg2avqnfUg2gQO+pT9WGMO+E/CF21m5H0gU8AV4GLwEmgCnwC3gEbwJMkST4elfsHJbIReGbv9uUAAAAASUVORK5CYII=); + background-repeat: no-repeat; +} + +.button.external-login.drupal { + background-color: #0f85b6; +} diff --git a/packages/rocketchat-drupal/package.js b/packages/rocketchat-drupal/package.js new file mode 100644 index 00000000000..76a586de6cd --- /dev/null +++ b/packages/rocketchat-drupal/package.js @@ -0,0 +1,22 @@ +Package.describe({ + name: 'rocketchat:drupal', + version: '0.0.1', + summary: 'RocketChat settings for Drupal oAuth2' +}); + +Package.onUse(function(api) { + api.versionsFrom('1.0'); + api.use('ecmascript'); + api.use('service-configuration'); + api.use('rocketchat:lib@0.0.1'); + api.use('rocketchat:custom-oauth'); + + // api.use('templating', 'client'); + + api.addFiles('common.js'); + api.addFiles('login-button.css', 'client'); + api.addFiles('startup.js', 'server'); + + api.use('templating', 'client'); +}); + diff --git a/packages/rocketchat-drupal/startup.js b/packages/rocketchat-drupal/startup.js new file mode 100644 index 00000000000..c0923d8f826 --- /dev/null +++ b/packages/rocketchat-drupal/startup.js @@ -0,0 +1,14 @@ +RocketChat.settings.addGroup('OAuth', function() { + this.section('Drupal', function() { + const enableQuery = { + _id: 'Accounts_OAuth_Drupal', + value: true + }; + + this.add('Accounts_OAuth_Drupal', false, { type: 'boolean' }); + this.add('API_Drupal_URL', '', { type: 'string', public: true, enableQuery, i18nDescription: 'API_Drupal_URL_Description' }); + this.add('Accounts_OAuth_Drupal_id', '', { type: 'string', enableQuery }); + this.add('Accounts_OAuth_Drupal_secret', '', { type: 'string', enableQuery }); + this.add('Accounts_OAuth_Drupal_callback_url', '_oauth/drupal', { type: 'relativeUrl', readonly: true, force: true, enableQuery }); + }); +}); diff --git a/packages/rocketchat-emoji-custom/client/lib/emojiCustom.js b/packages/rocketchat-emoji-custom/client/lib/emojiCustom.js index 89756e7cf67..ca4f9f6bccd 100644 --- a/packages/rocketchat-emoji-custom/client/lib/emojiCustom.js +++ b/packages/rocketchat-emoji-custom/client/lib/emojiCustom.js @@ -49,16 +49,6 @@ getEmojiUrlFromName = function(name, extension) { Blaze.registerHelper('emojiUrlFromName', getEmojiUrlFromName); -function updateEmojiPickerList() { - let html = ''; - for (const entry of RocketChat.emoji.packages.emojiCustom.emojisByCategory.rocket) { - const renderedEmoji = RocketChat.emoji.packages.emojiCustom.render(`:${ entry }:`); - html += `
  • ${ renderedEmoji }
  • `; - } - $('.rocket.emoji-list').empty().append(html); - RocketChat.EmojiPicker.updateRecent(); -} - deleteEmojiCustom = function(emojiData) { delete RocketChat.emoji.list[`:${ emojiData.name }:`]; const arrayIndex = RocketChat.emoji.packages.emojiCustom.emojisByCategory.rocket.indexOf(emojiData.name); @@ -78,7 +68,7 @@ deleteEmojiCustom = function(emojiData) { } } } - updateEmojiPickerList(); + RocketChat.EmojiPicker.updateRecent(); }; updateEmojiCustom = function(emojiData) { @@ -154,7 +144,7 @@ updateEmojiCustom = function(emojiData) { } } - updateEmojiPickerList(); + RocketChat.EmojiPicker.updateRecent(); }; Meteor.startup(() => diff --git a/packages/rocketchat-emoji/emojiPicker.js b/packages/rocketchat-emoji/emojiPicker.js index db72fca46aa..5b618d3f52e 100644 --- a/packages/rocketchat-emoji/emojiPicker.js +++ b/packages/rocketchat-emoji/emojiPicker.js @@ -39,7 +39,6 @@ function getEmojisByCategory(category) { //set correctPackage here to allow for recent emojis to work properly if (isSetNotNull(() => RocketChat.emoji.list[`:${ emoji }:`].emojiPackage)) { const correctPackage = RocketChat.emoji.list[`:${ emoji }:`].emojiPackage; - const image = RocketChat.emoji.packages[correctPackage].render(`:${ emoji }${ tone }:`); html += `
  • ${ image }
  • `; @@ -126,10 +125,13 @@ Template.emojiPicker.helpers({ emojiList(category) { const t = Template.instance(); const searchTerm = t.currentSearchTerm.get(); + const activeCategory = t.currentCategory.get(); + //this will cause the reflow when recent list gets updated + t.recentNeedsUpdate.get(); - //clear dynamic categories to prevent duplication issues - if (category === 'recent' || category === 'rocket') { - $(`.${ category }.emoji-list`).empty(); + //we only need to replace the active category, since switching tabs resets the filter + if (activeCategory !== category) { + return; } if (searchTerm.length > 0) { @@ -263,7 +265,7 @@ Template.emojiPicker.events({ Template.emojiPicker.onCreated(function() { this.tone = RocketChat.EmojiPicker.getTone(); const recent = RocketChat.EmojiPicker.getRecent(); - + this.recentNeedsUpdate = new ReactiveVar(false); this.currentCategory = new ReactiveVar(recent.length > 0 ? 'recent' : 'people'); this.currentSearchTerm = new ReactiveVar(''); @@ -276,4 +278,10 @@ Template.emojiPicker.onCreated(function() { $('.current-tone').addClass(`tone-${ newTone }`); this.tone = newTone; }; + + this.autorun(() => { + if (this.recentNeedsUpdate.get()) { + this.recentNeedsUpdate.set(false); + } + }); }); diff --git a/packages/rocketchat-emoji/emojiPicker.less b/packages/rocketchat-emoji/emojiPicker.less index 05454ae2478..9ce3d99a788 100644 --- a/packages/rocketchat-emoji/emojiPicker.less +++ b/packages/rocketchat-emoji/emojiPicker.less @@ -1,5 +1,3 @@ -@import "lesshat.less"; - .emoji-picker-icon { cursor: pointer; font-size: 18px; @@ -10,7 +8,7 @@ &:hover { &::before { - .transform(scale(1.2)); + transform: scale(1.2); } } } @@ -73,7 +71,7 @@ transition: transform 0.2s ease; &:hover { - .transform(scale(1.2)); + transform: scale(1.2); background-color: #dddddd; } } @@ -123,12 +121,12 @@ top: 25px; z-index: 1; transition: transform 0.2s ease, visibility 0.2s ease, opacity 0.2s ease; - .transform(translateY(-20px)); + transform: translateY(-20px); opacity: 0; visibility: hidden; &.show { - .transform(translateY(0px)); + transform: translateY(0); opacity: 1; display: block; visibility: visible; diff --git a/packages/rocketchat-emoji/lesshat.less b/packages/rocketchat-emoji/lesshat.less deleted file mode 100644 index 7c9f0ae86a2..00000000000 --- a/packages/rocketchat-emoji/lesshat.less +++ /dev/null @@ -1,17 +0,0 @@ -// 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 42a4c56634c..335cd28bb53 100644 --- a/packages/rocketchat-emoji/lib/EmojiPicker.js +++ b/packages/rocketchat-emoji/lib/EmojiPicker.js @@ -1,4 +1,4 @@ -/* globals Blaze, isSetNotNull, Template */ +/* globals Blaze, Template */ RocketChat.EmojiPicker = { width: 390, height: 238, @@ -115,23 +115,31 @@ RocketChat.EmojiPicker = { window.localStorage.setItem('emoji.recent', this.recent); RocketChat.emoji.packages.base.emojisByCategory.recent = this.recent; - this.updateRecent(); }, updateRecent() { - const total = RocketChat.emoji.packages.base.emojisByCategory.recent.length; - let html = ''; - for (let i = 0; i < total; i++) { - const emoji = RocketChat.emoji.packages.base.emojisByCategory.recent[i]; - - if (isSetNotNull(() => RocketChat.emoji.list[`:${ emoji }:`])) { - const emojiPackage = RocketChat.emoji.list[`:${ emoji }:`].emojiPackage; - const renderedEmoji = RocketChat.emoji.packages[emojiPackage].render(`:${ emoji }:`); - html += `
  • ${ renderedEmoji }
  • `; - } else { - this.recent = _.without(this.recent, emoji); - } + const instance = Template.instance(); + if (instance) { + instance.recentNeedsUpdate.set(true); + } else { + this.refreshDynamicEmojiLists(); } - $('.recent.emoji-list').empty().append(html); + }, + refreshDynamicEmojiLists() { + const dynamicEmojiLists = [ + RocketChat.emoji.packages.base.emojisByCategory.recent, + RocketChat.emoji.packages.emojiCustom.emojisByCategory.rocket + ]; + + dynamicEmojiLists.forEach((category) => { + if (category) { + for (let i = 0; i < category.length; i++) { + const emoji = category[i]; + if (!RocketChat.emoji.list[`:${ emoji }:`]) { + category = _.without(category, emoji); + } + } + } + }); } }; diff --git a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json index 2d1adcaa6c5..c931564c628 100644 --- a/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json +++ b/packages/rocketchat-google-natural-language/.npm/package/npm-shrinkwrap.json @@ -1,8 +1,8 @@ { "dependencies": { "ajv": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.4.tgz", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "from": "ajv@>=4.9.1 <5.0.0" }, "ansi-regex": { @@ -30,11 +30,6 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "from": "arrify@>=1.0.1 <2.0.0" }, - "ascli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", - "from": "ascli@>=1.0.0 <2.0.0" - }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -46,8 +41,8 @@ "from": "assert-plus@>=0.2.0 <0.3.0" }, "async": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz", "from": "async@>=2.1.2 <3.0.0" }, "asynckit": { @@ -65,84 +60,30 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", "from": "aws4@>=1.2.1 <2.0.0" }, - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "from": "balanced-match@>=0.4.1 <0.5.0" - }, "base64url": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", - "from": "base64url@>=1.0.4 <1.1.0", - "dependencies": { - "concat-stream": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.10.tgz", - "from": "concat-stream@>=1.4.7 <1.5.0" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "from": "isarray@0.0.1" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "from": "readable-stream@>=1.1.9 <1.2.0" - } - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "from": "base64url@>=2.0.0 <3.0.0" }, "bcrypt-pbkdf": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0" }, - "bl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "from": "bl@>=1.1.2 <1.2.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" - } - } - }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "from": "boom@>=2.0.0 <3.0.0" }, - "brace-expansion": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", - "from": "brace-expansion@>=1.0.0 <2.0.0" - }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "from": "buffer-equal-constant-time@>=1.0.1 <2.0.0" + "from": "buffer-equal-constant-time@1.0.1" }, "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" - }, - "bytebuffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", - "from": "bytebuffer@>=5.0.0 <6.0.0" - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "from": "camelcase@>=1.0.1 <2.0.0" - }, - "camelcase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz", - "from": "camelcase-keys@>=1.0.0 <2.0.0" + "from": "buffer-shims@>=1.0.0 <1.1.0" }, "capture-stack-trace": { "version": "1.0.0", @@ -159,26 +100,11 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "from": "chalk@>=1.1.1 <2.0.0" }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "from": "cliui@>=3.0.3 <4.0.0" - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "from": "co@>=4.6.0 <5.0.0" }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "from": "code-point-at@>=1.0.0 <2.0.0" - }, - "colour": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", - "from": "colour@>=0.7.1 <0.8.0" - }, "combined-stream": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", @@ -189,11 +115,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "from": "commander@>=2.9.0 <3.0.0" }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "from": "concat-map@0.0.1" - }, "concat-stream": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", @@ -226,11 +147,6 @@ } } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "from": "decamelize@>=1.1.1 <2.0.0" - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -249,14 +165,7 @@ "ecdsa-sig-formatter": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", - "from": "ecdsa-sig-formatter@>=1.0.0 <2.0.0", - "dependencies": { - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "from": "base64url@>=2.0.0 <3.0.0" - } - } + "from": "ecdsa-sig-formatter@1.0.9" }, "end-of-stream": { "version": "1.0.0", @@ -274,8 +183,8 @@ "from": "escape-string-regexp@>=1.0.2 <2.0.0" }, "extend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "from": "extend@>=3.0.0 <4.0.0" }, "extsprintf": { @@ -289,15 +198,10 @@ "from": "forever-agent@>=0.6.1 <0.7.0" }, "form-data": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "from": "form-data@>=2.1.1 <2.2.0" }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "from": "fs.realpath@>=1.0.0 <2.0.0" - }, "generate-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", @@ -308,14 +212,9 @@ "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "from": "generate-object-property@>=1.1.0 <2.0.0" }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "from": "get-stdin@>=4.0.1 <5.0.0" - }, "getpass": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "from": "getpass@>=0.1.1 <0.2.0", "dependencies": { "assert-plus": { @@ -325,68 +224,14 @@ } } }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "from": "glob@>=7.0.5 <8.0.0" - }, "google-auth-library": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.9.10.tgz", - "from": "google-auth-library@>=0.9.10 <0.10.0", - "dependencies": { - "async": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz", - "from": "async@>=1.4.2 <1.5.0" - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "from": "caseless@>=0.11.0 <0.12.0" - }, - "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "from": "form-data@>=1.0.0-rc4 <1.1.0", - "dependencies": { - "async": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", - "from": "async@^2.0.1" - } - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "from": "har-validator@>=2.0.6 <2.1.0" - }, - "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" - }, - "qs": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", - "from": "qs@>=6.2.0 <6.3.0" - }, - "request": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", - "from": "request@>=2.74.0 <2.75.0" - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "from": "tunnel-agent@>=0.4.1 <0.5.0" - } - } + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.10.0.tgz", + "from": "google-auth-library@>=0.10.0 <0.11.0" }, "google-auto-auth": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/google-auto-auth/-/google-auto-auth-0.5.2.tgz", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/google-auto-auth/-/google-auto-auth-0.5.4.tgz", "from": "google-auto-auth@>=0.5.2 <0.6.0" }, "google-gax": { @@ -402,8 +247,8 @@ } }, "google-p12-pem": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-0.1.1.tgz", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-0.1.2.tgz", "from": "google-p12-pem@>=0.1.0 <0.2.0" }, "google-proto-files": { @@ -417,14 +262,14 @@ "from": "graceful-readlink@>=1.0.0" }, "grpc": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.1.2.tgz", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.3.0.tgz", "from": "grpc@>=1.1.0 <2.0.0", "dependencies": { "node-pre-gyp": { - "version": "0.6.33", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz", - "from": "node-pre-gyp@0.6.33", + "version": "0.6.34", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz", + "from": "node-pre-gyp@0.6.34", "dependencies": { "mkdirp": { "version": "0.5.1", @@ -439,25 +284,42 @@ } }, "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "from": "nopt@>=3.0.6 <3.1.0", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "from": "nopt@>=4.0.1 <5.0.0", "dependencies": { "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", "from": "abbrev@>=1.0.0 <2.0.0" + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "from": "osenv@>=0.1.4 <0.2.0", + "dependencies": { + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "from": "os-homedir@>=1.0.0 <2.0.0" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "from": "os-tmpdir@>=1.0.0 <2.0.0" + } + } } } }, "npmlog": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz", - "from": "npmlog@>=4.0.1 <5.0.0", + "from": "npmlog@>=4.0.2 <5.0.0", "dependencies": { "are-we-there-yet": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "from": "are-we-there-yet@>=1.1.2 <1.2.0", "dependencies": { "delegates": { @@ -466,14 +328,14 @@ "from": "delegates@>=1.0.0 <2.0.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||>=1.1.13 <2.0.0", + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "from": "readable-stream@>=2.0.6 <3.0.0", "dependencies": { "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" + "from": "buffer-shims@>=1.0.0 <1.1.0" }, "core-util-is": { "version": "1.0.2", @@ -496,9 +358,9 @@ "from": "process-nextick-args@>=1.0.6 <1.1.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" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz", + "from": "string_decoder@>=1.0.0 <1.1.0" }, "util-deprecate": { "version": "1.0.2", @@ -515,13 +377,13 @@ "from": "console-control-strings@>=1.1.0 <1.2.0" }, "gauge": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "from": "gauge@>=2.7.1 <2.8.0", "dependencies": { "aproba": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.0.tgz", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", "from": "aproba@>=1.0.3 <2.0.0" }, "has-unicode": { @@ -590,9 +452,9 @@ } }, "rc": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", - "from": "rc@>=1.1.6 <1.2.0", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "from": "rc@>=1.1.7 <2.0.0", "dependencies": { "deep-extend": { "version": "0.4.1", @@ -610,16 +472,16 @@ "from": "minimist@>=1.2.0 <2.0.0" }, "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "from": "strip-json-comments@>=1.0.4 <1.1.0" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "from": "strip-json-comments@>=2.0.1 <2.1.0" } } }, "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "from": "request@>=2.79.0 <3.0.0", + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "from": "request@>=2.81.0 <3.0.0", "dependencies": { "aws-sign2": { "version": "0.6.0", @@ -632,9 +494,9 @@ "from": "aws4@>=1.2.1 <2.0.0" }, "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "from": "caseless@>=0.11.0 <0.12.0" + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "from": "caseless@>=0.12.0 <0.13.0" }, "combined-stream": { "version": "1.0.5", @@ -659,8 +521,8 @@ "from": "forever-agent@>=0.6.1 <0.7.0" }, "form-data": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "from": "form-data@>=2.1.1 <2.2.0", "dependencies": { "asynckit": { @@ -671,113 +533,38 @@ } }, "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "from": "har-validator@>=2.0.6 <2.1.0", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "from": "har-validator@>=4.2.1 <4.3.0", "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "from": "chalk@>=1.1.1 <2.0.0", + "ajv": { + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.7.tgz", + "from": "ajv@>=4.9.1 <5.0.0", "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "from": "ansi-styles@>=2.2.1 <3.0.0" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "from": "escape-string-regexp@>=1.0.2 <2.0.0" - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "from": "has-ansi@>=2.0.0 <3.0.0", - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "from": "ansi-regex@>=2.0.0 <3.0.0" - } - } + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "from": "co@>=4.6.0 <5.0.0" }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "from": "strip-ansi@>=3.0.0 <4.0.0", - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "from": "ansi-regex@>=2.0.0 <3.0.0" - } - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "from": "supports-color@>=2.0.0 <3.0.0" - } - } - }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "from": "commander@>=2.9.0 <3.0.0", - "dependencies": { - "graceful-readlink": { + "json-stable-stringify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "from": "graceful-readlink@>=1.0.0" - } - } - }, - "is-my-json-valid": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "dependencies": { - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "from": "generate-function@>=2.0.0 <3.0.0" - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "from": "generate-object-property@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "from": "json-stable-stringify@>=1.0.1 <2.0.0", "dependencies": { - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "from": "is-property@>=1.0.0 <2.0.0" + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "from": "jsonify@>=0.0.0 <0.1.0" } } - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "from": "jsonpointer@>=4.0.0 <5.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" } } }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "dependencies": { - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "from": "pinkie@>=2.0.0 <3.0.0" - } - } + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "from": "har-schema@>=1.0.5 <2.0.0" } } }, @@ -819,10 +606,15 @@ "from": "assert-plus@>=0.2.0 <0.3.0" }, "jsprim": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", "from": "jsprim@>=1.2.2 <2.0.0", "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "from": "assert-plus@1.0.0" + }, "extsprintf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", @@ -841,8 +633,8 @@ } }, "sshpk": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", "from": "sshpk@>=1.7.0 <2.0.0", "dependencies": { "asn1": { @@ -871,8 +663,8 @@ "from": "ecc-jsbn@>=0.1.1 <0.2.0" }, "getpass": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "from": "getpass@>=0.1.1 <0.2.0" }, "jodid25519": { @@ -881,8 +673,8 @@ "from": "jodid25519@>=1.0.0 <2.0.0" }, "jsbn": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "from": "jsbn@>=0.1.0 <0.2.0" }, "tweetnacl": { @@ -910,14 +702,14 @@ "from": "json-stringify-safe@>=5.0.1 <5.1.0" }, "mime-types": { - "version": "2.1.14", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz", + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "from": "mime-types@>=2.1.7 <2.2.0", "dependencies": { "mime-db": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz", - "from": "mime-db@>=1.26.0 <1.27.0" + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "from": "mime-db@>=1.27.0 <1.28.0" } } }, @@ -926,10 +718,20 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "from": "oauth-sign@>=0.8.1 <0.9.0" }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "from": "performance-now@>=0.2.0 <0.3.0" + }, "qs": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.0.tgz", - "from": "qs@>=6.3.0 <6.4.0" + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "from": "qs@>=6.4.0 <6.5.0" + }, + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "from": "safe-buffer@>=5.0.1 <6.0.0" }, "stringstream": { "version": "0.0.5", @@ -949,9 +751,9 @@ } }, "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "from": "tunnel-agent@>=0.4.1 <0.5.0" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "from": "tunnel-agent@>=0.6.0 <0.7.0" }, "uuid": { "version": "3.0.1", @@ -961,9 +763,9 @@ } }, "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "from": "rimraf@>=2.5.4 <2.6.0", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "from": "rimraf@>=2.6.1 <3.0.0", "dependencies": { "glob": { "version": "7.1.1", @@ -998,8 +800,8 @@ "from": "minimatch@>=3.0.0 <4.0.0", "dependencies": { "brace-expansion": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", "from": "brace-expansion@>=1.0.0 <2.0.0", "dependencies": { "balanced-match": { @@ -1040,12 +842,12 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "from": "semver@>=5.3.0 <5.4.0" + "from": "semver@>=5.3.0 <6.0.0" }, "tar": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "from": "tar@>=2.2.1 <2.3.0", + "from": "tar@>=2.2.1 <3.0.0", "dependencies": { "block-stream": { "version": "0.0.9", @@ -1053,8 +855,8 @@ "from": "block-stream@*" }, "fstream": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "from": "fstream@>=1.0.2 <2.0.0", "dependencies": { "graceful-fs": { @@ -1072,26 +874,26 @@ } }, "tar-pack": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz", - "from": "tar-pack@>=3.3.0 <3.4.0", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "from": "tar-pack@>=3.4.0 <4.0.0", "dependencies": { "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "from": "debug@>=2.2.0 <2.3.0", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.5.tgz", + "from": "debug@>=2.2.0 <3.0.0", "dependencies": { "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "from": "ms@0.7.1" + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", + "from": "ms@0.7.3" } } }, "fstream": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz", - "from": "fstream@>=1.0.10 <1.1.0", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "from": "fstream@>=1.0.10 <2.0.0", "dependencies": { "graceful-fs": { "version": "4.1.11", @@ -1108,7 +910,7 @@ "fstream-ignore": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "from": "fstream-ignore@>=1.0.5 <1.1.0", + "from": "fstream-ignore@>=1.0.5 <2.0.0", "dependencies": { "inherits": { "version": "2.0.3", @@ -1121,8 +923,8 @@ "from": "minimatch@>=3.0.0 <4.0.0", "dependencies": { "brace-expansion": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", "from": "brace-expansion@>=1.0.0 <2.0.0", "dependencies": { "balanced-match": { @@ -1142,9 +944,9 @@ } }, "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "from": "once@>=1.3.3 <1.4.0", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "from": "once@>=1.3.3 <2.0.0", "dependencies": { "wrappy": { "version": "1.0.2", @@ -1154,14 +956,14 @@ } }, "readable-stream": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", - "from": "readable-stream@>=2.1.4 <2.2.0", + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "from": "readable-stream@>=2.1.4 <3.0.0", "dependencies": { "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" + "from": "buffer-shims@>=1.0.0 <1.1.0" }, "core-util-is": { "version": "1.0.2", @@ -1171,7 +973,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "from": "inherits@>=2.0.0 <3.0.0" + "from": "inherits@>=2.0.0 <2.1.0" }, "isarray": { "version": "1.0.0", @@ -1184,9 +986,9 @@ "from": "process-nextick-args@>=1.0.6 <1.1.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" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz", + "from": "string_decoder@>=1.0.0 <1.1.0" }, "util-deprecate": { "version": "1.0.2", @@ -1198,7 +1000,7 @@ "uid-number": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "from": "uid-number@>=0.0.6 <0.1.0" + "from": "uid-number@>=0.0.6 <0.0.7" } } } @@ -1207,9 +1009,9 @@ } }, "gtoken": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-1.2.1.tgz", - "from": "gtoken@>=1.1.0 <2.0.0" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-1.2.2.tgz", + "from": "gtoken@>=1.2.1 <2.0.0" }, "har-schema": { "version": "1.0.5", @@ -1241,41 +1043,16 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "from": "http-signature@>=1.1.0 <1.2.0" }, - "indent-string": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", - "from": "indent-string@>=1.1.0 <2.0.0" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "from": "inflight@>=1.0.4 <2.0.0" - }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "from": "inherits@>=2.0.3 <3.0.0" }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "from": "invert-kv@>=1.0.0 <2.0.0" - }, "is": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz", "from": "is@>=3.0.1 <4.0.0" }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "from": "is-finite@>=1.0.0 <2.0.0" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0" - }, "is-my-json-valid": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", @@ -1342,31 +1119,26 @@ "from": "jsonpointer@>=4.0.0 <5.0.0" }, "jsprim": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz", - "from": "jsprim@>=1.2.2 <2.0.0" - }, - "jwa": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.0.2.tgz", - "from": "jwa@>=1.0.0 <1.1.0", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "from": "jsprim@>=1.2.2 <2.0.0", "dependencies": { - "base64url": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-0.0.6.tgz", - "from": "base64url@>=0.0.4 <0.1.0" + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "from": "assert-plus@1.0.0" } } }, - "jws": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", - "from": "jws@>=3.0.0 <3.1.0" + "jwa": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "from": "jwa@>=1.1.4 <2.0.0" }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "from": "lcid@>=1.0.0 <2.0.0" + "jws": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "from": "jws@>=3.1.4 <4.0.0" }, "lodash": { "version": "4.17.4", @@ -1376,7 +1148,7 @@ "lodash.noop": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", - "from": "lodash.noop@>=3.0.0 <3.1.0" + "from": "lodash.noop@>=3.0.1 <4.0.0" }, "log-driver": { "version": "1.2.5", @@ -1386,24 +1158,7 @@ "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "from": "long@>=3.0.0 <4.0.0" - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "from": "map-obj@>=1.0.0 <2.0.0" - }, - "meow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-2.0.0.tgz", - "from": "meow@>=2.0.0 <2.1.0", - "dependencies": { - "object-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", - "from": "object-assign@>=1.0.0 <2.0.0" - } - } + "from": "long@>=3.2.0 <4.0.0" }, "methmeth": { "version": "1.1.0", @@ -1416,44 +1171,29 @@ "from": "mime@>=1.2.11 <2.0.0" }, "mime-db": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz", - "from": "mime-db@>=1.26.0 <1.27.0" + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "from": "mime-db@>=1.27.0 <1.28.0" }, "mime-types": { - "version": "2.1.14", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz", + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "from": "mime-types@>=2.1.7 <2.2.0" }, - "minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", - "from": "minimatch@>=3.0.2 <4.0.0" - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "from": "minimist@>=1.1.0 <2.0.0" - }, "modelo": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/modelo/-/modelo-4.2.0.tgz", "from": "modelo@>=4.2.0 <5.0.0" }, "nan": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.5.1.tgz", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", "from": "nan@>=2.0.0 <3.0.0" }, "node-forge": { - "version": "0.6.49", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.49.tgz", - "from": "node-forge@>=0.6.46 <0.7.0" - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "from": "number-is-nan@>=1.0.0 <2.0.0" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", + "from": "node-forge@>=0.7.1 <0.8.0" }, "oauth-sign": { "version": "0.8.2", @@ -1470,21 +1210,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", "from": "once@>=1.3.0 <1.4.0" }, - "optjs": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", - "from": "optjs@>=3.2.2 <3.3.0" - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "from": "os-locale@>=1.4.0 <2.0.0" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "from": "path-is-absolute@>=1.0.0 <2.0.0" - }, "performance-now": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", @@ -1511,9 +1236,9 @@ "from": "propprop@>=0.3.1 <0.4.0" }, "protobufjs": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.2.tgz", - "from": "protobufjs@>=5.0.0 <6.0.0" + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.7.3.tgz", + "from": "protobufjs@>=6.7.0 <7.0.0" }, "punycode": { "version": "1.4.1", @@ -1526,15 +1251,10 @@ "from": "qs@>=6.4.0 <6.5.0" }, "readable-stream": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.3.tgz", + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", "from": "readable-stream@>=2.2.2 <3.0.0" }, - "repeating": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "from": "repeating@>=1.1.0 <2.0.0" - }, "request": { "version": "2.81.0", "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", @@ -1556,9 +1276,9 @@ "from": "har-validator@>=2.0.6 <2.1.0" }, "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" + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "from": "node-uuid@>=1.4.7 <1.5.0" }, "qs": { "version": "6.3.2", @@ -1600,8 +1320,8 @@ } }, "sshpk": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.11.0.tgz", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", "from": "sshpk@>=1.7.0 <2.0.0", "dependencies": { "assert-plus": { @@ -1626,20 +1346,10 @@ "resolved": "https://registry.npmjs.org/string-format-obj/-/string-format-obj-1.1.0.tgz", "from": "string-format-obj@>=1.1.0 <2.0.0" }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "from": "string-template@>=0.2.0 <0.3.0" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "from": "string-width@>=1.0.1 <2.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" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz", + "from": "string_decoder@>=1.0.0 <1.1.0" }, "stringstream": { "version": "0.0.5", @@ -1701,16 +1411,6 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", "from": "verror@1.3.6" }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "from": "window-size@>=0.1.4 <0.2.0" - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "from": "wrap-ansi@>=2.0.0 <3.0.0" - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1720,23 +1420,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "from": "xtend@>=4.0.0 <5.0.0" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "from": "y18n@>=3.2.0 <4.0.0" - }, - "yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "from": "yargs@>=3.10.0 <4.0.0", - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "from": "camelcase@>=2.0.1 <3.0.0" - } - } } } } diff --git a/packages/rocketchat-i18n/i18n/bg.i18n.json b/packages/rocketchat-i18n/i18n/bg.i18n.json index fbedfc1e680..893252288ea 100644 --- a/packages/rocketchat-i18n/i18n/bg.i18n.json +++ b/packages/rocketchat-i18n/i18n/bg.i18n.json @@ -1,6 +1,8 @@ { "#channel": "#канал", "0_Errors_Only": "0 - Само Грешки", + "1_Errors_and_Information": "1 - Грешки и Информация", + "2_Erros_Information_and_Debug": "2 - Грешки, Информация и Дебъг", "403": "Забранен", "@username": "@потребителско име", "@username_message": "@потребителско име", @@ -23,6 +25,7 @@ "Accounts_OAuth_Facebook": "Влизана с Facebook профил", "Accounts_OAuth_Google": "Влизане с Google профил", "Accounts_RegistrationForm_Public": "Обществен", + "All_channels": "Всички канали", "All_messages": "Всички съобщения", "and": "и", "Application_Name": "Име на Приложение", @@ -38,6 +41,8 @@ "Busy_male": "Зает", "channel": "канал", "Channel": "Канал", + "Channels": "Канали", + "Channels_list": "Списък на публични канали", "Click_here": "Натисни тук", "close": "затвори", "Close": "Затвори", @@ -84,6 +89,7 @@ "LDAP_Port": "Порт", "Leave_room": "Излез от стаята", "line": "линия", + "List_of_Channels": "Списък с Канали", "Loading...": "Зареждане...", "Log_Package": "Покажи пакет", "Login": "Влез", @@ -96,6 +102,7 @@ "Message_too_long": "Съобщението е твърде дълго", "Messages": "Съобщения", "minutes": "Минути", + "More_channels": "Още канали", "My_Account": "Моя Профил", "n_messages": "%s съобщения", "N_new_messages": "%s нови съобщения", @@ -112,6 +119,7 @@ "Online": "На линия", "Oops!": "Упс", "Open": "Отвори", + "Password": "Парола", "People": "Хора", "Please_add_a_comment": "Добави коментар моля", "Please_wait": "Моля изчакайте", @@ -145,6 +153,8 @@ "Site_Name": "Име на Сайта", "Skip": "Прескочи", "Smarsh_MissingEmail_Email": "Липсваща Електрона поща", + "Stats_Total_Channels": "Общо Канали", + "Stats_Total_Messages_Channel": "Общо Съобщения в Канали", "Test_Desktop_Notifications": "Изпробвай Известия на работния плот", "Thank_you_exclamation_mark": "Благодаря!", "The_server_will_restart_in_s_seconds": "Сървъра ще бъде рестартиран след %s секунди", diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json index eefd791a54f..436bcca1e9d 100644 --- a/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -17,6 +17,7 @@ "Accessing_permissions": "L'accés als permisos", "Account_SID": "Compte SID", "Accounts": "Comptes", + "Accounts_AllowAnonymousAccess": "Permet accés anònim", "Accounts_AllowDeleteOwnAccount": "Permetre als usuaris eliminar el seu propi compte", "Accounts_AllowedDomainsList": "Llista de dominis permesos", "Accounts_AllowedDomainsList_Description": "Llista dels dominis permesos separada per comes ", @@ -62,6 +63,10 @@ "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_Drupal": "Activa inici de sessió de Drupal", + "Accounts_OAuth_Drupal_callback_url": "Redirect URI de Drupal oAuth2", + "Accounts_OAuth_Drupal_id": "Client ID de Drupal oAuth2", + "Accounts_OAuth_Drupal_secret": "Client Secret de Drupal oAuth2", "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", @@ -170,6 +175,8 @@ "API_CORS_Origin": "Origen CORS", "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_Drupal_URL": "Adreça URL del servidor de Drupal", + "API_Drupal_URL_Description": "Exemple: https://domini.com (sense la barra final)", "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)", @@ -182,9 +189,13 @@ "API_Enable_CORS": "Activa CORS", "API_Enable_Direct_Message_History_EndPoint": "Activa la consulta de l'historial de missatges directes", "API_Enable_Direct_Message_History_EndPoint_Description": "Això activa el `/api/v1/im.history.others` que permet veure missatges directes enviats per altres usuaris tot i no formar-ne part.", + "API_Enable_Shields": "Activa escuts", + "API_Enable_Shields_Description": "Activa els escuts disponibles a `/api/v1/shields.svg`", "API_GitHub_Enterprise_URL": "URL del servidor", "API_GitHub_Enterprise_URL_Description": "Exemple: http://domain.com (sense la barra final)", "API_Gitlab_URL": "URL de GitLab", + "API_Shield_Types": "Tipus d'escut", + "API_Shield_Types_Description": "Tipus d'escut que s'activaran, com a llista separada per comes. Triar entre `online`, `channel` o `*` per a tots", "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)?", @@ -406,6 +417,7 @@ "Desktop_Notifications_Enabled": "Les notificacions d'escriptori estan activades", "Direct_message_someone": "Envia un missatge directe a algú", "Direct_Messages": "Missatges directes", + "Disable_Notifications": "Desactiva notificacions", "Disable_two-factor_authentication": "Desactiva l'autenticació de dos factors", "Display_offline_form": "Mostra el formulari de fora de línia", "Displays_action_text": "Mostra text de l'acció", @@ -618,6 +630,7 @@ "Give_the_application_a_name_This_will_be_seen_by_your_users": "Bateja l'aplicació. El nom escollit serà visible als usuaris.", "Global": "Global", "GoogleCloudStorage": "Emmagatzematge Google Cloud", + "GoogleNaturalLanguage_ServiceAccount_Description": "Arxiu JSON amb la clau del compte de servei (\"Service account key\"). Pots trobar més informació [aquí](https://cloud.google.com/natural-language/docs/common/auth#set_up_a_service_account)", "GoogleTagManager_id": "ID de Google Tag Manager", "Guest_Pool": "Llista de clients", "Hash": "Hash", @@ -633,6 +646,7 @@ "Hide_room": "Oculta sala", "Hide_Room_Warning": "Segur que voleu ocultar la sala \"%s\"?", "Hide_roles": "Amaga rols", + "Hide_Unread_Room_Status": "Amaga l'estat de sales no llegides", "Hide_usernames": "Oculta els noms d'usuari", "Highlights": "Ressalta", "Highlights_How_To": "Per ser notificat quan algú esmenta una paraula o frase, afegeix-la aquí. Es poden separar les paraules o frases amb comes. No es distingeix entre majúscules i minúscules.", @@ -725,6 +739,8 @@ "Integration_Retry_Count_Description": "Quantes vegades s'ha de reintentar la integració si la petició a l'adreça URL falla?", "Integration_Retry_Delay": "Temps de reintent", "Integration_Retry_Delay_Description": "Quin algorisme de retard de reintent s'ha d'utilitzar? 10^x o 2^x o x*2", + "Integration_Run_When_Message_Is_Edited": "Executa en edicions", + "Integration_Run_When_Message_Is_Edited_Description": "Aquesta integració s'ha d'executar quan el missatge s'edita? Desactivar aquesta opció farà que la integració només s'executi en missatges nous .", "Integration_Word_Trigger_Placement": "Posició indeterminada de paraula", "Integration_Word_Trigger_Placement_Description": "S'hauria d'activar el disparador quan la paraula es troba en un lloc de la frase fora del començament?", "Integration_updated": "La integració s'ha actualitzat.", @@ -782,6 +798,7 @@ "Jitsi_Enable_Channels": "Activa als canals", "join": "Unir-se", "Join_audio_call": "Unir-se a la trucada", + "Join_Chat": "Uneix-te al xat", "Join_default_channels": "Unir-se als canals predeterminats", "Join_the_Community": "Uneix-te a la comunitat", "Join_the_given_channel": "Unir-se al canal proporcionat", @@ -962,6 +979,9 @@ "Message_AllowUnrecognizedSlashCommand": "Permet /comandes no reconegudes", "Message_AlwaysSearchRegExp": "Sempre cercar utilitzant RegExp", "Message_AlwaysSearchRegExp_Description": "Recomanem activar-ho si el teu idioma no està suportat per la cerca de text MongoDB.", + "Message_Attachments": "Adjunts al missatge", + "Message_Attachments_GroupAttach": "Agrupa els botons d'adjuntar", + "Message_Attachments_GroupAttachDescription": "Això uneix les icones en un menú desplegable. Ocupen menys espai a la pantalla.", "Message_AudioRecorderEnabled": "Gravadora d'àudio activa", "Message_AudioRecorderEnabledDescription": "Requereix que els arxius de tipus 'audio/wav' siguin un tipus de fitxer vàlid dins les opcions de 'Pujar arxius'.", "Message_BadWordsFilterList": "Afegir paraulotes a la llista negra", @@ -1065,6 +1085,7 @@ "Nothing_found": "No s'ha trobat res", "Notification_Duration": "Duració de la notificació", "Notifications": "Notificacions", + "Notifications_Muted_Description": "Si esculls silenciar-ho tot, no veuràs la sala destacada a la llista quan hi hagi nous missatges, excepte si són mencions. Silenciar les notificacions sobreescriurà les opcions de notificació.", "Notify_all_in_this_room": "Notifica a tothom d'aquest canal", "Notify_active_in_this_room": "Notifica als usuaris actius d'aquesta sala", "Num_Agents": "# d'agents", @@ -1207,6 +1228,7 @@ "Register": "Crea un compte nou", "Registration": "Registre", "Registration_Succeeded": "Registre reeixit", + "Register_or_login_to_send_messages": "Registra't o identifica't per enviar missatges", "Registration_via_Admin": "Registre via Admin", "Regular_Expressions": "Expressions regulars", "Release": "Llançament", @@ -1322,6 +1344,7 @@ "Sending": "Enviant...", "Served_By": "Servit per", "Service": "Servei", + "Service_account_key": "Service account key", "Set_as_moderator": "Fes-lo moderador", "Set_as_owner": "Fes-lo propietari", "Settings": "Configuració", @@ -1522,13 +1545,14 @@ "UI_DisplayRoles": "Mostra rols", "UI_Merge_Channels_Groups": "Uneix grups privats amb canals", "UI_Use_Name_Avatar": "Utilitza les inicials del nom complet per generar l'avatar per defecte", + "UI_Use_Real_Name": "Utilitza el nom real", "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", "Unpin_Message": "Desfixa el missatge", - "Unread_Alert": "Alerta de no llegit", + "Unread_Tray_Icon_Alert": "Icona d'alerta de no llegits a la safata", "Unread_Messages": "Missatges no llegits", "Unread_Rooms": "Sales no llegides", "Unread_Rooms_Mode": "Mode de sales no llegides", diff --git a/packages/rocketchat-i18n/i18n/cs.i18n.json b/packages/rocketchat-i18n/i18n/cs.i18n.json index cb32f6ccd72..ba2dab094d9 100644 --- a/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -17,6 +17,7 @@ "Accessing_permissions": "Přístup k oprávnění", "Account_SID": "SID účtu", "Accounts": "Účty", + "Accounts_AllowAnonymousAccess": "Povolit anonymní přístup", "Accounts_AllowDeleteOwnAccount": "Povolit uživatelům odstranit vlastní účet", "Accounts_AllowedDomainsList": "Seznam povolených domén", "Accounts_AllowedDomainsList_Description": "Čárkami oddělený seznam povolených domén", @@ -62,6 +63,10 @@ "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_Drupal": "Povolit Drupal přihlášení", + "Accounts_OAuth_Drupal_callback_url": "Drupal oAuth2 URI Přesměrování", + "Accounts_OAuth_Drupal_id": "Drupal oAuth2 ID klienta", + "Accounts_OAuth_Drupal_secret": "Drupal oAuth2 Secret klienta", "Accounts_OAuth_Facebook": "Facebook Přihlášení", "Accounts_OAuth_Facebook_callback_url": "Facebook Callback URL", "Accounts_OAuth_Facebook_id": "Facebook App Id", @@ -170,6 +175,8 @@ "API_CORS_Origin": "CORS Origin", "API_Default_Count": "Výchozí počet", "API_Default_Count_Description": "Výchozí počet výsledků v REST API pokud není zažádáno konkrétní číslo", + "API_Drupal_URL": "Drupal URL Serveru", + "API_Drupal_URL_Description": "Například: https://domain.com (bez lomítka na konci)", "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", @@ -182,9 +189,13 @@ "API_Enable_CORS": "Povolit CORS", "API_Enable_Direct_Message_History_EndPoint": "Povolit Endpoint přímých zpráv", "API_Enable_Direct_Message_History_EndPoint_Description": "Povolí endpoint `/api/v1/im.history.others` přes který lze stahovat přímé zprávy mezi všemi uživateli.", + "API_Enable_Shields": "Povolit sdílecí ikony", + "API_Enable_Shields_Description": "Ikony dostupné na adrese `/api/v1/shields.svg`", "API_GitHub_Enterprise_URL": "Adresa URL serveru", "API_GitHub_Enterprise_URL_Description": "Příklad: http://domain.com (bez lomítka na konci)", "API_Gitlab_URL": "GitLab URL", + "API_Shield_Types": "Typy ikon", + "API_Shield_Types_Description": "Čárkou oddělený seznam povolených typů. Na výběr z `online`, `channel` nebo `*` pro všechny.", "API_Token": "API Token", "API_Upper_Count_Limit": "Maximální počet", "API_Upper_Count_Limit_Description": "Kolik nejvíce záznamů smí REST API vrátit (pokud není limitovaná)", @@ -245,6 +256,7 @@ "Back_to_integration_detail": "Zpět na detail integrace", "Back_to_login": "Zpět na přihlašovací formulář", "Back_to_permissions": "Zpět na práva", + "Backup_codes": "Záložní kódy", "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta funkcionalita. Videohovory musí být povoleny.", "Block_User": "Blokovat uživatele", "Body": "Obsah", @@ -405,6 +417,8 @@ "Desktop_Notifications_Enabled": "Oznámení na ploše jsou povolena", "Direct_message_someone": "Přímá zpráva někomu", "Direct_Messages": "Přímé zprávy", + "Disable_Notifications": "Zakázat notifikace", + "Disable_two-factor_authentication": "Zakázat dvoufázové ověření", "Display_offline_form": "Zobrazit offline formulář", "Displays_action_text": "Zobrazuje text akce", "Do_you_want_to_change_to_s_question": "Chcete změnit na %s?", @@ -447,10 +461,12 @@ "Empty_title": "Prázdný název", "Enable": "Povolit", "Enable_Desktop_Notifications": "Aktivovat oznámení na ploše", + "Enable_two-factor_authentication": "Povolit dvoufázové ověření", "Enabled": "Povoleno", "Enable_Svg_Favicon": "Povolit SVG favikonu", "Encrypted_message": "Šifrovaná zpráva", "End_OTR": "Ukončit konverzaci mimo záznam", + "Enter_authentication_code": "Zadejte autentizační kód", "Enter_Alternative": "Alternativní mód (odesílat po stisku Enter + Ctrl/Alt/Shift/CMD)", "Enter_a_regex": "Zadejte regulární výraz", "Enter_a_room_name": "Zadejte název místnosti", @@ -614,6 +630,7 @@ "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í", "GoogleCloudStorage": "Google Cloud Storage", + "GoogleNaturalLanguage_ServiceAccount_Description": "JSON klíč účtu služby. Více informací naleznete [zde](https://cloud.google.com/natural-language/docs/common/auth#set_up_a_service_account)", "GoogleTagManager_id": "ID Google tag manageru", "Guest_Pool": "Skupina hostů", "Hash": "Hash", @@ -629,6 +646,7 @@ "Hide_room": "Skrýt místnost", "Hide_Room_Warning": "Jste si jisti, že chcete skrýt místnost \"%s\"?", "Hide_roles": "Schovat role", + "Hide_Unread_Room_Status": "Schovat stav nepřečtených místností", "Hide_usernames": "Skrýt uživatelská jména", "Highlights": "Klíčová slova", "Highlights_How_To": "Chcete-li být upozorněni, když někdo zmíní slovo nebo frázi, přidejte jej sem. Můžete oddělit slova nebo fráze čárkami. Velikost písmen nehraje roli", @@ -721,6 +739,8 @@ "Integration_Retry_Count_Description": "Kolikrát by se integrace měla znova pokusit volat URL pokud byl první pokus neúspěšný?", "Integration_Retry_Delay": "Čas prodlení opakování", "Integration_Retry_Delay_Description": "Jaký algoritmus prodlení by se měl použít?10^x, 2^x nebo x*2", + "Integration_Run_When_Message_Is_Edited": "Spustit při editaci", + "Integration_Run_When_Message_Is_Edited_Description": "Měla by být integrace spuštěna po editaci zprávy? Pokud volbu vypnete, integrace se bude spouštět pouze pro nové zprávy.", "Integration_Word_Trigger_Placement": "Přepisování slov kdekoliv", "Integration_Word_Trigger_Placement_Description": "Mělo by se vyvolat pokud je slovo umístěno jinde než na začátku?", "Integration_updated": "Integrace byla aktualizována", @@ -740,6 +760,7 @@ "Invalid_room_name": "%s není platné jméno místnosti,
    použijte pouze písmena, číslice a pomlčky", "Invalid_secret_URL_message": "Adresa URL je neplatná.", "Invalid_setting_s": "Neplatné nastavení: %s", + "Invalid_two_factor_code": "Neplatný dvoufázový kód", "invisible": "neviditelný", "Invisible": "Neviditelný", "Invitation": "Pozvánka", @@ -777,6 +798,7 @@ "Jitsi_Enable_Channels": "Povolit v místnostech", "join": "Připojit", "Join_audio_call": "Připojit k hovoru", + "Join_Chat": "Připojit k chatu", "Join_default_channels": "Připojit se k výchozím místnostem", "Join_the_Community": "Zapojte se do komunity", "Join_the_given_channel": "Přidejte se k dané místnosti", @@ -924,6 +946,7 @@ "Mailer_body_tags": "Je nutné použít [unsubscribe] pro vložení odkazu na odhlášení.
    Můžete také použít [name], [fname], [lname] pro uživatelské jméno, křestí jméno a příjmen nebo [email] pro e-mail uživatele.", "Mailing": "Mailing", "Make_Admin": "Změnit na Správce", + "Make_sure_you_have_a_copy_of_your_codes": "Pečlivě is uschovejte kopii svých kódů: __codes__ Pokud ztratíte přístup ke své autentizační aplikaci, můžete jeden z nich použít k přihlášení.", "Manager_added": "Manažer přidán", "Manager_removed": "Manažer odstraněn", "Managing_assets": "Správa datových zdrojů", @@ -956,6 +979,9 @@ "Message_AllowUnrecognizedSlashCommand": "Povolit nerozpoznané lomítkové příkazy", "Message_AlwaysSearchRegExp": "Vždy hledat pomocí regulárních výrazů", "Message_AlwaysSearchRegExp_Description": "Doporučujeme nastavit `Ano` pokud Váš jazyk není podporován v textového vyhledávání MongoDB .", + "Message_Attachments": "Přílohy zprávy", + "Message_Attachments_GroupAttach": "Sjednotit tlačítka přílohy", + "Message_Attachments_GroupAttachDescription": "Zobrazi tlačítka přílohy jako rozklikávací menu, zabere méně místa na obrazovce", "Message_AudioRecorderEnabled": "Záznam zvuku povolen", "Message_AudioRecorderEnabledDescription": "Je třeba mít povolené `audio/wav` soubory v nabídce 'Nahrání souboru'", "Message_BadWordsFilterList": "Přidat sprostá slova na černou listinu", @@ -1059,6 +1085,7 @@ "Nothing_found": "Nic nalezeno", "Notification_Duration": "Délka zobrazení oznámení", "Notifications": "Oznámení", + "Notifications_Muted_Description": "Pokud ztišíte všechno, neuvidíte zvýrazněné místnosti s novými zprávami, krom zmínek. Ztišení notifikací přetěží nastavení notifikací v jednotlivých místnostech.", "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ů", @@ -1095,6 +1122,7 @@ "optional": "volitelný", "Use_minor_colors": "Použít nevýraznou barevnou baletu (ve výchozím stavu podědí výraznou paletu)", "or": "nebo", + "Open_your_authentication_app_and_enter_the_code": "Otevřete autentizační aplikaci a zadejte vygenerovaný kód. Můžete použít jeden ze svých záložních kódů.", "Order": "Objednat", "OS_Arch": "Architektura OS", "OS_Cpus": "Počet CPU OS", @@ -1196,9 +1224,11 @@ "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", + "Regenerate_codes": "Přegenerovat záložní kódy", "Register": "Zaregistrovat nový účet", "Registration": "Registrace", "Registration_Succeeded": "Registrace úspěšná", + "Register_or_login_to_send_messages": "Pro odeslání zpráv je třeba se zaregistrovat nebo přihlásit", "Registration_via_Admin": "Registrace přes Admin", "Regular_Expressions": "Regulární výrazy", "Release": "Verze", @@ -1276,6 +1306,7 @@ "Save_to_enable_this_action": "Uložte pro povolení akce", "Saved": "Uloženo", "Saving": "Ukládání", + "Scan_QR_code": "V autentizační aplikace jako Google Authenticator, Authy nebo Duo naskenujte QR kód. Aplikace vám poté zobrazí 6-ti místný kód, který zadejte níže.", "Scope": "Rozsah", "Screen_Share": "Sdílení obrazovky", "Script_Enabled": "Skript Povolen", @@ -1285,6 +1316,7 @@ "Search_Private_Groups": "Vyhledávání soukromých skupin", "seconds": "sekundy", "Secret_token": "Tajný token", + "Security": "Zabezpečení", "Select_a_department": "Vyberte oddělení", "Select_a_user": "Vyberte uživatele", "Select_an_avatar": "Vyberte avatar", @@ -1312,6 +1344,7 @@ "Sending": "Odesílání ...", "Served_By": "Poskytovatel", "Service": "Služba", + "Service_account_key": "Klíč účty služby", "Set_as_moderator": "Nastavit jako moderátora", "Set_as_owner": "Nastavit jako vlastníka", "Settings": "Nastavení", @@ -1478,6 +1511,10 @@ "This_is_a_push_test_messsage": "Toto je testovací notifikace", "This_room_has_been_archived_by__username_": "Tato místnost byla archivována uživatelem __username__", "This_room_has_been_unarchived_by__username_": "Tato místnost byla úspěšně odarchivována uživatelem __username__", + "Two-factor_authentication": "Dvoufázové ověření", + "Two-factor_authentication_disabled": "Dvoufázová ověření zakázáno", + "Two-factor_authentication_enabled": "Dvoufázové ověření povoleno", + "Two-factor_authentication_is_currently_disabled": "Dvoufázová ověření je momentálně zakázáno", "Thursday": "Čtvrtek", "Time_in_seconds": "Čas v sekundách", "Title": "Název", @@ -1508,13 +1545,14 @@ "UI_DisplayRoles": "Zobrazit Role", "UI_Merge_Channels_Groups": "Sloučit privátní skupiny s místnostmi", "UI_Use_Name_Avatar": "Použít iniciály celého jména uživatele pro výchozí avatar", + "UI_Use_Real_Name": "Použít skutečné jméno", "Unarchive": "Zrušit archivaci", "Unblock_User": "Odblokovat uživatele", "Unmute_someone_in_room": "Zrušit ztlumení někoho v místnosti", "Unmute_user": "Zrušit ztlumení uživatele", "Unnamed": "Nepojmenovaný", "Unpin_Message": "Odepnout Zprávu", - "Unread_Alert": "Nepřečtené upozornění", + "Unread_Tray_Icon_Alert": "Ikona v oznamovací oblasti upozorňuje na nepřečtené zprávy", "Unread_Messages": "Nepřečtěné zprávy", "Unread_Rooms": "Nepřečtené místnosti", "Unread_Rooms_Mode": "Mód Nepřečtených místností", @@ -1599,6 +1637,7 @@ "Verification_Email_Subject": "[Site_Name] - Potvrďte svůj účet", "Verification_Email": "Klikněte na tento odkaz pro potvrzení svého účtu.", "Verified": "Ověřený", + "Verify": "Ověřit", "Version": "Verze", "Video_Chat_Window": "Video chat", "Video_Conference": "Video konference", @@ -1653,6 +1692,7 @@ "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Webhooky můžete použít pro jednoduchou integraci s Vaším CRM", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Livechat místnost nelze opustit. Použijte prosím tlačítko zavřít.", "You_have_been_muted": "Byli jste ztišeni a nemůžete v této místnosti mluvit", + "You_have_n_codes_remaining": "Zbývá vám __number__ kódů.", "You_have_not_verified_your_email": "Neověřili jste svůj e-mail.", "You_have_successfully_unsubscribed": "Úspěšně jste se odhlásili z našeho seznamu.", "You_must_join_to_view_messages_in_this_channel": "Pro zobrazení zpráv v této místnosti je třeba se připojit", diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json index d342f6c1d80..95bf6f01167 100644 --- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -1127,7 +1127,6 @@ "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", diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index dc42be722c0..24e7b29e401 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -62,6 +62,10 @@ "Accounts_OAuth_Custom_Token_Path": "Pfad des Token", "Accounts_OAuth_Custom_Token_Sent_Via": "Token gesendet über", "Accounts_OAuth_Custom_Username_Field": "Benutzernamen Feld", + "Accounts_OAuth_Drupal": "Login mit Drupal aktiviert", + "Accounts_OAuth_Drupal_callback_url": "Drupal oAuth Redirect Url", + "Accounts_OAuth_Drupal_id": "Drupal oAuth2 Client ID", + "Accounts_OAuth_Drupal_secret": "Drupal oAuth2 Client Schlüssel", "Accounts_OAuth_Facebook": "Anmeldung über Facebook erlauben", "Accounts_OAuth_Facebook_callback_url": "Facebook-Callback-URL", "Accounts_OAuth_Facebook_id": "Facebook-App-ID", @@ -155,25 +159,35 @@ "Analytics_features_messages_Description": "Zeichnet benutzerdefinierte Ereignisse im Zusammenhang mit Aktionen eines Nutzer auf Nachrichten auf.", "Analytics_features_rooms_Description": "Zeichnet benutzerdefinierte Ereignisse im Zusammenhang mit Aktionen in einem Kanal oder einer Gruppe (erstellen, verlassen, löschen) auf.", "Analytics_features_users_Description": "Zeichnet benutzerdefinierte Ereignisse (Passwort-Reset-Zeiten, Profilbild ändern, etc) auf.", + "Analytics_Google": "Google Analytics", + "Analytics_Google_id": "Tracking ID", "and": "und", "And_more": "Und __length__ mehr", "Animals_and_Nature": "Tiere & Natur", + "Announcement": "Ankündigung", "API": "API", "API_Allow_Infinite_Count_Description": "Erlaube Rückgabe von REST API Ergebnissen in einem einzigen Abruf", "API_Analytics": "Analytics", "API_CORS_Origin": "CORS Origin", "API_Default_Count": "Standard Anzahl", + "API_Default_Count_Description": "Anzahl an Ergebnissen von REST API Anfragen wenn keine Anzahl übergeben wurde.", + "API_Drupal_URL": "Drupal Server URL", + "API_Drupal_URL_Description": "Beispiel: https://domain.de (ohne schließenden /)", "API_Embed": "Einbetten", + "API_Embed_Description": "Eingebettete Link Vorschau für Links die von Benutzern gepostet wurden aktiv.", "API_EmbedDisabledFor": "Einbettungen für Benutzer deaktivieren", "API_EmbedDisabledFor_Description": "Durch Kommata getrennte Liste von Benutzernamen", "API_EmbedIgnoredHosts": "Ignorierte Hosts einbetten", "API_EmbedIgnoredHosts_Description": "Kommagetrennte Liste von Hosts oder CIDR-Adressen, z. B. localhost, 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16", "API_EmbedSafePorts": "Sichere Ports", "API_EmbedSafePorts_Description": "Kommagetrennte Liste der Ports, für die eine Vorschau erlaubt ist.", + "API_Enable_CORS": "Aktiviere CORS", "API_GitHub_Enterprise_URL": "Server-URL", "API_GitHub_Enterprise_URL_Description": "Beispiel: http://domain.com (ohne Schrägstrich am Ende)", "API_Gitlab_URL": "GitLab-URL", "API_Token": "API-Token", + "API_Upper_Count_Limit": "Max. Anzahl an Einträgen", + "API_Upper_Count_Limit_Description": "Max. Anzahl an Einträgen die die REST API zurückliefen soll (sofern ohne Einschränkung)? ", "API_User_Limit": "User Limit für das Hinzufügen aller Benutzer auf Kanal", "API_Wordpress_URL": "Wordpress-URL", "Apiai_Key": "Api.ai-Schlüssel", @@ -205,6 +219,9 @@ "AutoLinker_Urls_TLD": "AutoLinker TLD-URLs", "AutoLinker_Urls_www": "AutoLinker \"www\"-URLs", "AutoLinker_UrlsRegExp": "AutoLinker RegExp für URLs", + "Automatic_Translation": "Automatische Übersetzung", + "Auto_Translate": "Auto-Übersetzung", + "AutoTranslate_Enabled": "Aktiviere die Automatische Übersetzung", "Available": "Verfügbar", "Available_agents": "Verfügbare Agenten", "Avatar": "Profilbild", @@ -235,18 +252,26 @@ "busy_male": "beschäftigt", "Busy_male": "Beschäftigt", "by": "durch", + "Content": "Inhalt", + "cache_cleared": "Zwischenspeicher gelöscht", "Cancel": "Abbrechen", "Cancel_message_input": "Abbrechen", "Cannot_invite_users_to_direct_rooms": "Benutzer können nicht in private Nachrichtenräume eingeladen werden.", "CAS_button_color": "Hintergrundfarbe des Login-Buttons", "CAS_button_label_color": "Farbe des Login-Button-Texts", "CAS_button_label_text": "Text des Login-Buttons", + "CAS_enabled": "Aktiviert", + "CAS_popup_height": "Höhe des Login Pop Up", + "CAS_popup_width": "Breite des Login Pop Up", + "CAS_Sync_User_Data_Enabled": "Benutzerdaten immer synchronisieren", "CDN_PREFIX": "CDN-Präfix", "Certificates_and_Keys": "Zertifikate und Schlüssel", "Changing_email": "E-Mail-Adresse ändern", + "Change_Room_Type": "Ändere den Raum Typ", "channel": "Kanal", "Channel": "Kanal", "Channel_already_exist": "Der Kanal '#%s' ist bereits vorhanden.", + "Channel_created": "Channel `#%s` angelegt.", "Channel_already_Unarchived": "Kanal mit dem Namen '#%s' ist bereits im unarchivierten Zustand", "Channel_Archived": "Kanal mit dem Namen '#%s' wurde erfolgreich archiviert", "Channel_doesnt_exist": "Der Kanal `#%s` existiert nicht.", @@ -264,10 +289,12 @@ "Choose_messages": "Nachrichten auswählen", "Choose_the_alias_that_will_appear_before_the_username_in_messages": "Wählen Sie den Alias, der vor dem Benutzernamen in den Nachrichten angezeigt wird.", "Choose_the_username_that_this_integration_will_post_as": "Wählen Sie den Benutzernamen, der die Integration veröffentlicht.", + "clear": "löschen", + "clear_cache_now": "Zwischenspeicher jetzt leeren", "Clear_all_unreads_question": "Möchten Sie alle ungelesenen Nachrichten löschen?", "Click_here": "Hier klicken", "Client_ID": "Client-ID", - "Client_Secret": "Client-Secret", + "Client_Secret": "Client-Schlüssel", "Clients_will_refresh_in_a_few_seconds": "Clients werden in wenigen Sekunden aktualisiert", "close": "Schließen", "Close": "Schließen", @@ -293,6 +320,8 @@ "Create_new": "Neu erstellen", "Created_at": "Erstellt am", "Created_at_s_by_s": "Erstellt am %s von %s", + "CROWD_Reject_Unauthorized": "Unauthorisierte ablehnen.", + "CRM_Integration": "CRM Integration", "Current_Chats": "Aktuelle Chats", "Custom": "Benutzerdefiniert", "Custom_Emoji": "Benutzerdefinierte Emoji", @@ -380,6 +409,7 @@ "Enter_a_regex": "Regex eingeben", "Enter_a_room_name": "Raumnamen eingeben", "Enter_a_username": "Benutzernamen eingeben", + "Enter_Behaviour": "Verhalten der Enter-Taste:", "Enter_name_here": "Namen hier eingeben", "Enter_to": "Enter-Taste: ", "Error": "Fehler", @@ -1042,7 +1072,7 @@ "Selected_agents": "Ausgewählte Agenten", "Send": "Senden", "Send_a_message": "Eine Nachricht schicken", - "Send_a_test_mail_to_my_user": "Eine Test-E-Mail an die Benutzer senden", + "Send_a_test_mail_to_my_user": "Eine Test-E-Mail an mich senden", "Send_a_test_push_to_my_user": "Eine Test-Push-Nachricht an mich senden", "Send_confirmation_email": "Bestätigungsmail versenden", "Send_data_into_RocketChat_in_realtime": "Daten in Rocket.Chat in Echtzeit senden.", @@ -1059,7 +1089,7 @@ "Sending": "Senden...", "Service": "Service", "Set_as_moderator": "Zum Moderator ernennen", - "Set_as_owner": "zum Besitzer machen", + "Set_as_owner": "Zum Besitzer machen", "Settings": "Einstellungen", "Settings_updated": "Die Einstellungen wurden aktualisiert.", "Share_Location_Title": "Standort teilen?", @@ -1203,7 +1233,6 @@ "Unmute_user": "Benutzern das Chatten erlauben ", "Unnamed": "Unbenannt", "Unpin_Message": "Nachicht nicht mehr anheften", - "Unread_Alert": "Über Ungelesenes benachrichtigen", "Unread_Rooms": "Ungelesene Räume", "Unread_Rooms_Mode": "Ungelesene Räume aufgelistet anzeigen ", "Unstar_Message": "Markierung entfernen", diff --git a/packages/rocketchat-i18n/i18n/el.i18n.json b/packages/rocketchat-i18n/i18n/el.i18n.json index b3edc3db552..d4046ac1362 100644 --- a/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/packages/rocketchat-i18n/i18n/el.i18n.json @@ -47,7 +47,8 @@ "Accounts_OAuth_Custom_Enable": "Καθιστώ ικανό", "Accounts_OAuth_Custom_id": "Ταυτότητα", "Accounts_OAuth_Custom_Identity_Path": "Path ταυτότητα", - "Accounts_OAuth_Custom_Login_Style": "Είσοδος Style", + "Accounts_OAuth_Custom_Login_Style": "Τρόπος Εισόδου", + "Accounts_OAuth_Custom_Merge_Users": "Συγχώνευση χρηστ", "Accounts_OAuth_Custom_Secret": "Μυστικό", "Accounts_OAuth_Custom_Token_Path": "Token Path", "Accounts_OAuth_Custom_Token_Sent_Via": "Token αποστέλλονται μέσω", @@ -71,8 +72,8 @@ "Accounts_OAuth_Google_callback_url": "Google επανάκλησης URL", "Accounts_OAuth_Google_id": "Google Id", "Accounts_OAuth_Google_secret": "Google Secret", - "Accounts_OAuth_Linkedin": "LinkedIn Login", - "Accounts_OAuth_Linkedin_callback_url": "Linkedin επανάκλησης URL", + "Accounts_OAuth_Linkedin": "LinkedIn Είσοδος", + "Accounts_OAuth_Linkedin_callback_url": "Linkedin URL Επανάκλησης", "Accounts_OAuth_Linkedin_id": "LinkedIn Id", "Accounts_OAuth_Linkedin_secret": "LinkedIn Secret", "Accounts_OAuth_Meteor": "Meteor Login", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 3dfe8c4b2d5..1d18d31ef92 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -17,6 +17,8 @@ "Accessing_permissions": "Accessing permissions", "Account_SID": "Account SID", "Accounts": "Accounts", + "Accounts_AllowAnonymousRead": "Allow anonymous read", + "Accounts_AllowAnonymousWrite": "Allow anonymous write", "Accounts_AllowDeleteOwnAccount": "Allow users to delete own account", "Accounts_AllowedDomainsList": "Allowed Domains List", "Accounts_AllowedDomainsList_Description": "Comma-separated list of allowed domains", @@ -34,6 +36,7 @@ "Accounts_BlockedUsernameList": "Blocked Username List", "Accounts_BlockedUsernameList_Description": "Comma-separated list of blocked usernames (case-insensitive)", "Accounts_CustomFields_Description": "Should be a valid JSON where keys are the field names containing a dictionary of field settings. Example:
    {\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n} ", + "Accounts_DefaultUsernamePrefixSuggestion": "Default username prefix suggestion", "Accounts_denyUnverifiedEmail": "Deny unverified email", "Accounts_EmailVerification": "Email Verification", "Accounts_EmailVerification_Description": "Make sure you have correct SMTP settings to use this feature", @@ -62,6 +65,10 @@ "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_Drupal": "Drupal Login Enabled", + "Accounts_OAuth_Drupal_callback_url": "Drupal oAuth2 Redirect URI", + "Accounts_OAuth_Drupal_id": "Drupal oAuth2 Client ID", + "Accounts_OAuth_Drupal_secret": "Drupal oAuth2 Client Secret", "Accounts_OAuth_Facebook": "Facebook Login", "Accounts_OAuth_Facebook_callback_url": "Facebook Callback URL", "Accounts_OAuth_Facebook_id": "Facebook App Id", @@ -170,6 +177,8 @@ "API_CORS_Origin": "CORS Origin", "API_Default_Count": "Default Count", "API_Default_Count_Description": "The default count for REST API results if the consumer did not provided any.", + "API_Drupal_URL": "Drupal Server URL", + "API_Drupal_URL_Description": "Example: https://domain.com (excluding trailing slash)", "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", @@ -410,6 +419,7 @@ "Desktop_Notifications_Enabled": "Desktop Notifications are Enabled", "Direct_message_someone": "Direct message someone", "Direct_Messages": "Direct Messages", + "Disable_Notifications": "Disable Notifications", "Disable_two-factor_authentication": "Disable two-factor authentication", "Display_offline_form": "Display offline form", "Displays_action_text": "Displays action text", @@ -638,6 +648,7 @@ "Hide_room": "Hide room", "Hide_Room_Warning": "Are you sure you want to hide the room \"%s\"?", "Hide_roles": "Hide roles", + "Hide_Unread_Room_Status": "Hide Unread Room Status", "Hide_usernames": "Hide usernames", "Highlights": "Highlights", "Highlights_How_To": "To be notified when someone mentions a word or phrase, add it here. You can separate words or phrases with commas. Highlight Words are not case sensitive.", @@ -659,11 +670,11 @@ "Iframe_Integration_receive_enable": "Enable Receive", "Iframe_Integration_receive_enable_Description": "Allow parent window to send commands to Rocket.Chat.", "Iframe_Integration_receive_origin": "Receive origins", - "Iframe_Integration_receive_origin_Description": "Only pages with given origin will be able to send commands or `*` for all origins. You can use multiple values separated by `,`. Example `http://localhost,https://localhost`", + "Iframe_Integration_receive_origin_Description": "Origins with protocol prefix, separated by commas, which are allowed to receive commands e.g. 'https://localhost, http://localhost', or * to allow receiving from anywhere.", "Iframe_Integration_send_enable": "Enable Send", "Iframe_Integration_send_enable_Description": "Send events to parent window", "Iframe_Integration_send_target_origin": "Send target origin", - "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`", + "Iframe_Integration_send_target_origin_Description": "Origin with protocol prefix, which commands are sent to e.g. 'https://localhost', or * to allow sending to anywhere.", "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:", @@ -730,6 +741,8 @@ "Integration_Retry_Count_Description": "How many times should the integration be tried if the call to the url fails?", "Integration_Retry_Delay": "Retry Delay", "Integration_Retry_Delay_Description": "Which delay algorithm should the retrying use? 10^x or 2^x or x*2", + "Integration_Run_When_Message_Is_Edited": "Run On Edits", + "Integration_Run_When_Message_Is_Edited_Description": "Should the integration run when the message is edited? Setting this to false will cause the integration to only run on new messages.", "Integration_Word_Trigger_Placement": "Word Placement Anywhere", "Integration_Word_Trigger_Placement_Description": "Should the Word be Triggered when placed anywhere in the sentence other than the beginning?", "Integration_updated": "Integration has been updated.", @@ -968,9 +981,9 @@ "Message_AllowUnrecognizedSlashCommand": "Allow Unrecognized Slash Commands", "Message_AlwaysSearchRegExp": "Always search using RegExp", "Message_AlwaysSearchRegExp_Description": "We recommend to set `True` if your language is not supported on MongoDB text search.", - "Message_Attachments" : "Message Attachments", - "Message_Attachments_GroupAttach" : "Group Attachment Buttons", - "Message_Attachments_GroupAttachDescription" : "This groups the icons under an expandable menu. Takes up less screen space.", + "Message_Attachments": "Message Attachments", + "Message_Attachments_GroupAttach": "Group Attachment Buttons", + "Message_Attachments_GroupAttachDescription": "This groups the icons under an expandable menu. Takes up less screen space.", "Message_AudioRecorderEnabled": "Audio Recorder Enabled", "Message_AudioRecorderEnabledDescription": "Requires 'audio/wav' files to be an accepted media type within 'File Upload' settings.", "Message_BadWordsFilterList": "Add bad words to the blacklist", @@ -1074,6 +1087,7 @@ "Nothing_found": "Nothing found", "Notification_Duration": "Notification Duration", "Notifications": "Notifications", + "Notifications_Muted_Description": "If you choose to mute everything, you won't see the room highlight in the list when there are new messages, except for mentions. Muting notifications will override notifications settings.", "Notify_all_in_this_room": "Notify all in this room", "Notify_active_in_this_room": "Notify active users in this room", "Num_Agents": "# Agents", @@ -1112,6 +1126,7 @@ "or": "or", "Open_your_authentication_app_and_enter_the_code": "Open your authentication app and enter the code. You can also use one of your backup codes.", "Order": "Order", + "Or_talk_as_anonymous": "Or talk as anonymous", "OS_Arch": "OS Arch", "OS_Cpus": "OS CPU Count", "OS_Freemem": "OS Free Memory", @@ -1350,6 +1365,7 @@ "Showing_archived_results": "

    Showing %s archived results

    ", "Showing_online_users": "Showing: __total_showing__, Online: __online__, Total: __total__ users", "Showing_results": "

    Showing %s results

    ", + "Sign_in_to_start_talking": "Sign in to start talking", "since_creation": "since %s", "Site_Name": "Site Name", "Site_Url": "Site URL", @@ -1532,14 +1548,14 @@ "UI_DisplayRoles": "Display Roles", "UI_Merge_Channels_Groups": "Merge private groups with channels", "UI_Use_Name_Avatar": "Use full name initials to generate default avatar", - "UI_Use_Real_Name" : "Use Real Name", + "UI_Use_Real_Name": "Use Real Name", "Unarchive": "Unarchive", "Unblock_User": "Unblock User", "Unmute_someone_in_room": "Unmute someone in the room", "Unmute_user": "Unmute user", "Unnamed": "Unnamed", "Unpin_Message": "Unpin Message", - "Unread_Alert": "Unread Alert", + "Unread_Tray_Icon_Alert": "Unread Tray Icon Alert", "Unread_Messages": "Unread Messages", "Unread_Rooms": "Unread Rooms", "Unread_Rooms_Mode": "Unread Rooms Mode", @@ -1703,4 +1719,4 @@ "your_message_optional": "your message (optional)", "Your_password_is_wrong": "Your password is wrong!", "Your_push_was_sent_to_s_devices": "Your push was sent to %s devices" -} +} \ No newline at end of file diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json index 9d2ccf453c8..e5dceb0f426 100644 --- a/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/packages/rocketchat-i18n/i18n/es.i18n.json @@ -578,6 +578,7 @@ "Importer_Prepare_Uncheck_Deleted_Users": "Los usuarios desmarque eliminados", "Importer_progress_error": "No se pudo obtener el progreso de la importación.", "Importer_setup_error": "Se produjo un error al configurar el importador.", + "Importer_Source_File": "Selección del archivo de origen", "Incoming_Livechats": "LiveChats entrantes", "inline_code": "inline_code", "Install_Extension": "Instalar extension", @@ -588,9 +589,14 @@ "Installation": "Instalación ", "Installed_at": "instalado en", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instrucciones a sus visitantes llenan el formulario para enviar un mensaje", + "Integration_Advanced_Settings": "Ajustes Avanzados", "Integration_added": "La Integración ha sido añadida", "Integration_Incoming_WebHook": "Integración WebHook de Entrada", "Integration_New": "Nueva Integración", + "Integrations_Outgoing_Type_FileUploaded": "Archivo Subido", + "Integrations_Outgoing_Type_RoomArchived": "chat archivado", + "Integrations_Outgoing_Type_RoomCreated": "chat creado(público y privado)", + "Integrations_Outgoing_Type_RoomJoined": "Usuario se ha unido al chat", "Integration_Outgoing_WebHook": "Integración web hook saliente", "Integration_updated": "La Integración ha sido actualizada", "Integrations": "Integraciones", @@ -607,14 +613,18 @@ "Invalid_pass": "La contraseña no debe estar vacia", "Invalid_room_name": "%s no es un nombre de sala válido,
    use solo letras, números y guiones", "Invalid_secret_URL_message": "El URL proporcionado es invalido.", + "Invalid_setting_s": "Configuración no válida: %s", "invisible": "invisible", "Invisible": "Invisible", + "Invitation": "Invitación", "Invitation_HTML": "HTML de la Invitación", "Invitation_HTML_Default": "

    Se le ha invitado a

    [Site_Name]

    Ir a [Site_URL] y probar la mejor solución de chat de código abierto disponibles en la actualidad!

    ", "Invitation_HTML_Description": "Es posible utilizar los siguientes marcadores:
    ", "Invitation_Subject": "Asunto de la Invitación", "Invitation_Subject_Default": "Se le ha invitado a [Site_Name]", "Invite_user_to_join_channel": "Invitar a un usuario a unirse a este canal", + "Invite_user_to_join_channel_all_to": "Invitar a todos los usuarios de este canal a unirse a [#channel]", + "Invite_user_to_join_channel_all_from": "Invitar a todos los usuarios de [#channell] para unirse a este canal", "Invite_Users": "Invitar Usuarios", "is_also_typing": "también esta escribiendo", "is_also_typing_female": "tambien esta escribiendo", @@ -622,6 +632,8 @@ "is_typing": "está escribiendo", "is_typing_female": "esta escribiendo", "is_typing_male": "esta escribiendo", + "IRC_Channel_Join": "Salida del comando JOIN", + "IRC_Channel_Leave": "Salida del comando PART", "It_works": "Funciona", "italics": "cursiva", "Jitsi_Chrome_Extension": "Id extensión de Chrome", @@ -660,6 +672,7 @@ "Layout_Terms_of_Service": "Términos del Servicio", "LDAP": "LDAP", "LDAP_CA_Cert": "CA Cert", + "LDAP_Connect_Timeout": "Tiempo de espera de conexión(ms)", "LDAP_Custom_Domain_Search": "Dominio Personalizado de Busqueda", "LDAP_Custom_Domain_Search_Description": "Un pedazo de JSON que gobierna se unen y la conexión y la información es de la forma:
    {\"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\"}", "LDAP_Default_Domain": "Dominio Predeterminado", @@ -1214,7 +1227,6 @@ "Unmute_user": "Des-silenciar usuario", "Unnamed": "Sin nombre", "Unpin_Message": "Desfijar Mensaje", - "Unread_Alert": "Alerta de no leídos", "Unread_Rooms": "Salas sin leer", "Unread_Rooms_Mode": "Modo Salas sin leer", "Unstar_Message": "Eliminar Destacado", diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index 46f42480760..7d9b43e0c9f 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -62,22 +62,23 @@ "Accounts_OAuth_Custom_Token_Path": "URL de Token", "Accounts_OAuth_Custom_Token_Sent_Via": "Jeton envoyé via", "Accounts_OAuth_Custom_Username_Field": "Champ nom d'utilisateur", + "Accounts_OAuth_Drupal_secret": "Drupal oAuth2 Client Secret", "Accounts_OAuth_Facebook": "Connexion avec Facebook", "Accounts_OAuth_Facebook_callback_url": "Facebook URL de Callback", "Accounts_OAuth_Facebook_id": "ID Facebook App ", "Accounts_OAuth_Facebook_secret": "Facebook Secret", - "Accounts_OAuth_Github": "Connexion avec GitHub", + "Accounts_OAuth_Github": "OAuth activé", "Accounts_OAuth_Github_callback_url": "GitHub URL de Callback", "Accounts_OAuth_GitHub_Enterprise": "OAuth activé", "Accounts_OAuth_GitHub_Enterprise_callback_url": "GitHub Entreprise URL de Callback", - "Accounts_OAuth_GitHub_Enterprise_id": "GitHub Entreprise ID", - "Accounts_OAuth_GitHub_Enterprise_secret": "GitHub Entreprise Secret", - "Accounts_OAuth_Github_id": "ID client", - "Accounts_OAuth_Github_secret": "GitHub Secret", + "Accounts_OAuth_GitHub_Enterprise_id": "ID Client", + "Accounts_OAuth_GitHub_Enterprise_secret": "Secret Client", + "Accounts_OAuth_Github_id": "ID Client", + "Accounts_OAuth_Github_secret": "Client Secret", "Accounts_OAuth_Gitlab": "OAuth activé", "Accounts_OAuth_Gitlab_callback_url": "GitLab URL de Callback", "Accounts_OAuth_Gitlab_id": "ID GitLab", - "Accounts_OAuth_Gitlab_secret": "Mot de passe client", + "Accounts_OAuth_Gitlab_secret": "Client Secret", "Accounts_OAuth_Google": "Connexion avec Google", "Accounts_OAuth_Google_callback_url": "Google URL de Callback", "Accounts_OAuth_Google_id": "ID Google ", @@ -124,11 +125,11 @@ "Add": "Ajouter", "Add_agent": "Ajouter d'un assistant", "Add_custom_oauth": "Ajouter un OAuth personnalisé", - "Add_Domain": "Ajouter domaine", + "Add_Domain": "Ajouter un domaine", "Add_manager": "Ajouter un manager", "Add_user": "Ajouter un utilisateur", "Add_User": "Ajouter un utilisateur", - "Add_users": "Ajouter plusieurs utilisateurs", + "Add_users": "Ajouter des utilisateurs", "Adding_OAuth_Services": "Ajout de services OAuth", "Adding_permission": "Ajout d'une permission", "Adding_user": "Ajout d'un utilisateur", @@ -140,8 +141,8 @@ "Agent_added": "Assistant ajouté", "Agent_removed": "Assistant supprimé", "Alias": "Alias", - "Alias_Format": "format d'alias", - "Alias_Format_Description": "Importer les messages de Slack avec un alias; %s est remplacé par le nom d'utilisateur. Si laissé vide, aucun alias ne sera utilisé.", + "Alias_Format": "Format d'alias", + "Alias_Format_Description": "Importer les messages de Slack avec un alias ; %s est remplacé par le nom d'utilisateur. Si laissé vide, aucun alias ne sera utilisé.", "Alias_Set": "Définir alias", "All": "Tous", "All_channels": "Tous les canaux", @@ -151,7 +152,7 @@ "Allow_Invalid_SelfSigned_Certs_Description": "Autoriser les certificats SSL invalides et auto-signés pour la validation des liens et leurs prévisualisations.", "Allow_switching_departments": "Autoriser les visiteurs a changé de département", "Always_open_in_new_window": "Toujours ouvrir dans une nouvelle fenêtre", - "Analytics_features_enabled": "Fonctionnalités activées.", + "Analytics_features_enabled": "Fonctionnalités activées", "Analytics_features_messages_Description": "Suivre les événements personnalisés liés aux actions d'un utilisateur sur des messages.", "Analytics_features_rooms_Description": "Suivre les événements personnalisés liés aux actions sur un canal ou un groupe (créer, quitter, supprimer).", "Analytics_features_users_Description": "Suivre les événements personnalisés liés à des actions liées aux utilisateurs (nombre de réinitialisations de mot de passe, changements d'image de profil, etc.).", @@ -159,23 +160,26 @@ "and": "et", "And_more": "et encore __length__", "Animals_and_Nature": "Faune et flore", + "Announcement": "Annonce", "API": "API", "API_Allow_Infinite_Count": "Autoriser à tout recevoir", "API_Allow_Infinite_Count_Description": "Autoriser les appels REST API à tout retourner en une seule réponse ?", "API_Analytics": "Métriques", "API_Default_Count": "Quantité par défaut", - "API_Embed": "Intégration", - "API_Embed_Description": "Détermine si les prévisualisations de liens sont activées ou non lorsqu'un utilisateur publie une URL.", + "API_Drupal_URL": "Drupal URL du serveur", + "API_Embed": "Aperçu des liens intégrés", + "API_Embed_Description": "Détermine si les prévisualisations de liens sont activées ou non lorsqu'un utilisateur publie un lien vers un site web.", "API_EmbedCacheExpirationDays": "Durée d'expiration du cache embarqué (en jours)", "API_EmbedDisabledFor": "Désactiver l'intégration pour les utilisateurs", - "API_EmbedDisabledFor_Description": "Liste de noms d'utilisateurs séparés par des virgules", + "API_EmbedDisabledFor_Description": "Liste de noms d'utilisateurs séparés par des virgules pour désactiver les aperçus de liens intégrés.", "API_EmbedIgnoredHosts": "Hôtes à ne pas intégrer", "API_EmbedIgnoredHosts_Description": "Liste des hôtes ou des adresses CIDR, séparés par des virgules. Par exemple : localhost, 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16", "API_EmbedSafePorts": "Ports approuvés", "API_EmbedSafePorts_Description": "Liste des ports autorisés pour la prévisualisation, séparés par des virgules.", "API_Enable_CORS": "Activer CORS", + "API_Enable_Direct_Message_History_EndPoint_Description": "Ceci active `/api/v1/im.history.others`qui permet la visualisation des messages privés envoyés par tous les utilisateurs.", "API_GitHub_Enterprise_URL": "URL du serveur", - "API_GitHub_Enterprise_URL_Description": "Exemple: http://domain.com (sans slash final)", + "API_GitHub_Enterprise_URL_Description": "Exemple : http://domain.com (sans slash final)", "API_Gitlab_URL": "URL GitLab", "API_Token": "Jeton API", "API_Upper_Count_Limit": "Nombre maximum d'enregistrements", @@ -218,7 +222,7 @@ "AutoTranslate_Enabled_Description": "Activer la traduction automatique permettra aux utilisateurs disposant de la permission auto-translate d'avoir l'ensemble des messages traduits automatiquement dans leur langue. Des frais peuvent s'appliquer, voir la documentation Google", "Available": "Disponible", "Available_agents": "Assistants disponibles", - "Avatar": "Modifier l'avatar", + "Avatar": "Avatar", "Avatar_changed_successfully": "Avatar modifié avec succès", "Avatar_URL": "URL de l'avatar", "Avatar_url_invalid_or_error": "L'URL est invalide ou non accessible. Essayez de nouveau, mais avec une URL différente.", @@ -239,7 +243,7 @@ "bold": "gras", "bot_request": "Requête de bot", "BotHelpers_userFields": "Champs utilisateur", - "BotHelpers_userFields_Description": "CSV des champs utilisateurs accessibles par les méthodes d'aide des bots", + "BotHelpers_userFields_Description": "CSV des champs utilisateurs accessibles par les méthodes d'aide des bots.", "Branch": "Branche", "Bugsnag_api_key": "Clef d'API Bugsnag", "busy": "occupé", @@ -250,10 +254,10 @@ "Busy_male": "Occupé", "by": "par", "Content": "Contenu", - "cache_cleared": "Effacer le cache", + "cache_cleared": "Cache effacé", "Cancel": "Annuler", "Cancel_message_input": "Annuler", - "Cannot_invite_users_to_direct_rooms": "Impossible d'inviter des utilisateurs dans les salons privés", + "Cannot_invite_users_to_direct_rooms": "Impossible d'inviter des utilisateurs dans les discussions privées", "CAS_autoclose": "Fermer automatiquement la fenêtre popup de connexion", "CAS_base_url": "URL de base pour SSO", "CAS_base_url_Description": "URL de base pour votre service externe de connexion SSO, par exemple: http://sso.example.com/sso/", @@ -262,32 +266,32 @@ "CAS_button_label_text": "Bouton de connexion, texte / label", "CAS_enabled": "Activé", "CAS_login_url": "URL de login SSO", - "CAS_login_url_Description": "URL de connexion pour votre service externe de connexion SSO, par exemple: http://sso.example.com/sso/", + "CAS_login_url_Description": "URL de connexion pour votre service externe de connexion SSO, par exemple : http://sso.example.com/sso/", "CAS_popup_height": "Hauteur de la fenêtre popup de connexion", "CAS_popup_width": "Largeur de la fenêtre popup de connexion", "CAS_Sync_User_Data_Enabled": "Toujours synchroniser les données utilisateur", - "CAS_Sync_User_Data_Enabled_Description": "Toujours synchroniser les données externes de l'utilisateur CAS vers les attributs disponibles après connexion. Note : dans tous les cas, les attributs sont toujours synchronisés après la création du compte.", + "CAS_Sync_User_Data_Enabled_Description": "Toujours synchroniser les données externes de l'utilisateur CAS avec les attributs disponibles après connexion. Note : dans tous les cas, les attributs sont toujours synchronisés après la création du compte.", "CAS_Sync_User_Data_FieldMap": "Attributs de carte", "CAS_version": "Version CAS", "CAS_version_Description": "Utiliser seulement une version de CAS supportée par votre service CAS SSO", "CDN_PREFIX": "Préfixe CDN", "Certificates_and_Keys": "Certificats et clés", "Changing_email": "Modification des adresses e-mail", - "Change_Room_Type": "Changer le type de salon", + "Change_Room_Type": "Modification du type de salon", "channel": "canal", "Channel": "Canal", "Channel_already_exist": "Le canal '#% s' existe déjà.", "Channel_created": "Canal `#%s` créé", - "Channel_already_Unarchived": "Canal avec le nom `#% s` est déjà dans l'état Désarchivées", - "Channel_Archived": "Canal avec le nom `#% s` a été archivé avec succès", + "Channel_already_Unarchived": "Canal avec le nom `#%s` est déjà dans l'état désarchivé", + "Channel_Archived": "Canal avec le nom `#%s` a été archivé avec succès", "Channel_doesnt_exist": "Le canal `#%s` n'existe pas.", - "Channel_Unarchived": "Canal avec le nom `#% s` a été plus archivée", + "Channel_Unarchived": "Canal avec le nom `#%s` a été désarchivé avec succès", "Channels": "Canaux", "Channels_list": "Liste des canaux publics", "Chat_button": "Bouton chat", "Chat_closed": "Chat fermé", "Chat_closed_successfully": "Chat fermé avec succès", - "Chat_window": "fenêtre de chat", + "Chat_window": "Fenêtre de chat", "Chatops_Enabled": "Activer Chatops", "Chatops_Title": "Panneau de contrôle des Chatops", "Chatops_Username": "Nom d'utilisateur Chatops", @@ -301,10 +305,10 @@ "Click_here": "Cliquez ici", "Click_here_for_more_info": "Cliquer ici pour plus d'information", "Client_ID": "ID du client", - "Client_Secret": "Secret du client", + "Client_Secret": "Secret Client", "Clients_will_refresh_in_a_few_seconds": "Les clients vont être rechargés dans quelques secondes", "close": "fermer", - "Close": "Fermer", + "Close": "Fermeture", "Closed": "Fermé", "Closed_by_visitor": "Fermé par le visiteur", "Closing_chat": "Fermeture du chat", @@ -316,7 +320,7 @@ "Confirm_password": "Confirmez votre mot de passe", "Conversation": "Conversation", "Conversation_closed": "Conversation fermée : __comment__.", - "Convert_Ascii_Emojis": "Convertir le code ASCII en Emoji", + "Convert_Ascii_Emojis": "Convertir le code ASCII en émoticône", "Copied": "Copié", "Copy": "Copier", "Copy_to_clipboard": "Copier dans le presse-papier", @@ -324,45 +328,54 @@ "Count": "Nombre", "Cozy": "Confortable", "Create": "Créer", - "Create_A_New_Channel": "Créer une nouvelle chaîne", + "Create_A_New_Channel": "Créer un nouveau canal", "Create_new": "Créer nouveau", "Created_at": "Créé le", - "Created_at_s_by_s": "Créé à %s par %s", + "Created_at_s_by_s": "Créé le %s par %s", "CROWD_Reject_Unauthorized": "Rejeter si non-autorisé", "Current_Chats": "Discussions actuelles", "Custom": "Personnalisé", "Custom_Emoji": "Emoticône personnalisée", "Custom_Emoji_Add": "Ajouter une nouvelle émoticône", - "Custom_Emoji_Added_Successfully": "Emoji personnalisé ajouté avec succès", - "Custom_Emoji_Delete_Warning": "Effacer un emoji ne peut pas être annulé.", - "Custom_Emoji_Error_Invalid_Emoji": "émoticône non valide", - "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "L'émoji personnalisé ou un de ses alias est déjà en cours d'utilisation.", - "Custom_Emoji_Has_Been_Deleted": "L'émoji personnalisé a été effacé.", - "Custom_Emoji_Info": "Information sur l'émoji personnalisé", - "Custom_Emoji_Updated_Successfully": "Emoji personnalisé mis à jour avec succès", - "Custom_Fields": "Champs Personnalisés", + "Custom_Emoji_Added_Successfully": "Emoticône personnalisée ajouté avec succès", + "Custom_Emoji_Delete_Warning": "Effacer une émoticône est irréversible.", + "Custom_Emoji_Error_Invalid_Emoji": "Emoticône non valide", + "Custom_Emoji_Error_Name_Or_Alias_Already_In_Use": "L'émoticône personnalisée ou un de ses alias est déjà en cours d'utilisation.", + "Custom_Emoji_Has_Been_Deleted": "L'émoticône personnalisée a été effacé.", + "Custom_Emoji_Info": "Information sur l'émoticône personnalisée", + "Custom_Emoji_Updated_Successfully": "Emoticône personnalisée mise à jour avec succès", + "Custom_Fields": "Champs personnalisés", "Custom_oauth_helper": "Lorsque vous configurez votre service OAuth, vous devez indiquer une URL pour le Callback. Utilisez
    %s
    ", "Custom_oauth_unique_name": "Nom unique de l'OAuth personnalisé", + "Custom_Scripts": "Scripts personnalisés", "Custom_Script_Logged_In": "Script personnalisé pour les utilisateurs connectés", "Custom_Script_Logged_Out": "Script personnalisé pour les utilisateurs déconnectés", + "Custom_Sounds": "Son personnalisés", + "Custom_Sound_Add": "Ajouter un son personnalisé", + "Custom_Sound_Delete_Warning": "La suppression d'un son est irréversible.", + "Custom_Sound_Error_Invalid_Sound": "Son invalide", + "Custom_Sound_Error_Name_Already_In_Use": "Le nom de son personnalisé est déjà utilisé", + "Custom_Sound_Has_Been_Deleted": "Le son personnalisé a été supprimé.", + "Custom_Sound_Info": "Information du son personalisé", "Custom_Sound_Saved_Successfully": "Son personnalisé enregistré avec succès", "Custom_Translations": "Traductions personnalisées", "Custom_Translations_Description": "Devrait être un JSON valide où les clefs sont des langues conenant un dictionnaire de clefs et de traductions.\nExemple:
    {\n \"en\": {\n  \"key\": \"translation\"\n },\n \"fr\": {\n  \"key\": \"traduction\"\n }\n} ", + "CustomSoundsFilesystem": "Système de fichier des sons personnalisés", "Dashboard": "Tableau de bord", "Date": "Date", "Date_From": "De", "Date_to": "à", "days": "jours", - "DB_Migration": "Base de données de migration", - "DB_Migration_Date": "Base de données de migration date", + "DB_Migration": "Mise à jour de la base de données", + "DB_Migration_Date": "Date de mise à jour de la base de données", "Deactivate": "Désactiver", "Decline": "Refuser", "Default": "Défaut", "Delete": "Supprimer", - "Delete_message": "Suppression de messages", + "Delete_message": "Supprimer le message", "Delete_my_account": "Supprimer mon compte", - "Delete_Room_Warning": "Supprimer un salon supprimera également tous les messages postés dans le salon. Cela ne peut pas être annulé.", - "Delete_User_Warning": "Supprimer un utilisateur va également supprimer tous les messages de celui-ci. Cette action ne peut être annulée.", + "Delete_Room_Warning": "Supprimer un salon supprimera également tous les messages postés dans le salon. Cette action est irréversible.", + "Delete_User_Warning": "Supprimer un utilisateur va également supprimer tous les messages de celui-ci. Cette action est irréversible.", "Deleted": "Supprimé !", "Department": "Département", "Department_removed": "Département supprimé", @@ -377,21 +390,21 @@ "Desktop_Notifications_Duration_Description": "Secondes pour afficher une notification de bureau. Cela peut affecter le Centre de Notification de OS X. Entrez 0 pour utiliser les paramètres du navigateur par défaut et ne pas affecter le Centre de Notification de OS X.", "Desktop_Notifications_Enabled": "Les notifications sur le bureau sont activées", "Direct_message_someone": "Envoyer un message privé à quelqu'un", - "Direct_Messages": "Messages privés", + "Direct_Messages": "Messages Privés", "Display_offline_form": "Affichage formulaire hors ligne", "Displays_action_text": "Texte d'affichage", - "Do_you_want_to_change_to_s_question": "Voulez-vous changer pour %s ?", + "Do_you_want_to_change_to_s_question": "Voulez-vous changer pour %s ?", "Domain": "Domaine", "Domain_added": "Domaine ajouté", - "Domain_removed": "Domaine retiré", + "Domain_removed": "Domaine supprimé", "Domains": "Domaines", "Download_Snippet": "Telecharger", - "Drop_to_upload_file": "Glissez-déposez pour transférer un fichier", + "Drop_to_upload_file": "Glissez-déposez pour envoyer un fichier", "Dry_run": "Essai à vide", "Dry_run_description": "Envoi un unique e-mail, à l'adresse de l'expéditeur. L'adresse e-mail doit appartenir à un utilisateur valide.", - "Duplicate_archived_channel_name": "Un canal archivé avec le nom '%s' existe", - "Duplicate_archived_private_group_name": "Un Groupe Privé archivé avec le nom '%s' existe", - "Duplicate_channel_name": "Un canal avec le nom '% s' existe", + "Duplicate_archived_channel_name": "Un canal archivé avec le nom '#%s' existe", + "Duplicate_archived_private_group_name": "Un groupe privé archivé avec le nom '%s' existe", + "Duplicate_channel_name": "Un canal avec le nom '%s' existe", "Duplicate_private_group_name": "Un groupe privé avec le nom '%s' existe déjà", "Duration": "Durée", "Edit": "Modifier", @@ -399,24 +412,24 @@ "Edit_Department": "Éditer le département", "Edit_Trigger": "Éditer le déclencheur", "edited": "modifié", - "Editing_room": "Modification des salons", + "Editing_room": "Modification du salon", "Editing_user": "Modification des utilisateurs", "Email": "E-mail", "Email_address_to_send_offline_messages": "Adresse e-mail pour envoyer les messages hors ligne", "Email_already_exists": "L'adresse e-mail existe déjà", "Email_body": "Corps du message", "Email_Change_Disabled": "Votre administrateur de Rocket.Chat a désactivé le changement d'adresse e-mail", - "Email_Footer_Description": "Vous pouvez utiliser les variables suivantes:
    ", + "Email_Footer_Description": "Vous pouvez utiliser les variables suivantes:
    ", "Email_from": "De", - "Email_Header_Description": "Vous pouvez utiliser les variables suivantes:
    ", + "Email_Header_Description": "Vous pouvez utiliser les variables suivantes:
    ", "Email_Notification_Mode": "Notifications hors-ligne par e-mail", "Email_Notification_Mode_All": "Toutes les Mentions/MP", "Email_Notification_Mode_Disabled": "Désactivé", - "Email_or_username": "Adresse email ou nom d'utilisateur", + "Email_or_username": "Adresse e-mail ou nom d'utilisateur", "Email_subject": "Sujet", "Email_verified": "Adresse e-mail vérifiée", - "Emoji": "Emoji", - "EmojiCustomFilesystem": "Système de fichier d'émoticones personnalisés", + "Emoji": "Emoticône ", + "EmojiCustomFilesystem": "Système de fichier d'émoticônes personnalisés", "Empty_title": "Titre vide", "Enable": "Activer", "Enable_Desktop_Notifications": "Activer les notifications sur le bureau", @@ -425,9 +438,9 @@ "Encrypted_message": "Message chiffré", "End_OTR": "Arrêter OTR", "Enter_Alternative": "Mode alternatif (envoyé avec Entrée + Ctrl/Alt/Shift/CMD)", - "Enter_a_regex": "Entrez une expression rationnelle", + "Enter_a_regex": "Saisissez une expression rationnelle", "Enter_a_room_name": "Saisissez un nom de salon", - "Enter_a_username": "Entrez un nom d'utilisateur", + "Enter_a_username": "Saisissez un nom d'utilisateur", "Enter_Behaviour": "Comportement de la touche Entrée", "Enter_Behaviour_Description": "Cela change si la touche Entrée enverra un message ou ajoutera un retour à la ligne", "Enter_name_here": "Saisissez le nom ici", @@ -449,26 +462,26 @@ "error-department-not-found": "Département introuvable", "error-duplicate-channel-name": "Un canal avec le nom '__channel_name__' existe déjà", "error-email-domain-blacklisted": "Le domaine de l'adresse e-mail est sur liste noire", - "error-email-send-failed": "Erreur lors de la tentative d'envoi d'email: __message__", + "error-email-send-failed": "Erreur lors de la tentative d'envoi d'e-mail : __message__", "error-field-unavailable": "__field__ est déjà utilisé :(", "error-file-too-large": "Le fichier est trop lourd", "error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe Import.", "error-input-is-not-a-valid-field": "__input__ n'est pas un __field__ valide", - "error-invalid-actionlink": "lien d'action non valide", + "error-invalid-actionlink": "Lien d'action non valide", "error-invalid-arguments": "Arguments non valides", "error-invalid-asset": "Ressource invalide", "error-invalid-channel": "Canal non valide.", "error-invalid-channel-start-with-chars": "Canal non valide. Commencez par @ ou #", "error-invalid-custom-field": "Champ personnalisé invalide", - "error-invalid-custom-field-name": "Nom de champ personnalisé invalide. Utilisez des lettres, des chiffres et des tirets (milieu et bas).", + "error-invalid-custom-field-name": "Nom de champ personnalisé invalide. Utilisez uniquement des lettres, des chiffres et des tirets (milieu et bas).", "error-invalid-date": "Date fournie invalide.", "error-invalid-description": "Description non valide", "error-invalid-domain": "Domaine non valide", "error-invalid-email": "Adresse e-mail __email__ non valide", - "error-invalid-email-address": "Adresse email invalide", + "error-invalid-email-address": "Adresse e-mail invalide", "error-invalid-file-height": "Hauteur du fichier non valide", "error-invalid-file-type": "Type de fichier non valide", - "error-direct-message-file-upload-not-allowed": "L'envoi de fichier n'est pas autorisé dans les messages directs", + "error-direct-message-file-upload-not-allowed": "L'envoi de fichier n'est pas autorisé dans les messages privés", "error-invalid-file-width": "Largeur du fichier non valide", "error-invalid-from-address": "Vous avez entré un expéditeur invalide (champ De).", "error-invalid-integration": "Intégration non valide", @@ -476,7 +489,7 @@ "error-invalid-method": "Méthode non valide", "error-invalid-name": "Nom invalide", "error-invalid-password": "Mot de passe invalide", - "error-invalid-redirectUri": "redirectUri invalide", + "error-invalid-redirectUri": "URI de redirection invalide", "error-invalid-role": "Rôle non valide", "error-invalid-room": "Salon invalide", "error-invalid-room-name": "__room_name__ n'est pas un nom de salon valide,
    vous ne pouvez utiliser que des lettres, des nombres et des tirets (milieu et bas).", @@ -505,10 +518,12 @@ "error-user-is-not-activated": "L'utilisateur n'est pas activé", "error-user-not-in-room": "L'utilisateur n'est pas dans ce salon.", "error-user-registration-disabled": "L'inscription des nouveaux utilisateurs est désactivée", + "error-user-limit-exceeded": "Le nombre d'utilisateurs que vous essayez d'inviter pour #channel_name dépasse le nombre autorisé par l'administrateur", "error-user-registration-secret": "L'inscription des utilisateurs n'est autorisée que depuis une URL Secrète", "error-you-are-last-owner": "Vous êtes le dernier propriétaire. Veuillez choisir un nouveau propriétaire avant de quitter le salon.", "Error_changing_password": "Erreur lors du changement de mot de passe", "Esc_to": "Échap pour", + "Event_Trigger_Description": "Sélectionner quel type d'événements déclencheront cette intégration WebHook Sortant", "every_30_minutes": "Une fois toutes les 30 minutes", "every_hour": "Une fois par heure", "every_six_hours": "Une fois toutes les 6 heures", @@ -518,26 +533,27 @@ "False": "Non", "Favorite_Rooms": "Activer les salons favoris", "Favorites": "Favoris", - "Features_Enabled": "Caractéristiques Enabled", + "Features_Enabled": "Fonctionnalités activées", "Field": "Champ", "Field_removed": "Champ supprimé", "Field_required": "Champs requis", - "File_exceeds_allowed_size_of_bytes": "Le fichier dépasse la taille maximale autorisée : __size__ octets", - "File_not_allowed_direct_messages": "L'envoi de fichier n'est pas autorisé dans les messages directs.", - "File_type_is_not_accepted": "Ce tye de fichier n'est pas accepté.", + "File_exceeds_allowed_size_of_bytes": "Le fichier dépasse la taille maximale autorisée : __size__. ", + "File_not_allowed_direct_messages": "L'envoi de fichier n'est pas autorisé dans les messages privés.", + "File_type_is_not_accepted": "Ce type de fichier n'est pas accepté.", "File_uploaded": "Fichier envoyé", "FileUpload": "Envoi de fichiers", "FileUpload_Enabled": "Envois de fichiers activés", "FileUpload_Disabled": "L'envoi de fichiers n'est pas activé.", - "FileUpload_Enabled_Direct": "L'envoi de fichiers est activé dans les messages directs", + "FileUpload_Enabled_Direct": "L'envoi de fichiers est activé dans les messages privés", "FileUpload_File_Empty": "Fichier vide", "FileUpload_FileSystemPath": "Chemin d'accès", + "FileUpload_GoogleStorage_Secret": "Secret Google Storage", "FileUpload_GoogleStorage_Secret_Description": "Merci de suivre ces instructions et copiez le résultat ici.", "FileUpload_MaxFileSize": "Taille maximale pour l'envoi de fichier (en octets)", - "FileUpload_MediaType_NotAccepted": "Types de Fichiers Non Acceptés", + "FileUpload_MediaType_NotAccepted": "Types de fichiers non acceptés", "FileUpload_MediaTypeWhiteList": "Types de média acceptés", "FileUpload_MediaTypeWhiteListDescription": "Liste des types de média (séparés par des virgules). Laisser vide pour accepter tous les types de media.", - "FileUpload_ProtectFiles": "Protéger les fichiers uploadés", + "FileUpload_ProtectFiles": "Protéger les fichiers envoyés", "FileUpload_ProtectFilesDescription": "Seuls les utilisateurs authentifiés auront accès", "FileUpload_S3_Acl": "Amazon S3 acl", "FileUpload_S3_AWSAccessKeyId": "Amazon S3 AWSAccessKeyId", @@ -552,7 +568,7 @@ "Flags": "Drapeaux", "Follow_social_profiles": "Suivez-nous sur les réseaux sociaux, clonez le projet sur GitHub et partagez vos idées à propos de Rocket.Chat sur notre tableau Trello.", "Food_and_Drink": "Nourriture & Boissons", - "Footer": "Bas de page", + "Footer": "Pied de page", "Fonts": "Polices", "For_your_security_you_must_enter_your_current_password_to_continue": "Pour votre sécurité, vous devez ré-entrer votre mot de passe pour continuer", "Force_Disable_OpLog_For_Cache": "Forcer la désactivation de la fonction OpLog pour le cache", @@ -580,19 +596,19 @@ "GoogleTagManager_id": "Google Tag Manager ID", "Guest_Pool": "Invités en attente", "Hash": "Hachage", - "Header": "En-tête", + "Header": "Entête", "Header_and_Footer": "Entêtes et Pied de page", "Hidden": "Caché", "Hide_Avatars": "Masquer les avatars", - "Hide_flextab": "Cacher le volet de réglages du canal par un clique", - "Hide_Group_Warning": "Êtes-vous sûr(e) de vouloir cacher le groupe \"%s\" ?", - "Hide_Private_Warning": "Êtes-vous sûr(e) de vouloir cacher la discussion avec \"%s\" ?", - "Hide_room": "Cacher le salon", - "Hide_Room_Warning": "Êtes-vous sûr(e) de vouloir cacher le salon \"%s\" ?", + "Hide_flextab": "Masquer le volet de réglages du canal par un clique", + "Hide_Group_Warning": "Êtes-vous sûr(e) de vouloir masquer le groupe \"%s\" ?", + "Hide_Private_Warning": "Êtes-vous sûr(e) de vouloir masquer la discussion avec \"%s\" ?", + "Hide_room": "Masquer le salon", + "Hide_Room_Warning": "Êtes-vous sûr(e) de vouloir masquer le salon \"%s\" ?", "Hide_roles": "Masquer les rôles", - "Hide_usernames": "Cacher les noms d'utilisateur", + "Hide_usernames": "Masquer les noms d'utilisateur", "Highlights": "Mises en avant", - "Highlights_How_To": "Pour être notifié(e) lorsque quelqu'un écrit un mot ou une phrase spécifique, ajoutez le/la ici. Vous pouvez les séparer par une virgule. Les termes surveillés ne sont pas sensibles à la casse.", + "Highlights_How_To": "Pour être notifié(e) lorsque quelqu'un écrit un mot ou une phrase spécifique, ajoutez le/la ici. Vous pouvez les séparer par des virgules. Les termes surveillés ne sont pas sensibles à la casse.", "Highlights_List": "Mots surveillés", "History": "Historique", "Host": "Hôte", @@ -604,11 +620,12 @@ "How_responsive_was_the_chat_agent": "L'assistant du chat avait-il des réponses adaptées ?", "How_satisfied_were_you_with_this_chat": "Étiez-vous satisfait de ce chat?", "How_to_handle_open_sessions_when_agent_goes_offline": "Comment gérer les sessions ouvertes lorsque l'asistant passe hors ligne", + "If_this_email_is_registered": "Si cet e-mail est enregistré, les instructions pour réinitialiser votre mot de passe vous serons envoyées. Si vous ne recevez pas d'email rapidement, merci de revenir et d'essayer à nouveau.", "If_you_are_sure_type_in_your_password": "Si vous êtes sûr(e), entrez votre mot de passe :", "If_you_are_sure_type_in_your_username": "Si vous êtes certain(e), saisissez votre nom d'utilisateur :", "Iframe_Integration": "Intégration Iframe", "Iframe_Integration_receive_enable": "Activer la réception", - "Iframe_Integration_receive_enable_Description": "Autoriser la fenêtre parente à envoyer des commandes à Rocket.Chat", + "Iframe_Integration_receive_enable_Description": "Autoriser la fenêtre parente à envoyer des commandes à Rocket.Chat.", "Iframe_Integration_receive_origin": "Recevoir les origines", "Iframe_Integration_receive_origin_Description": "Seules les pages ayant une origine donnée seront autorisée à envoyer des commandes ou `*` pour toutes les origines. Vous pouvez utiliser de multiples valeurs séparées par des` ,`. Exemple : `http://localhost,https://localhost`", "Iframe_Integration_send_enable": "Activer l'envoi", @@ -617,23 +634,23 @@ "Iframe_Integration_send_target_origin_Description": "Seules les pages ayant une origine donnée seront autorisée à recevoir des commandes ou `*` pour toutes les origines. Exemple : `http://localhost`", "Importer_Archived": "Archivé", "Importer_done": "Importation réussie !", - "Importer_finishing": "Finalisation de l'importation", + "Importer_finishing": "Finalisation de l'importation.", "Importer_From_Description": "Importer les données de __from__ dans Rocket.Chat.", - "Importer_import_cancelled": "Importation annulée", - "Importer_import_failed": "Un erreur est survenue pendant l'importation.", + "Importer_import_cancelled": "Importation annulée.", + "Importer_import_failed": "Un erreur est survenue lors de l'importation.", "Importer_importing_channels": "Importation des canaux.", "Importer_importing_messages": "Importation des messages.", "Importer_importing_started": "Début de l'importation.", "Importer_importing_users": "Importation des utilisateurs.", - "Importer_not_in_progress": "L'importateur n'est pas en cours d'exécution.", + "Importer_not_in_progress": "L'import n'est pas en cours d'exécution.", "Importer_Prepare_Restart_Import": "Recommencer l’Importation", "Importer_Prepare_Start_Import": "Commencer l'Importation", - "Importer_Prepare_Uncheck_Archived_Channels": "Désélectionner les Canaux Archivés", - "Importer_Prepare_Uncheck_Deleted_Users": "Désélectionner les Utilisateurs Supprimés", + "Importer_Prepare_Uncheck_Archived_Channels": "Désélectionner les canaux archivés", + "Importer_Prepare_Uncheck_Deleted_Users": "Désélectionner les utilisateurs supprimés", "Importer_progress_error": "Impossible d'obtenir l'état de l'importation.", "Importer_setup_error": "Une erreur est survenue lors du paramétrage de l'importateur.", "Importer_Source_File": "Sélection du fichier source", - "Incoming_Livechats": "Arrivée de nouveaux Livechats", + "Incoming_Livechats": "Arrivée de nouveaux chats en direct", "inline_code": "Ligne de code", "Install_Extension": "Installer l'extension", "Install_FxOs": "Installez Rocket.Chat dans votre Firefox", @@ -644,6 +661,8 @@ "Installed_at": "installé à", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instructions à votre visiteur de remplir le formulaire pour envoyer un message", "Impersonate_user": "Incarner l'utilisateur", + "Incoming_WebHook": "WebHook entrant", + "Integration_Advanced_Settings": "Paramètres avancés", "Integration_added": "L'intégration a été ajoutée", "Integration_Incoming_WebHook": "Intégration WebHook Entrant", "Integration_New": "Nouvelle intégration", @@ -652,27 +671,38 @@ "Integrations_Outgoing_Type_RoomCreated": "Salon créé (public et privé)", "Integrations_Outgoing_Type_RoomJoined": "L'utilisateur a rejoint le salon", "Integrations_Outgoing_Type_RoomLeft": "L'utilisateur a quitté le salon", + "Integrations_Outgoing_Type_SendMessage": "Message envoyé", + "Integrations_Outgoing_Type_UserCreated": "Utilisateur créé", "Integration_Outgoing_WebHook": "Intégration WebHook Sortant", + "Integration_Outgoing_WebHook_History": "Historique de l'intégration WebHook Sortant", + "Integration_Outgoing_WebHook_No_History": "L'intégration WebHook sortant n'a pas encore d'historique enregistré.", + "Integration_Outgoing_WebHook_History_Data_Passed_To_Trigger": "Données passées pour intégration", + "Integration_Outgoing_WebHook_History_Data_Passed_To_URL": "Données passées pour URL", + "Integration_Outgoing_WebHook_History_Http_Response_Error": "Erreur Réponse HTTP", + "Integration_Outgoing_WebHook_History_Http_Response": "Réponse HTTP", "Integration_updated": "L'intégration a été mise à jour", "Integrations": "Intégrations", + "Integrations_for_all_channels": "Entrez all_public_channelspour écouter sur tous les canaux publics, all_private_groupspour écouter sur tous les groupes privés, et all_direct_messagespour écouter sur tous les messages privés.", "InternalHubot": "Hubot interne", "InternalHubot_ScriptsToLoad": "Scripts à charger", "InternalHubot_ScriptsToLoad_Description": "Veuillez entrer une liste de scripts séparés par des virgules à charger depuis https://github.com/github/hubot-scripts/tree/master/src/scripts", "InternalHubot_Username_Description": "Cela doit être un nom d'utilisateur valide d'un bot enregistré sur votre serveur.", "Invalid_confirm_pass": "Les mots de passe renseignés ne sont pas les mêmes", "Invalid_email": "L'adresse e-mail saisie est invalide", - "Invalid_Export_File": "Le fichier transmis n'est pas un fichier d'exportation valide de %s", + "Invalid_Export_File": "Le fichier envoyé n'est pas un fichier d'exportation valide de %s", "Invalid_Import_File_Type": "Format du fichier d'importation invalide.", "Invalid_name": "Le nom doit être renseigné", "Invalid_notification_setting_s": "Paramètre de notification invalide : %s", "Invalid_pass": "Le mot de passe doit être renseigné", "Invalid_room_name": "%s n'est pas un nom de salon valide.
    Utilisez uniquement des lettres, des chiffres et des tirets (milieu et bas)", "Invalid_secret_URL_message": "L'URL fournie est invalide.", + "Invalid_setting_s": "Paramètre invalide : %s", "invisible": "invisible", "Invisible": "Invisible", + "Invitation": "Invitation", "Invitation_HTML": "Contenu HTML de l'invitation", "Invitation_HTML_Default": "

    Vous avez été invité à rejoindre

    [Site_Name]

    Accédez à [Site_URL] et essayez la meilleure solution de chat open source disponible aujourd'hui !

    ", - "Invitation_HTML_Description": "Vous pouvez utiliser les variables suivantes :
    ", + "Invitation_HTML_Description": "Vous pouvez utiliser les variables suivantes :
    ", "Invitation_Subject": "Sujet de l'invitation", "Invitation_Subject_Default": "Vous avez été invité à [Site_Name]", "Invite_user_to_join_channel": "Inviter un utilisateur à rejoindre le canal", @@ -685,12 +715,20 @@ "is_typing": "est en train d'écrire", "is_typing_female": "est en train d'écrire", "is_typing_male": "est en train d'écrire", - "IRC_Hostname": "Le serveur d'hôte IRC auquel se sonnecter", + "IRC_Channel_Join": "Sortie de la commande JOIN.", + "IRC_Channel_Leave": "Sortie de la commande PART.", + "IRC_Private_Message": "Sortie de la commande PRIVMSG.", + "IRC_Channel_Users": "Sortie de la commande NAMES.", + "IRC_Channel_Users_End": "Fin de sortie de la commande NAMES.", + "IRC_Hostname": "Le serveur d'hôte IRC auquel se connecter.", + "IRC_Login_Fail": "Sortie après une connexion échouée au serveur IRC.", + "IRC_Login_Success": "Sortie après une connexion réussie au serveur IRC.", + "IRC_Quit": "Sortie après avoir quitté une session IRC.", "It_works": "Ça marche", "italics": "italique", "Jitsi_Chrome_Extension": "Chrome Extension Id", "Jitsi_Enable_Channels": "Activer dans les canaux", - "join": "Rejoindre\n", + "join": "Rejoindre", "Join_audio_call": "Rejoindre l'appel audio", "Join_default_channels": "Rejoindre les canaux par défaut", "Join_the_Community": "Rejoignez la communauté", @@ -702,11 +740,11 @@ "Jump_to_message": "Aller au message", "Jump_to_recent_messages": "Aller aux messages récents", "Katex_Dollar_Syntax": "Autoriser Dollar Syntaxe", - "Katex_Dollar_Syntax_Description": "Autoriser avec $$ bloc Katex $$ et $ Katex inline $ syntaxes", + "Katex_Dollar_Syntax_Description": "Autoriser les syntaxes : $$bloc katex$$ et $katex inline$", "Katex_Enabled": "Katex activé", "Katex_Enabled_Description": "Autoriser l' utilisation Katex pour les mathématiques photocomposition dans les messages", "Katex_Parenthesis_Syntax": "Autoriser Parenthesis Syntaxe", - "Katex_Parenthesis_Syntax_Description": "Autoriser l'utilisation \\ [bloc Katex \\] et \\ (inline Katex \\) syntaxes", + "Katex_Parenthesis_Syntax_Description": "Autoriser les syntaxes : \\[bloc katex\\] et \\(inline katex\\)", "Knowledge_Base": "Base de connaissances", "Label": "Label", "Language": "Langue", @@ -727,7 +765,7 @@ "LDAP_Custom_Domain_Search": "Domaine de recherche personnalisé", "LDAP_Custom_Domain_Search_Description": "Un bout de JSON qui contrôle la liaison et la connexion et qui a le format :
    {\"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\"}", "LDAP_Default_Domain": "Domaine par défaut", - "LDAP_Description": "Un LDAP est une base de données hiérarchique que de nombreuses entreprises utilisent pour fournir une authentification unique à travers leurs services - l'utilisateur partage un mot de passe entre plusieurs sites et services. Pour avoir plus d'informations sur la configuration avancée et voir des exemples, consulter notre wiki: https://rocket.chat/docs/administrator-guides/authentication/ldap/ .", + "LDAP_Description": "Un LDAP est une base de données hiérarchique que de nombreuses entreprises utilisent pour fournir une authentification unique à travers leurs services - l'utilisateur partage un mot de passe entre plusieurs sites et services. Pour avoir plus d'informations sur la configuration avancée et voir des exemples, consulter notre wiki : https://rocket.chat/docs/administrator-guides/authentication/ldap/ .", "LDAP_Domain_Base": "Domaine de base", "LDAP_Domain_Base_Description": "Le chemin complet du Distinguished Name (DN) d'un sous arbre LDAP dans lequel vous souhaitez chercher les utilisateurs et les groupes. Vous pouvez en ajouter autant que vous voulez ; cependant, chaque groupe doit être défini dans le même domaine de base que les utilisateurs qui le composent. Si vous spécifiez des restrictions sur des groupes d'utilisateurs, seuls les utilisateurs appartenant à ces groupes seront dans le périmètre de la recherche. Nous recommandons de spécifier le top level de votre arbre LDAP en tant que domaine de base et d'utiliser le filtre de recherche pour contrôler l'accès.", "LDAP_Domain_Search_Filter": "Filtre pour la recherche dans le domaine", @@ -742,34 +780,34 @@ "LDAP_Domain_Search_User_Description": "L'utilisateur LDAP qui réalise les recherches d'utilisateur pour authentifier les autres utilisateurs quand ils se connectent.
    Il s'agit typiquement d'un compte de service créé spécialement pour l'intégration de tierces parties. Utilisez un nom complet, tel que `cn=Administrator,cn=Users,dc=Example,dc=com`.", "LDAP_Domain_Search_User_ID": "ID de l'utilisateur pour la recherche dans le domaine", "LDAP_Domain_Search_User_ID_Description": "L'attribut LDAP qui identifie l'utilisateur LDAP qui essaye de s'authentifier. Ce champ est `sAMAccountName` pour la plupart des installations Active Directory, mais cela peut être `uid` pour d'autres solutions de LDAP, comme OpenLDAP. Vous pouvez utiliser `mail` pour identifier les utilisateurs par leur adresse e-mail ou n'importe quel autre attribut que vous souhaitez.
    Vous pouvez utiliser plusieurs valeurs séparées par des virgules pour permettre aux utilisateur de s'identifier en utilisant plusieurs identifiants comme le nom d'utilisateur ou l'adresse e-mail.", - "LDAP_Enable": "Activer LDAP", + "LDAP_Enable": "Activer", "LDAP_Enable_Description": "Essayer d'utiliser LDAP pour l'authentification.", "LDAP_Encryption": "Chiffrement", "LDAP_Encryption_Description": "La méthode de chiffrement utilisée pour sécuriser les communications avec le serveur LDAP. Parmi les exemples on trouve `plain` (pas de chiffrement), `SSL/LDAPS` (chiffrement dès le début) et `StartTLS` (chiffrement une fois la connexion établie).", "LDAP_Host": "Hôte", "LDAP_Host_Description": "L'hôte LDAP, par exemple `ldap.exemple.com` ou `10.0.0.30`.", "LDAP_Import_Users": "Importer les utilisateurs LDAP", - "LDAP_Import_Users_Description": "Si VRAI les processus de synchronisation importeront l'ensemble des utilisateurs LDAP
    \n\n*Attention !* Spécifiez les filtres de recherche pour ne pas importer trop d'utilisateurs.", + "LDAP_Import_Users_Description": "Si OUI les processus de synchronisation importeront l'ensemble des utilisateurs LDAP
    \n*Attention !* Spécifiez les filtres de recherche pour ne pas importer trop d'utilisateurs.", "LDAP_Login_Fallback": "Login Fallback", "LDAP_Login_Fallback_Description": "Si l'authentification LDAP échoue, essaye de se connecter avec un compte local. Aide quand le LDAP est inaccessible.", "LDAP_Merge_Existing_Users": "Fusionner les utilisateurs existants", "LDAP_Merge_Existing_Users_Description": "*Attention !* Si vous importez un utilisateur depuis le LDAP et qu'un utilisateur avec le même nom existe déjà, les informations et le mot de passe du LDAP lui seront attribués.", - "LDAP_Port": "Port LDAP", - "LDAP_Port_Description": "Port pour accéder au LDAP ( ex : 389 ou 636 pour LDAPS)", + "LDAP_Port": "Port", + "LDAP_Port_Description": "Port pour accéder au LDAP (ex : 389 ou 636 pour LDAPS)", "LDAP_Reject_Unauthorized": "Rejeter les personnes non autorisées", - "LDAP_Sync_User_Avatar": "Synchronisation de l' Avatar Utilisateur", + "LDAP_Sync_User_Avatar": "Synchronisation de l'avatar utilisateur", "LDAP_Sync_User_Data": "Synchronisation des données", - "LDAP_Sync_User_Data_Description": "Garder les données de l'utilisateur synchronisées avec celles du serveur lors de la connexion (par exemple: nom, adresse e-mail).", + "LDAP_Sync_User_Data_Description": "Garder les données de l'utilisateur synchronisées avec celles du serveur lors de la connexion (par exemple : nom, adresse e-mail).", "LDAP_Sync_User_Data_FieldMap": "Liste des champs utilisateur", - "LDAP_Sync_User_Data_FieldMap_Description": "Configurer la façon dont les champs de compte utilisateur (comme l'adresse e-mail) sont remplis à partir d'un enregistrement dans l'annuaire LDAP (une fois trouvé). À titre d'exemple, {\"cn\": \"nom\", \"mail\": \"email\"} choisira le nom lisible d'une personne à partir de l'attribut cn, et son adresse e-mail à partir de l'attribut mail.
    Les champs disponibles comprennent le nom et l'adresse e-mail.", - "LDAP_Sync_Users": "Synchronisation des Utilisateurs", + "LDAP_Sync_User_Data_FieldMap_Description": "Configurer la façon dont les champs de compte utilisateur (comme l'adresse e-mail) sont remplis à partir d'un enregistrement dans l'annuaire LDAP (une fois trouvé). À titre d'exemple, {\"cn\": \"nom\", \"mail\": \"email\"} choisira le nom lisible d'une personne à partir de l'attribut cn, et son adresse e-mail à partir de l'attribut mail.
    Les champs disponibles comprennent `name`et `email`.", + "LDAP_Sync_Users": "Synchronisation des utilisateurs", "LDAP_Test_Connection": "Tester la connexion", "LDAP_Unique_Identifier_Field": "Champ de l'identifiant unique", "LDAP_Unique_Identifier_Field_Description": "Le champ utilisé pour lier l'utilisateur LDAP à l'utilisateur Rocket.Chat. Vous pouvez renseigner plusieurs valeurs séparées par des virgules pour obtenir la valeur depuis l'enregistrement LDAP.
    La valeur par défaut est `objectGUID,ibm-entryUUID,GUID,dominoUNID,nsuniqueId,uidNumber`", "LDAP_Use_Custom_Domain_Search": "Utiliser la recherche personnalisée dans le domaine", "LDAP_Use_Custom_Domain_Search_Description": "Écrivez vos propres filtres pour rechercher les utilisateurs dans le serveur LDAP.", "LDAP_Username_Field": "Champs du nom d'utilisateur", - "LDAP_Username_Field_Description": "Le champ qui sera utilisé en tant que *nom d'utilisateur* pour les nouveaux utilisateurs. Laisser vide pour utiliser le nom d'utilisateur renseigné sur la page de connexion.
    La valeur par défaut est `sAMAccountName`.", + "LDAP_Username_Field_Description": "Champ utilisé en tant que *nom d'utilisateur* pour les nouveaux utilisateurs. Laisser vide pour utiliser le nom d'utilisateur renseigné sur la page de connexion.
    Vous pouvez aussi utiliser un modèle à base de tags, comme `#{givenName}.#{sn}`. La valeur par défaut est `sAMAccountName`.", "LDAP_Group_Filter_Enable": "Activer le filtre des utilisateurs sur un groupe LDAP", "LDAP_Group_Filter_Enable_Description": "Restreint l'accès aux utilisateurs d'un groupe LDAP
    Utile pour les serveurs OpenLDAP sans recouvrement qui ne permettent pas le filtre *memberOf* ", "LDAP_Group_Filter_Group_Name": "Nom du groupe", @@ -781,24 +819,23 @@ "Leave_Room_Warning": "Êtes-vous sûr(e) de vouloir quitter le salon \"%s\" ?", "Leave_the_current_channel": "Quitter le salon", "line": "ligne", - "List_of_Channels": "Liste des chaînes", - "List_of_Direct_Messages": "Liste des Messages Privés", + "List_of_Channels": "Liste des canaux", + "List_of_Direct_Messages": "Liste des messages privés", "Livechat_agents": "Assistants du chat en direct", "Livechat_AllowedDomainsList": "Domaines autorisés pour le chat en direct", "Domains_allowed_to_embed_the_livechat_widget": "Liste des domaines autorisés à intégrer le widget de chat en direct, séparés par des virgules. Laissez vide pour autoriser tous les domaines. ", "Livechat_Dashboard": "Tableau de bord du chat en direct", "Livechat_enabled": "Chat en direct activé", - "Livechat_forward_open_chats": "chats Forward ouverts", "Livechat_forward_open_chats_timeout": "Timeout (en secondes) de transmettre les chats", "Livechat_guest_count": "Compteur d'invités", - "Livechat_Inquiry_Already_Taken": "Demande de Livechat déjà prise en compte", + "Livechat_Inquiry_Already_Taken": "Demande de chat en direct déjà prise en compte", "Livechat_managers": "Managers du chat en direct", "Livechat_offline": "Chat en direct hors ligne", "Livechat_online": "Chat en direct en ligne", "Livechat_open_inquiery_show_connecting": "Afficher le message de connexion au lieu de la zone de saisie lorsque l'invité n'est pas encore en relation avec un assistant", "Livechat_Queue": "File d'attente de Chat en direct", "Livechat_room_count": "Nombre de salons de chat en direct", - "Livechat_Routing_Method": "Méthode de routage du Livechat", + "Livechat_Routing_Method": "Méthode de routage du chat en direct", "Livechat_Take_Confirm": "Voulez-vous répondre à ce client ?", "Livechat_title": "Titre du chat en direct", "Livechat_title_color": "Couleur d'arrière plan du titre du chat en direct", @@ -824,11 +861,11 @@ "Mail_Messages_Instructions": "Choisissez les messages que vous souhaitez envoyer par e-mail en cliquant dessus", "Mail_Messages_Subject": "Voici une sélection de messages de \"%s\"", "Mailer": "Envoi d'e-mail", - "Mailer_body_tags": "Vous devez utiliser [unsubscribe] pour le lien de désinscription.
    Vous pouvez utiliser [name], [fname], [lname] pour le nom complet de l'utilisateur, le prénom et le nom de famille respectivement.
    Vous pouvez utiliser [email] pour l'adresse e-mail de l'utilisateur.", + "Mailer_body_tags": "Vous devez utiliser [unsubscribe] pour le lien de désinscription.
    Vous pouvez utiliser respectivement [name], [fname], [lname] pour le nom complet de l'utilisateur, le prénom et le nom de famille.
    Vous pouvez utiliser [email] pour l'adresse e-mail de l'utilisateur.", "Mailing": "E-mailing", "Make_Admin": "Promouvoir administrateur", "Manager_added": "Manager ajouté", - "Manager_removed": "Manager retiré", + "Manager_removed": "Manager supprimé", "Managing_assets": "Gestion des ressources", "Managing_integrations": "Gestion des intégrations", "MapView_Enabled": "Activer Mapview", @@ -855,13 +892,13 @@ "Message_AllowPinning": "Autoriser l'épinglement de message", "Message_AllowPinning_Description": "Autoriser les messages à être épinglés à n'importe quel canal.", "Message_AllowStarring": "Autoriser les favoris pour les messages", - "Message_AlwaysSearchRegExp": "Toujours rechercher l'aide de RegExp", - "Message_AlwaysSearchRegExp_Description": "Nous vous recommandons de mettre `true` si votre langue ne sont pas pris en charge sur MongoDB recherche de texte .", + "Message_AlwaysSearchRegExp": "Toujours rechercher en utilisant des expressions régulières", + "Message_AlwaysSearchRegExp_Description": "Nous vous recommandons de mettre `Oui` si votre langue ne sont pas pris en charge sur MongoDB recherche de texte .", "Message_AudioRecorderEnabled": "Enregistrement audio activé", - "Message_AudioRecorderEnabledDescription": "Nécessite que les fichiers de type « audio/wav » soient acceptés en tant que média dans les réglages d'envoi de fichiers.", + "Message_AudioRecorderEnabledDescription": "Nécessite que le type de média « audio/wav » soit accepté dans les paramètres d'envoi des fichiers.", "Message_BadWordsFilterList": "Ajouter des mots interdits à la liste noire", "Message_BadWordsFilterListDescription": "Ajouter une liste de mots interdits au filtre, séparés par des virgules ", - "Message_DateFormat": "Format de Date", + "Message_DateFormat": "Format de la date", "Message_DateFormat_Description": "Voir aussi : Moment.js", "Message_deleting_blocked": "Ce message ne peut plus être supprimé", "Message_editing": "Édition des messages", @@ -869,7 +906,7 @@ "Message_GroupingPeriodDescription": "Les messages seront regroupés avec les messages précédents si ils sont du même utilisateur et si le temps écoulé est inférieur au temps indiqué en secondes.", "Message_HideType_au": "Masquer les messages \"Utilisateur ajouté\"", "Message_HideType_mute_unmute": "Masquer les messages \"Utilisateur rendu muet / a retrouvé la parole\"", - "Message_HideType_ru": "Masquer les messages \"Utilisateur retiré\"", + "Message_HideType_ru": "Masquer les messages \"Utilisateur éjecté\"", "Message_HideType_uj": "Masquer les messages \"L'utilisateur a rejoint\"", "Message_HideType_ul": "Masquer les messages \"L'utilisateur a quitté\"", "Message_KeepHistory": "Conserver l'historique des messages", @@ -877,20 +914,21 @@ "Message_MaxAllowedSize": "Taille maximale d'un message", "Message_pinning": "Épingler des messages", "Message_removed": "Message supprimé", - "Message_SetNameToAliasEnabled": "Changer le nom d'utilisateur pour un alias dans le message", + "Message_SetNameToAliasEnabled": "Définir un nom d'utilisateur utilisé comme alias dans les messages", "Message_SetNameToAliasEnabled_Description": "Seulement si vous n'avez pas mis un alias. Les anciens messages alias ne sont pas changé si l'utilisateur a changé le nom", "Message_ShowDeletedStatus": "Afficher le statut de suppression", "Message_ShowEditedStatus": "Afficher le statut de modification", "Message_ShowFormattingTips": "Afficher les astuces de mise en forme", "Message_starring": "Mettre un message en favoris", - "Message_TimeFormat": "Format de la date et heure", + "Message_TimeFormat": "Format de l'heure", "Message_TimeAndDateFormat": "Format de l'heure et de la date", "Message_TimeFormat_Description": "Voir aussi : Moment.js", + "Message_TimeAndDateFormat_Description": "Voir aussi : Moment.js", "Message_too_long": "Message trop long", "Message_VideoRecorderEnabled": "Enregistreur vidéo activé", - "Message_VideoRecorderEnabledDescription": "Nécéssite que le type de média \"video/webm\" soit accepté dans les paramètres d'envoi des fichiers.", + "Message_VideoRecorderEnabledDescription": "Nécessite que le type de média \"video/webm\" soit accepté dans les paramètres d'envoi des fichiers.", "Messages": "Messages", - "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Les messages envoyés au WebHook Entrant seront postés ici", + "Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Les messages envoyés au WebHook Entrant seront postés ici.", "Meta": "Meta", "Meta_fb_app_id": "App ID Facebook", "Meta_google-site-verification": "Google Site Verification", @@ -902,7 +940,7 @@ "Monday": "Lundi", "Monitor_history_for_changes_on": "Surveiller l'historique des changements sur", "More_channels": "Plus de canaux", - "More_direct_messages": "Plus de Messages Privés", + "More_direct_messages": "Plus de messages privés", "More_groups": "Davantage de groupes", "More_unreads": "Davantage de messages non lus", "Msgs": "Messages", @@ -928,11 +966,12 @@ "New_password": "Nouveau mot de passe", "New_role": "Nouveau rôle", "New_Room_Notification": "Notification de nouveau salon", + "New_videocall_request": "Nouvelle demande d'appel vidéo", "New_Trigger": "Nouveau déclencheur", "No_available_agents_to_transfer": "Aucun assistant disponible pour le transfert", "No_channel_with_name_%s_was_found": "Aucun canal nommé \"%s\" n'a été trouvé !", "No_channels_yet": "Vous ne faites partie d’aucun canal pour le moment.", - "No_direct_messages_yet": "Vous n'avez pris part à aucune discussion privée pour le moment.", + "No_direct_messages_yet": "Vous n'avez pris part à aucune discussion pour le moment.", "No_Encryption": "Pas de Chiffrement", "No_group_with_name_%s_was_found": "Aucun groupe privé nommé \"%s\" n'a été trouvé !", "No_groups_yet": "Vous n'avez pas encore de groupes privés.", @@ -945,6 +984,7 @@ "No_user_with_username_%s_was_found": "Aucun utilisateur nommé \"%s\" n'a été trouvé !", "Nobody_available": "Personne n'est disponible", "Node_version": "Version de Node", + "None": "Aucun", "Normal": "Normal", "Not_authorized": "Non autorisé", "Not_Available": "Indisponible", @@ -967,23 +1007,24 @@ "Office_hours_enabled": "Heures de bureau activées", "Office_hours_updated": "Heures de bureau modifiées", "Offline": "Hors ligne", - "Offline_DM_Email": "|__site__] Vous avez reçu des messages privés de __user__", - "Offline_form": "forme Hors ligne", - "Offline_form_unavailable_message": "forme Offline Message indisponible", - "Offline_Link_Message": "Aller au message", + "Offline_DM_Email": "Vous avez reçu des messages privés de __user__", + "Offline_form": "Formulaire hors ligne", + "Offline_form_unavailable_message": "Message indisponible du formulaire hors ligne", + "Offline_Link_Message": "ALLER AU MESSAGE", "Offline_Mention_Email": "Vous avez été mentionné par __user__ dans le salon #__room__", - "Offline_message": "un message Hors ligne", - "Offline_success_message": "message de succès hors ligne", - "Offline_unavailable": "Offline indisponible", + "Offline_message": "Message hors ligne", + "Offline_success_message": "Message de succès hors ligne", + "Offline_unavailable": "Hors ligne indisponible", "On": "Allumé", "Online": "Connecté", "Only_On_Desktop": "Mode Bureau (envoyé seulement quand Entrée sur le bureau)", "Only_you_can_see_this_message": "Vous seul pouvez voir ce message", "Oops!": "Oups", - "Open": "Ouvrir", + "Open": "Ouvrerture", "Open_days_of_the_week": "Jours d'ouverture", - "Open_Livechats": "Ouvrir les Livechats", + "Open_Livechats": "Ouvrir les chats en direct", "Opened": "Ouvert", + "Opened_in_a_new_window": "Ouvert dans une nouvelle fenêtre.", "Opens_a_channel_group_or_direct_message": "Ouvre un canal, un groupe ou un message direct", "optional": "facultatif", "or": "ou", @@ -995,11 +1036,12 @@ "OS_Platform": "Plate-forme", "OS_Release": "Version", "OS_Totalmem": "Mémoire totale", - "OS_Type": "Type", + "OS_Type": "Type Système d'exploitation", "OS_Uptime": "Durée de fonctionnement", "others": "autres", "OTR": "Conversation chiffrée (OTR)", "OTR_is_only_available_when_both_users_are_online": "OTR est disponible uniquement lorsque les deux utilisateurs sont connectés", + "Outgoing_WebHook": "WebHook sortant", "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Modifier l'URL vers laquelle les fichiers sont téléversés. Cette URL est également utilisée pour les téléchargements sauf si un CDN a été configuré", "Page_title": "Titre de la page", "Page_URL": "URL de la page", @@ -1018,27 +1060,27 @@ "PiwikAnalytics_url_Description": "L'URL où le Piwik réside, assurez-vous d'inclure la barre trialing. Exemple: //piwik.rocket.chat/", "Placeholder_for_email_or_username_login_field": "Texte de remplacement pour l'adresse e-mail ou le nom d'utilisateur à la connexion", "Placeholder_for_password_login_field": "Texte de remplacement pour le mot de passe", - "Please_add_a_comment": "S'il vous plaît ajouter un commentaire", - "Please_add_a_comment_to_close_the_room": "S'il vous plaît, ajouter un commentaire à fermer la chambre", - "Please_answer_survey": "S'il vous plait , prenez un moment pour répondre à un court sondage à propos de ce chat ", + "Please_add_a_comment": "Merci d'ajouter un commentaire", + "Please_add_a_comment_to_close_the_room": "Merci d'ajouter un commentaire pour fermer le salon", + "Please_answer_survey": "Merci de prendre un moment pour répondre à un court sondage à propos de cette conversation", "Please_enter_value_for_url": "Veuillez entrer l'url de votre avatar.", "Please_enter_your_new_password_below": "Veuillez écrire votre nouveau mot de passe ci-dessous :", - "Please_enter_your_password": "Veuillez ré-entrer votre mot de passe", + "Please_enter_your_password": "Veuillez entrer votre mot de passe", "please_enter_valid_domain": "Merci d'entrer un domaine valide", "Please_fill_a_label": "Veuillez entrer une étiquette", "Please_fill_a_name": "Veuillez saisir un nom", "Please_fill_a_username": "Veuillez enter un nom d'utilisateur", "Please_fill_name_and_email": "Veuillez remplir le nom et l'adresse e-mail", "Please_select_an_user": "Merci de sélectionner un utilisateur", - "Please_select_enabled_yes_or_no": "Veuillez choisir une option pour Activé", + "Please_select_enabled_yes_or_no": "Veuillez choisir une option pour \"Activé\"", "Please_wait": "Veuillez patienter", "Please_wait_activation": "Veuillez patienter, cela peut prendre un peu de temps.", "Please_wait_while_OTR_is_being_established": "Veuillez patienter pendant l'établissement de l'OTR", - "Please_wait_while_your_account_is_being_deleted": "Veuillez patienter pendant que votre compte est supprimé...", - "Please_wait_while_your_profile_is_being_saved": "Veuillez patienter pendant que votre profil est enregistré ...", + "Please_wait_while_your_account_is_being_deleted": "Veuillez patienter pendant la suppression de votre compte...", + "Please_wait_while_your_profile_is_being_saved": "Veuillez patienter pendant l'enregistrement de votre profil...", "Port": "Port", "Post_as": "Publié en tant que", - "Post_to_Channel": "Publié sur le Canal", + "Post_to_Channel": "Publié sur le canal", "Post_to_s_as_s": "Publié sur %s en tant que %s", "Preferences": "Préférences", "Preferences_saved": "Préférences enregistrées", @@ -1086,17 +1128,20 @@ "Refresh_keys": "Rafraîchir les clefs", "Refresh_your_page_after_install_to_enable_screen_sharing": "Actualisez votre page après l'installation pour permettre le partage d'écran", "Register": "Créer un nouveau compte", + "Registration": "Enregistrement", "Registration_Succeeded": "Enregistrement réussi", + "Registration_via_Admin": "Enregistrement par un administrateur", + "Regular_Expressions": "Expressions régulières", "Release": "Version", "Remove": "Supprimer", "Remove_Admin": "Supprimer administrateur", - "Remove_as_moderator": "Retirer de la modération", - "Remove_as_owner": "Retirer de la liste des propriétaires", + "Remove_as_moderator": "Supprimer de la liste des modérateurs", + "Remove_as_owner": "Supprimer de la liste des propriétaires", "Remove_custom_oauth": "Supprimer l'OAuth personnalisé ", "Remove_from_room": "Éjecter du salon", "Remove_last_admin": "Retrait du dernier administrateur", "Remove_someone_from_room": "Éjecter quelqu'un du salon", - "Removed": "Retiré", + "Removed": "Supprimé", "Reply": "Répondre", "Report_Abuse": "Signaler un abus", "Report_exclamation_mark": "Signaler !", @@ -1111,20 +1156,22 @@ "Restart_the_server": "Redémarrer le serveur", "Role": "Rôle", "Role_Editing": "Édition des rôles", - "Role_removed": "Rôle retiré", + "Role_removed": "Rôle supprimé", "Room": "Salon", "Room_announcement_changed_successfully": "Annonce du salon modifiée avec succès", "Room_archivation_state": "État", "Room_archivation_state_false": "Actif", "Room_archivation_state_true": "Archivé", "Room_archived": "Salon archivé", - "room_changed_description": "La description de la salle a été changé en __room_description__ par __user_by__", + "room_changed_announcement": "L'annonce du salon a été modifié en : __room_announcement__ par __user_by__", + "room_changed_description": "Description du salon modifiée pour : __room_description__ par __user_by__", "room_changed_privacy": "Type du salon changé pour : __room_type__ par __user_by__", "room_changed_topic": "Sujet du salon changé pour : __room_topic__ by __user_by__", "Room_description_changed_successfully": "Description de la salle modifiée avec succès", "Room_has_been_deleted": "Le salon a été supprimé", "Room_has_been_archived": "Le salon a été archivé", - "Room_has_been_unarchived": "Le salon a été archivé", + "Room_has_been_unarchived": "Le salon a été désarchivé", + "Room_type_of_default_rooms_cant_be_changed": "C'est un salon par défaut et le type ne peut être modifié, merci de contacter un administrateur.", "Room_default_change_to_private_will_be_default_no_more": "Modifier ce canal en groupe privé fera qu'il ne sera plus un canal par défaut. Etes vous sûr de vouloir continuer ?", "Room_Info": "Informations sur le salon", "room_is_blocked": "Le salon est bloqué", @@ -1133,7 +1180,7 @@ "Room_name_changed": "Nom du salon changé en : __room_name__ par __user_by__", "Room_name_changed_successfully": "Nom du salon modifié avec succès", "Room_not_found": "Salon introuvable", - "Room_password_changed_successfully": "Mot de passe du salonmodifié avec succès", + "Room_password_changed_successfully": "Mot de passe du salon modifié avec succès", "Room_topic_changed_successfully": "Sujet du salon modifié avec succès", "Room_type_changed_successfully": "Type du salon modifié avec succès", "Room_unarchived": "Salon désarchivé", @@ -1165,9 +1212,9 @@ "Search": "Recherche", "Search_by_username": "Rechercher par nom d'utilisateur", "Search_Messages": "Rechercher dans les messages", - "Search_Private_Groups": "Rechercher dans les Groupes Privés", + "Search_Private_Groups": "Rechercher dans les groupes privés", "seconds": "secondes", - "Secret_token": "jeton secret", + "Secret_token": "Jeton secret", "Security": "Sécurité", "Select_a_department": "Sélectionner un département", "Select_a_user": "Sélectionner un utilisateur", @@ -1180,15 +1227,15 @@ "Send": "Envoyer", "Send_a_message": "Envoyez un message", "Send_a_test_mail_to_my_user": "Envoyer un e-mail de test à mon utilisateur", - "Send_a_test_push_to_my_user": "Envoyer une notification test aux utilisateurs", - "Send_confirmation_email": "Envoyer un email de confirmation", + "Send_a_test_push_to_my_user": "Envoyer une notification test à mon utilisateur", + "Send_confirmation_email": "Envoyer un e-mail de confirmation", "Send_data_into_RocketChat_in_realtime": "Envoyer des données dans Rocket.Chat en temps réel", "Send_email": "Envoyer l'e-mail", "Send_invitation_email": "Envoyer un e-mail d'invitation", "Send_invitation_email_error": "Vous n'avez pas fourni d'adresse e-mail valide.", "Send_invitation_email_info": "Vous pouvez envoyer plusieurs e-mails d'invitation à la fois.", "Send_invitation_email_success": "Vous avez envoyé avec succès un e-mail d'invitation aux adresses suivantes :", - "Send_request_on_chat_close": "Envoyer une demande sur le chat à proximité", + "Send_request_on_chat_close": "Envoyer une demande sur la fermeture du chat", "Send_request_on_offline_messages": "Envoyer une demande sur les messages hors ligne", "Send_Test": "Envoyer un test", "Send_welcome_email": "Envoyer un e-mail de bienvenue", @@ -1201,9 +1248,9 @@ "Settings": "Paramètres", "Settings_updated": "Paramètres mis à jour", "Share_Location_Title": "Partager votre position ?", - "Shared_Location": "Dossier partagé", + "Shared_Location": "Position partagée", "Should_be_a_URL_of_an_image": "Doit être l'URL d'une image.", - "Should_exists_a_user_with_this_username": "L'utilisateur doit déjà exister", + "Should_exists_a_user_with_this_username": "L'utilisateur doit déjà exister.", "Show_all": "Afficher tout", "Show_more": "Afficher plus", "show_offline_users": "montrer les utilisateur hors-ligne", @@ -1230,8 +1277,13 @@ "Slash_TableUnflip_Description": "Affiche ┬─┬ ノ (゜ - ゜ ノ)", "Slash_Topic_Description": "Définir le sujet", "Slash_Topic_Params": "message Sujet", + "Smarsh_Email": "Smarsh e-mail", + "Smarsh_Email_Description": "Smarsh e-mail à qui envoyer le fichier .eml", "Smarsh_Enabled": "Activer Smarsh", - "Smarsh_MissingEmail_Email": "Email manquant", + "Smarsh_Enabled_Description": "Le connecteur Smarsh eml est-il activé ou non ? (a besoin que le champ 'De' soit renseigné dans E-mail -> SMTP).", + "Smarsh_Interval": "Interval Smarsh", + "Smarsh_Interval_Description": "Le temps à attendre avant d'envoyer les messages (a besoin que le champ 'De' soit renseigné dans E-mail -> SMTP).", + "Smarsh_MissingEmail_Email": "E-mail manquant", "Smarsh_MissingEmail_Email_Description": "L'e-mail à montrer pour un utilisateur quand l'adresse est manquante, généralement pour les comptes des bots.", "Smileys_and_People": "Émojis & Portraits", "SMS_Enabled": "SMS activés", @@ -1247,12 +1299,12 @@ "SSL": "SSL", "Star_Message": "Mettre en favoris", "Starred_Messages": "Messages favoris", - "Start_audio_call": "Démarrer l'appel audio", + "Start_audio_call": "Démarrer un appel audio", "Start_Chat": "Démarrer un chat", "Start_of_conversation": "Début de la conversation", "Start_OTR": "Démarrer une conversation chiffrée (OTR)", - "Start_video_call": "Démarrer l'appel vidéo", - "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s": "Débuter avec %s pour l'utilisateur ou %s pour canal. Ex: %s ou %s", + "Start_video_call": "Démarrer un appel vidéo", + "Start_with_s_for_user_or_s_for_channel_Eg_s_or_s": "Débuter avec %s pour l'utilisateur ou %s pour le canal. Ex: %s ou %s", "Started_At": "Démarré(e) à", "Statistics": "Statistiques", "Statistics_reporting": "Envoyer des statistiques à Rocket.Chat", @@ -1270,6 +1322,8 @@ "Stats_Total_Livechat_Rooms": "Nombre total de salons de chat en direct", "Stats_Total_Messages": "Nombre total de messages", "Stats_Total_Messages_Channel": "Nombre total de messages dans les canaux", + "Stats_Total_Messages_Direct": "Nombre total de messages dans les discussions privées", + "Stats_Total_Messages_Livechat": "Nombre total de messages de chat en direct", "Stats_Total_Private_Groups": "Nombre total de groupes privés", "Stats_Total_Rooms": "Nombre total de salons", "Stats_Total_Users": "Nombre total d'utilisateurs", @@ -1285,15 +1339,15 @@ "Survey_instructions": "Notez chaque question en fonction de votre satisfaction, 1 signifiant que vous êtes très insatisfait et 5 que vous êtes entièrement satisfait.", "Symbols": "Symboles", "Sync_success": "Synchronisation réussie", - "Sync_Users": "Synchronisation des Utilisateurs", - "Tag": "Étiquette", + "Sync_Users": "Synchronisation des utilisateurs", + "Tag": "Tag", "Take_it": "Je prends !", "Test_Connection": "Tester la connexion", "Test_Desktop_Notifications": "Tester les notifications sur le bureau", "Thank_you_exclamation_mark": "Merci !", "Thank_you_for_your_feedback": "Merci pour votre retour", "The_application_name_is_required": "Le nom de l'application est requis", - "The_channel_name_is_required": "Le canal doit être nommé", + "The_channel_name_is_required": "Le nom du canal est obligatoire", "The_emails_are_being_sent": "Les e-mails sont en cours d'envoi.", "The_field_is_required": "Le champ %s est requis.", "The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "Le redimensionnement d'image ne fonctionnera pas car nous ne pouvons pas détecter d'installation d'ImageMagick ou de GraphicsMagick sur votre serveur.", @@ -1306,8 +1360,14 @@ "theme-color-content-background-color": "Couleur d'arrière plan du contenu", "theme-color-primary-background-color": "Couleur d'arrière plan principale", "theme-color-primary-font-color": "Couleur de la police principale", + "theme-color-primary-action-color": "Couleur de l'action primaire", "theme-color-secondary-background-color": "Couleur d'arrière plan secondaire", "theme-color-secondary-font-color": "Couleur de police secondaire", + "theme-color-secondary-action-color": "Couleur de l'action secondaire", + "theme-color-component-color": "Couleur du composant", + "theme-color-success-color": "Couleur de succès", + "theme-color-pending-color": "Couleur en attente", + "theme-color-error-color": "Couleur d'erreur", "theme-color-tertiary-background-color": "Couleur de fond tertiaire (contenu)", "theme-color-tertiary-font-color": "Couleur de police tertiaire (contenu)", "theme-color-link-font-color": "Couleur de la police des liens", @@ -1320,20 +1380,20 @@ "theme-color-unread-notification-color": "Couleur des notifications non-lues", "theme-custom-css": "CSS personnalisé", "theme-font-body-font-family": "Famille de police du body", - "There_are_no_agents_added_to_this_department_yet": "Il n'y a pas d'assistant ajouté à ce département pour le moment", + "There_are_no_agents_added_to_this_department_yet": "Il n'y a pas d'assistant ajouté à ce département pour le moment.", "There_are_no_integrations": "Il n'y a aucune intégration", "There_are_no_users_in_this_role": "Il n'y a aucun utilisateur avec ce rôle.", - "This_conversation_is_already_closed": "Ces conversation a déjà été fermée.", + "This_conversation_is_already_closed": "Cette conversation a déjà été fermée.", "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Cette adresse e-mail a déjà été utilisée et n'a pas été vérifiée. Veuillez changer votre mot de passe.", "This_is_a_desktop_notification": "Ceci est une notification sur le bureau.", "This_is_a_push_test_messsage": "Ceci est une notification de test", - "This_room_has_been_archived_by__username_": "Cette chambre a été archivé par __username__", - "This_room_has_been_unarchived_by__username_": "Cette chambre a été désarchivée par __username__", + "This_room_has_been_archived_by__username_": "Ce salon a été archivé par __username__", + "This_room_has_been_unarchived_by__username_": "Ce salon a été désarchivé par __username__", "Thursday": "Jeudi", "Time_in_seconds": "Temps en secondes", "Title": "Titre", "Title_bar_color": "Couleur de la barre de titre", - "Title_bar_color_offline": "Titre couleur de la barre hors-ligne", + "Title_bar_color_offline": "Couleur de la barre de titre hors-ligne", "Title_offline": "Titre déconnecté", "To_install_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site": "Pour installer le chat en direct Rocket.Chat (Livechat) sur votre site web, copiez-collez ce code au dessus de la dernière balise </body> du site.", "to_see_more_details_on_how_to_integrate": "pour plus de détails sur la façon d'intégrer.", @@ -1345,52 +1405,51 @@ "Transcript_of_your_livechat_conversation": "Transcription de votre conversation", "Translated": "Traduit", "Translations": "Traductions", - "Trigger_removed": "Déclencheur retiré", + "Trigger_removed": "Déclencheur supprimé", "Trigger_Words": "Mots déclencheurs", "Triggers": "Déclencheurs", "True": "Oui", "Tuesday": "Mardi", "Type": "Type", - "Type_your_email": "Tapez votre e-mail", - "Type_your_message": "Tapez votre message", - "Type_your_name": "Tapez votre nom", - "Type_your_new_password": "Écrivez votre nouveau mot de passe", + "Type_your_email": "Entrez votre e-mail", + "Type_your_message": "Entrez votre message", + "Type_your_name": "Entrez votre nom", + "Type_your_new_password": "Entrez votre nouveau mot de passe", "UI_DisplayRoles": "Afficher les rôles", - "UI_Merge_Channels_Groups": "Fusionner les groupes privés avec chaînes", + "UI_Merge_Channels_Groups": "Fusionner les groupes privés avec canaux", "Unarchive": "Désarchiver", "Unblock_User": "Débloquer", "Unmute_someone_in_room": "Rendre la parole à quelqu'un dans ce salon", "Unmute_user": "Rendre la parole", "Unnamed": "Sans nom", - "Unpin_Message": "Détacher ce message", - "Unread_Alert": "Alerte non lue", + "Unpin_Message": "Désépingler ce message", "Unread_Messages": "Messages non lus", "Unread_Rooms": "Salons contenant des messages non-lus", "Unread_Rooms_Mode": "Mode des salons non-lus", "Unstar_Message": "Supprimer des favoris", "Upload_file_description": "Description du fichier", "Upload_file_name": "Nom du fichier", - "Upload_file_question": "Transférer le fichier ?", + "Upload_file_question": "Envoyer le fichier ?", "Uploading_file": "Envoi du fichier en cours...", - "Uptime": "uptime", + "Uptime": "Durée de fonctionnement", "URL": "URL", "Use_account_preference": "Préférence du compte utilisateur", - "Use_Emojis": "Utiliser les Emojis", + "Use_Emojis": "Utiliser les émoticônes", "Use_Global_Settings": "Utiliser les paramètres globaux", "Use_initials_avatar": "Utiliser les initiales de votre nom d'utilisateur", "Use_service_avatar": "Utiliser l'avatar %s", "Use_this_username": "Utilisez ce nom d'utilisateur", - "Use_uploaded_avatar": "Utiliser l'avatar transmis", + "Use_uploaded_avatar": "Utiliser l'avatar envoyé", "Use_url_for_avatar": "Utilisez l'URL pour l'avatar", "Use_User_Preferences_or_Global_Settings": "Utiliser les préférences utilisateur ou les paramètres globaux", - "User__username__is_now_a_moderator_of__room_name_": "L'utilisateur __username__ est désormais un modérateur du salon __room_name__.", - "User__username__is_now_a_owner_of__room_name_": "L'utilisateur __username__ est désormais un propriétaire du salon __room_name__.", + "User__username__is_now_a_moderator_of__room_name_": "L'utilisateur __username__ est désormais un modérateur du salon __room_name__", + "User__username__is_now_a_owner_of__room_name_": "L'utilisateur __username__ est désormais un propriétaire du salon __room_name__", "User__username__removed_from__room_name__moderators": "L'utilisateur __username__ n'est plus modérateur du salon __room_name__.", "User__username__removed_from__room_name__owners": "L'utilisateur __username__ n'est plus propriétaire du salon __room_name__.", - "User_added": "L'utilisateur __user_added__ a été ajouté", + "User_added": "Utilisateur ajouté", "User_added_by": "L'utilisateur __user_added__ a été ajouté par __user_by__.", "User_added_successfully": "Utilisateur ajouté avec succès", - "User_doesnt_exist": "Aucun utilisateur nommé `@%s` existe.", + "User_doesnt_exist": "Aucun utilisateur nommé `@%s` existant.", "User_has_been_activated": "L'utilisateur a été activé", "User_has_been_deactivated": "L'utilisateur a été désactivé", "User_has_been_deleted": "L'utilisateur a été supprimé", @@ -1409,13 +1468,13 @@ "User_left_female": "A quitté le canal.", "User_left_male": "A quitté le canal.", "User_logged_out": "L'utilisateur est déconnecté", - "User_management": "Gestion des Utilisateurs", + "User_management": "Gestion des utilisateurs", "User_muted": "Utilisateur rendu muet", "User_muted_by": "L'utilisateur __user_muted__ a été rendu muet par __user_by__.", "User_not_found": "Utilisateur introuvable", "User_not_found_or_incorrect_password": "Utilisateur introuvable ou mot de passe incorrect", "User_or_channel_name": "Nom d'utilisateur ou de canal", - "User_removed": "Utilisateur retiré", + "User_removed": "Utilisateur ejecté", "User_removed_by": "L'utilisateur __user_removed__ a été éjecté par __user_by__.", "User_Settings": "Paramètres utilisateur", "User_unmuted_by": "L'utilisateur __user_by__ a rendu la parole à __user_unmuted__.", @@ -1427,7 +1486,7 @@ "Username_Change_Disabled": "L'administrateur de votre Rocket.Chat a désactivé la possibilité de changer de nom d'utilisateur", "Username_denied_the_OTR_session": "__username__ a refusé la session OTR", "Username_description": "Le nom d'utilisateur est utilisé pour permettre à d'autres personnes de vous mentionner dans leurs messages.", - "Username_doesnt_exist": "L'utilisateur `#%s` n'existe pas.", + "Username_doesnt_exist": "L'utilisateur `%s` n'existe pas.", "Username_ended_the_OTR_session": "__username__ a terminé la session OTR", "Username_invalid": "%s n'est pas un nom d'utilisateur valide,
    utilisez uniquement des lettres, des chiffres, des points et des tirets (milieu et bas)", "Username_is_already_in_here": "`@%s` est déjà présent.", @@ -1439,19 +1498,23 @@ "Users_in_role": "Utilisateurs ayant ce rôle", "UTF8_Names_Slugify": "Utiliser un slug (texte court) pour les noms UTF-8", "UTF8_Names_Validation": "Validation UTF8 des Noms", - "UTF8_Names_Validation_Description": "N’autorise pas les caractères spéciaux et les espaces. Vous pouvez utilisez - _ et . (tiret du milieu, tiret du bas et point) mais pas à la fin du nom", - "Validate_email_address": "Valider l'adresse email", + "UTF8_Names_Validation_Description": "Expression régulière utilisée pour valider les noms des utilisateurs et des canaux", + "Validate_email_address": "Valider l'adresse e-mail", "Verification": "Vérification", "Verification_email_sent": "E-mail de vérification envoyé", "Verification_Email_Subject": "[Nom_du_site] - Vérifiez votre compte", "Verification_Email": "Cliquez ici pour vérifier votre compte.", "Verified": "Vérifié(e)", + "Verify": "Vérifier", "Version": "Version", "Video_Chat_Window": "Video Chat", + "Video_Conference": "Vidéo Conférence", + "Videocall_declined": "Appel vidéo refusé", + "Videocall_enabled": "Appel vidéo activé", "View_All": "Voir tous", "View_Logs": "Voir les logs (journaux)", "View_mode": "Mode d'affichage", - "View_mode_info": "Cela modifie la quantité de messages de l'espace prenne à l'écran.", + "View_mode_info": "Cela modifie l'espace pris par les messages à l'écran.", "Viewing_room_administration": "Accès à la gestion des salons", "Visibility": "Visibilité", "Visible": "Visible", @@ -1460,11 +1523,12 @@ "Visitor_Navigation": "Navigation visiteur", "Visitor_page_URL": "Page d'accueil du visiteur (URL)", "Visitor_time_on_site": "Temps des visiteurs sur le site", - "Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être manuellement activé par un administrateur.", + "Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être activé manuellement par un administrateur.", + "Warnings": "Avertissements", "We_are_offline_Sorry_for_the_inconvenience": "Nous sommes hors ligne. Désolé pour le désagrément.", "We_have_sent_password_email": "Nous vous avons envoyé un e-mail avec des instructions pour réinitialiser votre mot de passe. Si vous ne le recevez pas dans quelques minutes, veuillez réessayer.", "We_have_sent_registration_email": "Nous vous avons envoyé un e-mail afin que vous confirmiez votre inscription. Si vous ne le recevez pas dans quelques minutes, veuillez réessayer.", - "Webhook_URL": "webhook URL", + "Webhook_URL": "Webhook URL", "Webhooks": "Webhooks", "WebRTC_Enable_Channel": "Activer pour les canaux publics", "WebRTC_Enable_Direct": "Activer pour les messages privés", @@ -1478,28 +1542,28 @@ "will_be_able_to": "sera capable de", "Would_you_like_to_return_the_inquiry": "Voulez-vous retourner le dossier ?", "Yes": "Oui", - "Yes_archive_it": "Oui, archivez le !", - "Yes_unarchive_it": "Oui, désarchivez le !", - "Yes_clear_all": "Oui, marquez tout comme lu !", - "Yes_delete_it": "Oui, je confirme la suppression !", - "Yes_hide_it": "Oui, je veux le cacher !", + "Yes_archive_it": "Oui, archive le !", + "Yes_unarchive_it": "Oui, désarchive le !", + "Yes_clear_all": "Oui, marque tout comme lu !", + "Yes_delete_it": "Oui, supprime le !", + "Yes_hide_it": "Oui, masque le !", "Yes_leave_it": "Oui, je veux partir !", - "Yes_mute_user": "Oui, rendre l'utilisateur muet !", - "Yes_remove_user": "Oui, éjecter l'utilisateur !", + "Yes_mute_user": "Oui, rend muet l'utilisateur !", + "Yes_remove_user": "Oui, éjecte l'utilisateur !", "You": "Toi", "you_are_in_preview_mode_of": "Aperçu du salon #__room_name__ ", "You_are_logged_in_as": "Vous êtes connecté en tant que", "You_are_not_authorized_to_view_this_page": "Vous n'avez pas l'autorisation de voir cette page.", - "You_can_change_a_different_avatar_too": "Vous pouvez ignorer l'avatar utilisé pour publier depuis cette intégration.", - "You_can_search_using_RegExp_eg": "Vous pouvez rechercher en utilisant RegExp. par exemple", - "You_can_use_an_emoji_as_avatar": "Vous pouvez également utiliser un emoji comme avatar.", - "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Vous pouvez utiliser les webhooks pour intégrer facilement une discussion en direct avec votre CRM.", - "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Vous ne pouvez pas quitter une salle de discussion en direct. S'il vous plaît, utilisez le bouton de fermeture.", + "You_can_change_a_different_avatar_too": "Vous pouvez écraser l'avatar utilisé pour publier depuis cette intégration.", + "You_can_search_using_RegExp_eg": "Vous pouvez rechercher en utilisant une expression régulière. par exemple", + "You_can_use_an_emoji_as_avatar": "Vous pouvez également utiliser une émoticone comme avatar.", + "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Vous pouvez utiliser les webhooks pour intégrer facilement un chat en direct avec votre CRM.", + "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Vous ne pouvez pas quitter un chat en direct. S'il vous plaît, utilisez le bouton de fermeture.", "You_have_been_muted": "Vous avez été rendu muet et ne pouvez donc pas parler dans ce salon", "You_have_not_verified_your_email": "Vous n'avez pas vérifié votre adresse e-mail.", "You_have_successfully_unsubscribed": "Vous êtes désabonné avec succès de notre liste de diffusion.", "You_must_join_to_view_messages_in_this_channel": "Vous devez rejoindre ce canal pour voir les messages", - "You_need_confirm_email": "Vous devez confirmer votre adresse email pour vous connecter !", + "You_need_confirm_email": "Vous devez confirmer votre adresse e-mail pour vous connecter !", "You_need_install_an_extension_to_allow_screen_sharing": "Vous devez installer une extension pour permettre le partage d'écran", "You_need_to_change_your_password": "Vous devez changer votre mot de passe.", "You_need_to_type_in_your_password_in_order_to_do_this": "Vous devez entrer votre mot de passe pour faire ceci !", diff --git a/packages/rocketchat-i18n/i18n/he.i18n.json b/packages/rocketchat-i18n/i18n/he.i18n.json index 459b1008f5f..b912f90ea58 100644 --- a/packages/rocketchat-i18n/i18n/he.i18n.json +++ b/packages/rocketchat-i18n/i18n/he.i18n.json @@ -1097,7 +1097,6 @@ "Unmute_user": "בטל השתקת משתמש", "Unnamed": "ללא שם", "Unpin_Message": "שחרור הודעה", - "Unread_Alert": "התרעה על לא נקרא", "Unread_Rooms": "חדרים שלא נקראו", "Unread_Rooms_Mode": "מצב חדרים שלא נקראו", "Unstar_Message": "הסר ממועדפים", diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json index 59f172d3704..83e9f1a937d 100644 --- a/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -1292,7 +1292,6 @@ "Unmute_user": "Uključi korisnika", "Unnamed": "Neimenovano", "Unpin_Message": "Otkvači Poruku", - "Unread_Alert": "Nepročitane obavijesti", "Unread_Rooms": "Nepročitane Sobe", "Unread_Rooms_Mode": "Mod Nepročitanih Soba", "Unstar_Message": "Ukloni zvjezdicu", diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index 910a4a4d526..06d62a2f4d7 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -2,7 +2,7 @@ "#channel": "#canale", "0_Errors_Only": "0 - Solo errori", "1_Errors_and_Information": "1 - Errori e informazioni", - "2_Erros_Information_and_Debug": "2 - Errori, informazioni e di debug", + "2_Erros_Information_and_Debug": "2 - Errori, informazioni e debug", "403": "Proibito", "500": "Errore interno del server", "@username": "@username", @@ -33,7 +33,7 @@ "Accounts_BlockedDomainsList_Description": "elenco separato da virgole dei domini bloccati", "Accounts_BlockedUsernameList": "Lista nomi utente bloccati", "Accounts_BlockedUsernameList_Description": "Lista di nomi utente bloccati, separati da virgole (ignora le maiuscole)", - "Accounts_CustomFields_Description": "Dovrebbe essere un JSON valido dove le chiavi sono i campi che contengono il dizionario delle impostazioni del campo. Esempio:
    {\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n} ", + "Accounts_CustomFields_Description": "Dovrebbe essere un JSON valido dove le chiavi sono i campi che contengono un dizionario di impostazioni. Esempio:
    {\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n} ", "Accounts_denyUnverifiedEmail": "Blocca email non verificate", "Accounts_EmailVerification": "Verifica email", "Accounts_EmailVerification_Description": "Assicurati di aver impostato SMTP in modo corretto per utilizzare questa funzione", @@ -361,7 +361,7 @@ "Custom_Sound_Info": "Informazioni Suono Personalizzato", "Custom_Sound_Saved_Successfully": "Suono personalizzato salvato con successo", "Custom_Translations": "Traduzioni personalizzate", - "Custom_Translations_Description": "Dovrebbe essere un JSON valido dove le chiavi sono i campi che contengono il dizionario delle impostazioni del campo. Esempio:
    {\n \"en\": {\n  \"Channels\": \"Rooms\"\n },\n \"pt\": {\n  \"Channels\": \"Salas\"\n }\n} ", + "Custom_Translations_Description": "Dovrebbe essere un JSON valido dove le chiavi sono le lingue che contengono un dizionario delle traduzioni. Esempio:
    {\n \"en\": {\n  \"Channels\": \"Rooms\"\n },\n \"pt\": {\n  \"Channels\": \"Salas\"\n }\n} ", "CustomSoundsFilesystem": "Filesystem suoni personalizzati", "Dashboard": "Dashboard", "Date": "Data", @@ -1045,7 +1045,7 @@ "Off_the_record_conversation": "Conversazione Off-the-record", "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Conversazione Off-the-record non è disponibile per il browser o dispositivo.", "Office_Hours": "Ore d'ufficio", - "Office_hours_enabled": "Ore d'ufficio attive", + "Office_hours_enabled": "Orario ufficio attivo", "Offline": "Offline", "Offline_DM_Email": "Hai ricevuto un messaggio diretto da __user__", "Offline_form": "Modulo offline", @@ -1482,7 +1482,6 @@ "Unmute_user": "Togli il muto all'utente", "Unnamed": "Senza nome", "Unpin_Message": "Rimuovi il pin dal messaggio", - "Unread_Alert": "Avviso Non Letto", "Unread_Rooms": "Stanze non letti", "Unread_Rooms_Mode": "Modalità Stanze Non Letta", "Unstar_Message": "Rimuovi segnalibro", diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json index 8cc4b4e22f4..d207cdff4f0 100644 --- a/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -1103,7 +1103,6 @@ "Unmute_user": "ミュートを解除", "Unnamed": "無名", "Unpin_Message": "ピン留めを外す", - "Unread_Alert": "未読アラート", "Unread_Rooms": "未読のあるルーム", "Unread_Rooms_Mode": "未読ルーム表示モード", "Unstar_Message": "スターを外す", diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json index b16692f959b..02492e8aa80 100644 --- a/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -5,7 +5,7 @@ "2_Erros_Information_and_Debug": "2 - 오류, 정보 및 디버그", "403": "금지됨", "500": "내부 서버 오류", - "@username": "@username", + "@username": "@사용자명", "@username_message": "@username ", "__username__is_no_longer__role__defined_by__user_by_": "__ 사용자 이름 __는 __user_by__에 의해, __role__ 더 이상 없다", "__username__was_set__role__by__user_by_": "__ 사용자 이름 __은 __user_by__에 의해 __role__ 설정했다", @@ -16,11 +16,11 @@ "Accessing_permissions": "권한 액세스", "Account_SID": "계정 SID", "Accounts": "계정", - "Accounts_AllowDeleteOwnAccount": "사용자가 자신의 계정을 삭제할 수있습니다.", - "Accounts_AllowedDomainsList": "허용 가능한 도메인 목록", + "Accounts_AllowDeleteOwnAccount": "사용자가 자신의 계정을 삭제할 수 있습니다.", + "Accounts_AllowedDomainsList": "허용된 도메인 목록", "Accounts_AllowedDomainsList_Description": "허용된 도메인을 쉼표(,)로 구분하기", "Accounts_AllowEmailChange": "이메일 변경을 허용합니다", - "Accounts_AllowPasswordChange": "암호 변경 허용", + "Accounts_AllowPasswordChange": "암호 변경을 허용합니다.", "Accounts_AllowUserAvatarChange": "사용자 아바타 변경을 허용", "Accounts_AllowUsernameChange": "사용자 이름 변경 허용", "Accounts_AllowUserProfileChange": "사용자 프로필 변경을 허용", @@ -32,13 +32,15 @@ "Accounts_BlockedDomainsList_Description": "차단 된 도메인의 쉼표로 구분 된 목록", "Accounts_BlockedUsernameList": "차단 된 사용자 이름 목록", "Accounts_BlockedUsernameList_Description": "차단 된 사용자 이름의 쉼표로 구분 된 목록 (대소 문자 구분)", + "Accounts_CustomFields_Description": "키는 필드 세팅의 딕셔너리(dictionary) 를 포함하는 필드 이름들이어야 합니다.\n\n예:
    {\n \"role\": {\n  \"type\": \"select\",\n  \"defaultValue\": \"student\",\n  \"options\": [\"teacher\", \"student\"],\n  \"required\": true,\n  \"modifyRecordField\": {\n   \"array\": true,\n   \"field\": \"roles\"\n  }\n },\n \"twitter\": {\n  \"type\": \"text\",\n  \"required\": true,\n  \"minLength\": 2,\n  \"maxLength\": 10\n }\n} ", "Accounts_denyUnverifiedEmail": "확인되지 않은 이메일 거부", "Accounts_EmailVerification": "이메일 확인", "Accounts_EmailVerification_Description": "이 기능을 사용하려면 SMTP설정이 올바르게 되어있는지 확인해주십시오.", "Accounts_Enrollment_Email": "등록된 이메일", "Accounts_Enrollment_Email_Default": "

    에 오신 것을 환영합니다

    [Site_Name]

    [Site_URL]로 이동하여 오늘날 최고의 오픈 소스 채팅 솔루션을보십시오!

    ", "Accounts_Enrollment_Email_Description": "당신은 각각 사용자의 전체 이름, 이름 또는 성을 위해 [lname], [name], [fname]을 사용할 수 있습니다.
    당신은 사용자의 이메일을 [email]을 사용할 수 있습니다.", - "Accounts_Enrollment_Email_Subject_Default": "에 오신 것을 환영합니다 [Site_Name]", + "Accounts_Enrollment_Email_Subject_Default": "[Site_Name] 에 오신 것을 환영합니다 ", + "Accounts_ForgetUserSessionOnWindowClose": "윈도루를 닫을 때에 사용자 설정을 삭제 합니다.", "Accounts_Iframe_api_method": "API 메소드", "Accounts_Iframe_api_url": "API URL", "Accounts_iframe_enabled": "사용", @@ -53,9 +55,13 @@ "Accounts_OAuth_Custom_id": "Id", "Accounts_OAuth_Custom_Identity_Path": "Identity 경로", "Accounts_OAuth_Custom_Login_Style": "로그인 스타일", + "Accounts_OAuth_Custom_Merge_Users": "사용자 합치기", + "Accounts_OAuth_Custom_Scope": "범위", "Accounts_OAuth_Custom_Secret": "비밀", "Accounts_OAuth_Custom_Token_Path": "Token 경로", "Accounts_OAuth_Custom_Token_Sent_Via": "토큰 보낸 비아", + "Accounts_OAuth_Custom_Username_Field": "사용자 이름 필드", + "Accounts_OAuth_Drupal": "Drupal Login 이 활성화 됨", "Accounts_OAuth_Facebook": "Facebook 로그인", "Accounts_OAuth_Facebook_callback_url": "페이스 북 콜백 URL", "Accounts_OAuth_Facebook_id": "Facebook 앱 ID", @@ -93,6 +99,8 @@ "Accounts_OAuth_Wordpress_id": "WordPress ID", "Accounts_OAuth_Wordpress_secret": "WordPress 암호", "Accounts_PasswordReset": "암호 재설정", + "Accounts_OAuth_Proxy_host": "프록시 서버", + "Accounts_OAuth_Proxy_services": "프록시 서비스", "Accounts_Registration_AuthenticationServices_Enabled": "인증 서비스에 등록", "Accounts_RegistrationForm": "등록 양식", "Accounts_RegistrationForm_Disabled": "비활성화", @@ -102,6 +110,7 @@ "Accounts_RegistrationForm_SecretURL": "등록 양식 비밀 URL", "Accounts_RegistrationForm_SecretURL_Description": "당신은 당신의 등록 URL에 추가됩니다 임의의 문자열을 제공해야합니다. 예 : https://demo.rocket.chat/register/[secret_hash]", "Accounts_RequireNameForSignUp": "회원 가입은 이름 필요", + "Accounts_SetDefaultAvatar": "기본 아바타 설정", "Accounts_ShowFormLogin": "보기 양식 기반 로그인", "Accounts_UseDefaultBlockedDomainsList": "기본값 사용 차단 된 도메인 목록", "Accounts_UseDNSDomainCheck": "DNS 도메인 확인을 사용하여", @@ -113,6 +122,7 @@ "Add": "추가", "Add_agent": "에이전트를 추가", "Add_custom_oauth": "사용자 정의 OAuth 추가", + "Add_Domain": "도메인 추가", "Add_manager": "관리자 추가", "Add_user": "사용자 추가", "Add_User": "사용자 추가", @@ -124,24 +134,33 @@ "Additional_Feedback": "추가 의견", "Administration": "관리", "After_OAuth2_authentication_users_will_be_redirected_to_this_URL": "OAuth2를 인증 한 후, 사용자는이 URL로 리디렉션됩니다", + "Agent": "에이전트", "Agent_added": "에이전트는 추가", "Agent_removed": "에이전트 제거", "Alias": "별명", + "Alias_Format": "별칭 형식", + "Alias_Set": "별칭 설정", "All": "모든", "All_channels": "모든 채널", "All_logs": "모든 로그", "All_messages": "모든 메시지", "Allow_Invalid_SelfSigned_Certs": "잘못된 Self-Signed Certs 허용", "Allow_Invalid_SelfSigned_Certs_Description": "링크 확인 및 미리보기 무효 및 자체 서명 된 SSL 인증서의 허용.", + "Allow_switching_departments": "방문자가 부서를 변경할 수 있도록 허용함", "Analytics_features_enabled": "기능 활성화", "Analytics_features_messages_Description": "사용자가 메시지에 대해 수행 행동과 관련된 사용자 정의 이벤트를 추적합니다.", "Analytics_features_rooms_Description": "채널 또는 그룹 (삭제두고 작성)에 대한 작업에 관련된 사용자 정의 이벤트를 추적합니다.", "Analytics_features_users_Description": "사용자 (암호 재설정 시간, 프로필 사진 변경 등)에 관련 작업에 관련된 사용자 정의 이벤트를 추적합니다.", + "Analytics_Google": "구글 애널리틱스", "and": "그리고", "And_more": "그리고 더 __length __", "Animals_and_Nature": "동물과 자연", + "Announcement": "공지", "API": "API", + "API_Allow_Infinite_Count_Description": "REST API 호출이 모든 내용을 하나의 호출에 리턴 할 수 있도록 할까요?", "API_Analytics": "분석", + "API_Drupal_URL": "Drupal 서버 URL", + "API_Drupal_URL_Description": "예: https://domain.com (마지막의 슬레시 제외)", "API_Embed": "포함", "API_EmbedDisabledFor": "사용자에대한 Embed 비활성화", "API_EmbedDisabledFor_Description": "쉼표로 구분된 사용자 이름 목록", @@ -153,6 +172,7 @@ "API_GitHub_Enterprise_URL_Description": "예: http://domain.com (마지막 슬래시 제외)", "API_Gitlab_URL": "GitLab URL", "API_Token": "API 토큰", + "API_Upper_Count_Limit": "최대 레코드 수", "API_User_Limit": "채널에 모든 사용자를 추가하는 사용자 제한", "API_Wordpress_URL": "WordPress URL", "Apiai_Key": "Api.ai 키", @@ -182,6 +202,10 @@ "AutoLinker_Urls_TLD": "AutoLinker TLD URL을", "AutoLinker_Urls_www": "AutoLinker 'WWW'의 URL", "AutoLinker_UrlsRegExp": "AutoLinker URL 정규 표현식", + "Automatic_Translation": "자동 번역", + "Auto_Translate": "자동 번역", + "AutoTranslate_Enabled": "자동 번역 활성화", + "AutoTranslate_GoogleAPIKey": "구글 API 키", "Available": "유효한", "Available_agents": "사용 가능한 에이전트", "Avatar": "아바타 변경", @@ -199,9 +223,12 @@ "Back_to_integrations": "위로 통합에", "Back_to_login": "로그인으로 돌아가기", "Back_to_permissions": "돌아 가기 권한", + "Block_User": "사용자 차단", "Body": "신체", "bold": "굵게", + "BotHelpers_userFields": "사용자 필드", "Branch": "분기", + "Bugsnag_api_key": "Bugsnag API 키", "busy": "바쁨", "Busy": "바쁨", "busy_female": "바쁨", @@ -209,9 +236,14 @@ "busy_male": "바쁨", "Busy_male": "바쁨", "by": "으로", + "cache_cleared": "캐시가 삭제됨", "Cancel": "취소", "Cancel_message_input": "취소", "Cannot_invite_users_to_direct_rooms": "객실을 지시하는 사용자를 초대 할 수 없습니다", + "CAS_autoclose": "로그인 팝업을 자동으로 닫음", + "CAS_button_color": "로그인 버튼 배경 색상", + "CAS_button_label_color": "로그인 버튼 텍스트 색상", + "CAS_button_label_text": "로그인 버튼 레이블", "CDN_PREFIX": "CDN Prefix", "Certificates_and_Keys": "인증서와 키", "Changing_email": "변경 이메일", diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json index 85a9d5cfc41..28492fd71c3 100644 --- a/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -1202,7 +1202,6 @@ "Unmute_user": "Anuluj wyciszenie użytkownika", "Unnamed": "Anonimowy", "Unpin_Message": "Odepnij wiadomość", - "Unread_Alert": "Alarm nieprzeczytany", "Unread_Rooms": "Nieprzeczytane pokoje", "Unread_Rooms_Mode": "Tryb nieprzeczytanych pokoi", "Unstar_Message": "Usuń oznaczenie", diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json index 92c5f703e5d..b677c068745 100644 --- a/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -421,6 +421,7 @@ "Field": "Fält", "Field_removed": "fältet avlägsnas", "File_exceeds_allowed_size_of_bytes": "Filen överskrider tillåten storlek __size__ bytes", + "File_uploaded": "Uppladdad fil", "FileUpload": "Uppladdad fil", "FileUpload_Enabled": "Filuppladdningar aktiverade", "FileUpload_File_Empty": "Tom fil", @@ -506,6 +507,7 @@ "Integration_added": "Integrationen har lagts", "Integration_Incoming_WebHook": "Inkommande WebHook Integration", "Integration_New": "Ny integrering", + "Integrations_Outgoing_Type_FileUploaded": "Uppladdad fil", "Integration_Outgoing_WebHook": "Utgående WebHook Integration", "Integration_updated": "Integrationen har uppdaterats", "Integrations": "Integreringar", @@ -1103,6 +1105,8 @@ "Unread_Rooms": "Olästa rum", "Unread_Rooms_Mode": "Olästa Rum Läge", "Unstar_Message": "Ta bort stjärnmarkering", + "Upload_file_description": "Filbeskrivning", + "Upload_file_name": "Filnamn", "Upload_file_question": "Ladda upp fil?", "Uploading_file": "Laddar upp fil...", "Uptime": "drifttid", diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index 10364945aa1..00b01cda799 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -1341,7 +1341,6 @@ "Unmute_user": "取消禁言", "Unnamed": "未命名", "Unpin_Message": "取消固定", - "Unread_Alert": "未读警报", "Unread_Messages": "未读消息", "Unread_Rooms": "未读房间", "Unread_Rooms_Mode": "未读房间模式", diff --git a/packages/rocketchat-importer-csv/server.js b/packages/rocketchat-importer-csv/server.js index 183346fb358..f701075e6a6 100644 --- a/packages/rocketchat-importer-csv/server.js +++ b/packages/rocketchat-importer-csv/server.js @@ -122,9 +122,9 @@ Importer.CSV = class ImporterCSV extends Importer.Base { super.updateRecord({ 'count.messages': messagesCount, 'messagesstatus': null }); super.addCountToTotal(messagesCount); - //Ensure we have some users, channels, and messages - if (tempUsers.length === 0 || tempChannels.length === 0 || messagesCount === 0) { - this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempChannels.length }, and the loaded messages ${ messagesCount }`); + //Ensure we have at least a single user, channel, or message + if (tempUsers.length === 0 && tempChannels.length === 0 && messagesCount === 0) { + this.logger.error('No users, channels, or messages found in the import file.'); super.updateProgress(Importer.ProgressStep.ERROR); return super.getProgress(); } diff --git a/packages/rocketchat-integrations/client/views/integrationsIncoming.js b/packages/rocketchat-integrations/client/views/integrationsIncoming.js index 1825d51a002..f255ea62ac2 100644 --- a/packages/rocketchat-integrations/client/views/integrationsIncoming.js +++ b/packages/rocketchat-integrations/client/views/integrationsIncoming.js @@ -118,7 +118,7 @@ Template.integrationsIncoming.helpers({ } }); - return `curl -X POST -H 'Content-Type: application/json' --data 'payload=${ JSON.stringify(data) }' ${ record.url }`; + return `curl -X POST -H 'Content-Type: application/json' --data '${ JSON.stringify(data) }' ${ record.url }`; }, editorOptions() { diff --git a/packages/rocketchat-integrations/client/views/integrationsOutgoing.html b/packages/rocketchat-integrations/client/views/integrationsOutgoing.html index 2a2e6b9e865..687be0a6ca1 100644 --- a/packages/rocketchat-integrations/client/views/integrationsOutgoing.html +++ b/packages/rocketchat-integrations/client/views/integrationsOutgoing.html @@ -225,6 +225,14 @@
    {{_ "Integration_Word_Trigger_Placement_Description"}}
    +
    + +
    + + +
    {{{_ "Integration_Run_When_Message_Is_Edited_Description"}}}
    +
    +
    {{/if}} diff --git a/packages/rocketchat-integrations/client/views/integrationsOutgoing.js b/packages/rocketchat-integrations/client/views/integrationsOutgoing.js index d8a9418fd47..8c98f87e070 100644 --- a/packages/rocketchat-integrations/client/views/integrationsOutgoing.js +++ b/packages/rocketchat-integrations/client/views/integrationsOutgoing.js @@ -9,7 +9,8 @@ Template.integrationsOutgoing.onCreated(function _integrationsOutgoingOnCreated( token: Random.id(24), retryFailedCalls: true, retryCount: 6, - retryDelay: 'powers-of-ten' + retryDelay: 'powers-of-ten', + runOnEdits: true }); this.updateRecord = () => { @@ -31,7 +32,8 @@ Template.integrationsOutgoing.onCreated(function _integrationsOutgoingOnCreated( triggerWordAnywhere: $('[name=triggerWordAnywhere]').val() ? $('[name=triggerWordAnywhere]').val().trim() : undefined, retryFailedCalls: $('[name=retryFailedCalls]:checked').val().trim() === '1', retryCount: $('[name=retryCount]').val() ? $('[name=retryCount]').val().trim() : 6, - retryDelay: $('[name=retryDelay]').val() ? $('[name=retryDelay]').val().trim() : 'powers-of-ten' + retryDelay: $('[name=retryDelay]').val() ? $('[name=retryDelay]').val().trim() : 'powers-of-ten', + runOnEdits: $('[name=runOnEdits]:checked').val().trim() === '1' }); }; @@ -287,11 +289,13 @@ Template.integrationsOutgoing.events({ let triggerWords; let triggerWordAnywhere; + let runOnEdits; if (RocketChat.integrations.outgoingEvents[event].use.triggerWords) { triggerWords = $('[name=triggerWords]').val().trim(); triggerWords = triggerWords.split(',').filter((word) => word.trim() !== ''); triggerWordAnywhere = $('[name=triggerWordAnywhere]').val().trim(); + runOnEdits = $('[name=runOnEdits]:checked').val().trim(); } let channel; @@ -338,7 +342,8 @@ Template.integrationsOutgoing.events({ retryFailedCalls: retryFailedCalls === '1', retryCount: retryCount ? retryCount : 6, retryDelay: retryDelay ? retryDelay : 'powers-of-ten', - triggerWordAnywhere: triggerWordAnywhere === '1' + triggerWordAnywhere: triggerWordAnywhere === '1', + runOnEdits: runOnEdits === '1' }; const params = Template.instance().data.params? Template.instance().data.params() : undefined; diff --git a/packages/rocketchat-integrations/server/api/api.js b/packages/rocketchat-integrations/server/api/api.js index 398b2cf6352..fe0a406b029 100644 --- a/packages/rocketchat-integrations/server/api/api.js +++ b/packages/rocketchat-integrations/server/api/api.js @@ -10,6 +10,7 @@ function buildSandbox(store = {}) { s, console, moment, + Livechat: RocketChat.Livechat, Store: { set(key, val) { return store[key] = val; @@ -207,6 +208,12 @@ function executeIntegrationRest() { return RocketChat.API.v1.failure(result.error); } this.bodyParams = result && result.content; + if (typeof result !== 'undefined') { + this.scriptResponse = result.response; + if (result.user) { + this.user = result.user; + } + } logger.incoming.debug('[Process Incoming Request result of Trigger', this.integration.name, ':]'); logger.incoming.debug('result', this.bodyParams); } catch ({stack}) { @@ -228,7 +235,10 @@ function executeIntegrationRest() { if (_.isEmpty(message)) { return RocketChat.API.v1.failure('unknown-error'); } - return RocketChat.API.v1.success(); + if (this.scriptResponse) { + logger.incoming.debug('response', this.scriptResponse); + } + return RocketChat.API.v1.success(this.scriptResponse); } catch ({error}) { return RocketChat.API.v1.failure(error); } diff --git a/packages/rocketchat-integrations/server/lib/triggerHandler.js b/packages/rocketchat-integrations/server/lib/triggerHandler.js index a4066474330..a06a8f4bdaa 100644 --- a/packages/rocketchat-integrations/server/lib/triggerHandler.js +++ b/packages/rocketchat-integrations/server/lib/triggerHandler.js @@ -389,6 +389,10 @@ RocketChat.integrations.triggerHandler = new class RocketChatIntegrationHandler if (message.bot) { data.bot = message.bot; } + + if (message.editedAt) { + data.isEdited = true; + } break; case 'fileUploaded': data.channel_id = room._id; @@ -585,6 +589,11 @@ RocketChat.integrations.triggerHandler = new class RocketChatIntegrationHandler } } + if (message && message.editedAt && !trigger.runOnEdits) { + logger.outgoing.debug(`The trigger "${ trigger.name }"'s run on edits is disabled and the message was edited.`); + return; + } + const historyId = this.updateHistory({ step: 'start-execute-trigger-url', integration: trigger, event }); const data = { diff --git a/packages/rocketchat-integrations/server/lib/validation.js b/packages/rocketchat-integrations/server/lib/validation.js index 9da2094cb76..d25a42c45e1 100644 --- a/packages/rocketchat-integrations/server/lib/validation.js +++ b/packages/rocketchat-integrations/server/lib/validation.js @@ -138,6 +138,11 @@ RocketChat.integrations.validateOutgoing = function _validateOutgoing(integratio } } + if (typeof integration.runOnEdits !== 'undefined') { + // Verify this value is only true/false + integration.runOnEdits = integration.runOnEdits === true; + } + _verifyUserHasPermissionForChannels(integration, userId, channels); _verifyRetryInformation(integration); diff --git a/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.js b/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.js index aaf1f3376bc..7719215729c 100644 --- a/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.js +++ b/packages/rocketchat-integrations/server/methods/outgoing/updateOutgoingIntegration.js @@ -44,6 +44,7 @@ Meteor.methods({ retryCount: integration.retryCount, retryDelay: integration.retryDelay, triggerWordAnywhere: integration.triggerWordAnywhere, + runOnEdits: integration.runOnEdits, _updatedAt: new Date(), _updatedBy: RocketChat.models.Users.findOne(this.userId, {fields: {username: 1}}) } diff --git a/packages/rocketchat-katex/katex.coffee b/packages/rocketchat-katex/katex.coffee deleted file mode 100644 index 701d9d28b2d..00000000000 --- a/packages/rocketchat-katex/katex.coffee +++ /dev/null @@ -1,181 +0,0 @@ -### -# KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web. -# https://github.com/Khan/KaTeX -### - -katex = require('katex') - -class Katex - constructor: -> - @delimiters_map = [ - { opener: '\\[', closer: '\\]', displayMode: true , enabled: () => @parenthesis_syntax_enabled() }, - { opener: '\\(', closer: '\\)', displayMode: false, enabled: () => @parenthesis_syntax_enabled() }, - { opener: '$$' , closer: '$$' , displayMode: true , enabled: () => @dollar_syntax_enabled() }, - { opener: '$' , closer: '$' , displayMode: false, enabled: () => @dollar_syntax_enabled() }, - ] - - # Searches for the first opening delimiter in the string from a given position - find_opening_delimiter: (str, start) -> # Search the string for each opening delimiter - matches = ({options: o, pos: str.indexOf(o.opener, start)} for o in @delimiters_map when o.enabled()) - positions = (m.pos for m in matches when m.pos >= 0) - - # No opening delimiters were found - if positions.length == 0 - return null - - # Take the first delimiter found - pos = Math.min.apply Math, positions - - match_index = (m.pos for m in matches).indexOf(pos) - match = matches[match_index] - - return match - - class Boundary - length: -> - return @end - @start - - extract: (str) -> - return str.substr @start, @length() - - # Returns the outer and inner boundaries of the latex block starting - # at the given opening delimiter - get_latex_boundaries: (str, opening_delimiter_match) -> - inner = new Boundary - outer = new Boundary - - # The closing delimiter matching to the opening one - closer = opening_delimiter_match.options.closer - - outer.start = opening_delimiter_match.pos - inner.start = opening_delimiter_match.pos + closer.length - - # Search for a closer delimiter after the opening one - closer_index = str.substr(inner.start).indexOf(closer) - if closer_index < 0 - return null - - inner.end = inner.start + closer_index - outer.end = inner.end + closer.length - - return { - outer: outer - inner: inner - } - - # Searches for the first latex block in the given string - find_latex: (str) -> - start = 0 - while (opening_delimiter_match = @find_opening_delimiter str, start++)? - - match = @get_latex_boundaries str, opening_delimiter_match - - if match?.inner.extract(str).trim().length - match.options = opening_delimiter_match.options - return match - - return null - - # Breaks a message to what comes before, after and to the content of a - # matched latex block - extract_latex: (str, match) -> - before = str.substr 0, match.outer.start - after = str.substr match.outer.end - - latex = match.inner.extract str - latex = s.unescapeHTML latex - - return { before: before, latex : latex, after : after } - - # Takes a latex math string and the desired display mode and renders it - # to HTML using the KaTeX library - render_latex: (latex, displayMode) -> - try - rendered = katex.renderToString latex , {displayMode: displayMode} - catch e - display_mode = if displayMode then "block" else "inline" - rendered = "
    " - rendered += "#{s.escapeHTML e.message}" - rendered += "
    " - - return rendered - - # Takes a string and renders all latex blocks inside it - render: (str, render_func) -> - result = '' - - loop - - # Find the first latex block in the string - match = @find_latex str - - unless match? - result += str - break - - parts = @extract_latex str, match - - # Add to the reuslt what comes before the latex block as well as - # the rendered latex content - rendered = render_func parts.latex, match.options.displayMode - result += parts.before + rendered - - # Set what comes after the latex block to be examined next - str = parts.after - - return result - - # Takes a rocketchat message and renders latex in its content - render_message: (message) -> - # Render only if enabled in admin panel - if @katex_enabled() - msg = message - - if not _.isString message - if _.trim message.html - msg = message.html - else - return message - - if _.isString message - render_func = (latex, displayMode) => - return @render_latex latex, displayMode - else - message.tokens ?= [] - - render_func = (latex, displayMode) => - token = "=&=#{Random.id()}=&=" - - message.tokens.push - token: token - text: @render_latex latex, displayMode - - return token - - msg = @render msg, render_func - - if not _.isString message - message.html = msg - else - message = msg - - return message - - katex_enabled: -> - return RocketChat.settings.get('Katex_Enabled') - - dollar_syntax_enabled: -> - return RocketChat.settings.get('Katex_Dollar_Syntax') - - parenthesis_syntax_enabled: -> - return RocketChat.settings.get('Katex_Parenthesis_Syntax') - - -RocketChat.katex = new Katex - -cb = RocketChat.katex.render_message.bind(RocketChat.katex) -RocketChat.callbacks.add 'renderMessage', cb, RocketChat.callbacks.priority.HIGH - 1, 'katex' - -if Meteor.isClient - Blaze.registerHelper 'RocketChatKatex', (text) -> - return RocketChat.katex.render_message text diff --git a/packages/rocketchat-katex/katex.js b/packages/rocketchat-katex/katex.js new file mode 100644 index 00000000000..234165def86 --- /dev/null +++ b/packages/rocketchat-katex/katex.js @@ -0,0 +1,255 @@ +/* + * KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web. + * https://github.com/Khan/KaTeX + */ +const katex = require('katex'); + +class Boundary { + constructor() {} + + length() { + return this.end - this.start; + } + + extract(str) { + return str.substr(this.start, this.length()); + } + +} + +class Katex { + constructor() { + this.delimiters_map = [ + { + opener: '\\[', + closer: '\\]', + displayMode: true, + enabled: () => { + return this.parenthesis_syntax_enabled(); + } + }, { + opener: '\\(', + closer: '\\)', + displayMode: false, + enabled: () => { + return this.parenthesis_syntax_enabled(); + } + }, { + opener: '$$', + closer: '$$', + displayMode: true, + enabled: () => { + return this.dollar_syntax_enabled(); + } + }, { + opener: '$', + closer: '$', + displayMode: false, + enabled: () => { + return this.dollar_syntax_enabled(); + } + } + ]; + } + // Searches for the first opening delimiter in the string from a given position + + find_opening_delimiter(str, start) { // Search the string for each opening delimiter + const matches = (() => { + const map = this.delimiters_map; + const results = []; + + map.forEach((op) => { + if (op.enabled()) { + results.push({ + options: op, + pos: str.indexOf(op.opener, start) + }); + } + }); + return results; + })(); + + const positions = (() => { + const results = []; + matches.forEach((pos) => { + if (pos.pos >= 0) { + results.push(pos.pos); + } + }); + return results; + })(); + + // No opening delimiters were found + if (positions.length === 0) { + return null; + } + + //Take the first delimiter found + const pos = Math.min.apply(Math, positions); + + const match_index = (()=> { + const results = []; + matches.forEach((m) => { + results.push(m.pos); + }); + return results; + })().indexOf(pos); + + const match = matches[match_index]; + return match; + } + + // Returns the outer and inner boundaries of the latex block starting + // at the given opening delimiter + get_latex_boundaries(str, opening_delimiter_match) { + const inner = new Boundary; + const outer = new Boundary; + + // The closing delimiter matching to the opening one + const closer = opening_delimiter_match.options.closer; + outer.start = opening_delimiter_match.pos; + inner.start = opening_delimiter_match.pos + closer.length; + + // Search for a closer delimiter after the opening one + const closer_index = str.substr(inner.start).indexOf(closer); + if (closer_index < 0) { + return null; + } + inner.end = inner.start + closer_index; + outer.end = inner.end + closer.length; + return { + outer, + inner + }; + } + + // Searches for the first latex block in the given string + find_latex(str) { + let start = 0; + let opening_delimiter_match; + + while ((opening_delimiter_match = this.find_opening_delimiter(str, start++)) != null) { + const match = this.get_latex_boundaries(str, opening_delimiter_match); + if (match && match.inner.extract(str).trim().length) { + match.options = opening_delimiter_match.options; + return match; + } + } + return null; + } + + // Breaks a message to what comes before, after and to the content of a + // matched latex block + extract_latex(str, match) { + const before = str.substr(0, match.outer.start); + const after = str.substr(match.outer.end); + let latex = match.inner.extract(str); + latex = s.unescapeHTML(latex); + return { + before, + latex, + after + }; + } + + // Takes a latex math string and the desired display mode and renders it + // to HTML using the KaTeX library + render_latex(latex, displayMode) { + let rendered; + try { + rendered = katex.renderToString(latex, { + displayMode + }); + } catch (error) { + const e = error; + const display_mode = displayMode ? 'block' : 'inline'; + rendered = `
    `; + rendered += `${ s.escapeHTML(e.message) }`; + rendered += '
    '; + } + return rendered; + } + + // Takes a string and renders all latex blocks inside it + render(str, render_func) { + let result = ''; + while (this.find_latex(str) != null) { + // Find the first latex block in the string + const match = this.find_latex(str); + const parts = this.extract_latex(str, match); + + // Add to the reuslt what comes before the latex block as well as + // the rendered latex content + const rendered = render_func(parts.latex, match.options.displayMode); + result += parts.before + rendered; + // Set what comes after the latex block to be examined next + str = parts.after; + } + return result += str; + } + + // Takes a rocketchat message and renders latex in its content + render_message(message) { + //Render only if enabled in admin panel + let render_func; + if (this.katex_enabled()) { + let msg = message; + if (!_.isString(message)) { + if (_.trim(message.html)) { + msg = message.html; + } else { + return message; + } + } + if (_.isString(message)) { + render_func = (latex, displayMode) => { + return this.render_latex(latex, displayMode); + }; + } else { + if (message.tokens == null) { + message.tokens = []; + } + render_func = (latex, displayMode) => { + const token = `=!=${ Random.id() }=!=`; + message.tokens.push({ + token, + text: this.render_latex(latex, displayMode) + }); + return token; + }; + } + msg = this.render(msg, render_func); + if (!_.isString(message)) { + message.html = msg; + } else { + message = msg; + } + } + return message; + } + + katex_enabled() { + return RocketChat.settings.get('Katex_Enabled'); + } + + dollar_syntax_enabled() { + return RocketChat.settings.get('Katex_Dollar_Syntax'); + } + + parenthesis_syntax_enabled() { + return RocketChat.settings.get('Katex_Parenthesis_Syntax'); + } + +} + +RocketChat.katex = new Katex; + +const cb = RocketChat.katex.render_message.bind(RocketChat.katex); + +RocketChat.callbacks.add('renderMessage', cb, RocketChat.callbacks.priority.HIGH - 1, 'katex'); + +if (Meteor.isClient) { + Blaze.registerHelper('RocketChatKatex', function(text) { + return RocketChat.katex.render_message(text); + }); +} diff --git a/packages/rocketchat-katex/package.js b/packages/rocketchat-katex/package.js index 033ea7efeee..de08f7f50f5 100644 --- a/packages/rocketchat-katex/package.js +++ b/packages/rocketchat-katex/package.js @@ -6,15 +6,14 @@ Package.describe({ }); Package.onUse(function(api) { - api.use('coffeescript'); api.use('ecmascript'); api.use('underscore'); api.use('templating'); api.use('underscorestring:underscore.string'); api.use('rocketchat:lib'); - api.addFiles('settings.coffee', 'server'); - api.addFiles('katex.coffee'); + api.addFiles('settings.js', 'server'); + api.addFiles('katex.js'); api.addFiles('client/style.css', 'client'); const katexPath = 'node_modules/katex/dist/'; diff --git a/packages/rocketchat-katex/settings.coffee b/packages/rocketchat-katex/settings.coffee deleted file mode 100644 index 255aa3dc9c2..00000000000 --- a/packages/rocketchat-katex/settings.coffee +++ /dev/null @@ -1,6 +0,0 @@ -Meteor.startup -> - enableQuery = {_id: 'Katex_Enabled', value: true} - RocketChat.settings.add 'Katex_Enabled', true, {type: 'boolean', group: 'Message', section: 'Katex', public: true, i18n: 'Katex_Enabled_Description'} - - RocketChat.settings.add 'Katex_Parenthesis_Syntax', true, {type: 'boolean', group: 'Message', section: 'Katex', public: true, enableQuery, i18nDescription: 'Katex_Parenthesis_Syntax_Description'} - RocketChat.settings.add 'Katex_Dollar_Syntax', false, {type: 'boolean', group: 'Message', section: 'Katex', public: true, enableQuery, i18nDescription: 'Katex_Dollar_Syntax_Description'} diff --git a/packages/rocketchat-katex/settings.js b/packages/rocketchat-katex/settings.js new file mode 100644 index 00000000000..006ef790e64 --- /dev/null +++ b/packages/rocketchat-katex/settings.js @@ -0,0 +1,32 @@ +Meteor.startup(function() { + const enableQuery = { + _id: 'Katex_Enabled', + value: true + }; + RocketChat.settings.add('Katex_Enabled', true, { + type: 'boolean', + group: 'Message', + section: 'Katex', + 'public': true, + i18n: 'Katex_Enabled_Description' + }); + RocketChat.settings.add('Katex_Parenthesis_Syntax', true, { + type: 'boolean', + group: 'Message', + section: 'Katex', + 'public': true, + enableQuery, + i18nDescription: 'Katex_Parenthesis_Syntax_Description' + }); + return RocketChat.settings.add('Katex_Dollar_Syntax', false, { + type: 'boolean', + group: 'Message', + section: 'Katex', + 'public': true, + enableQuery, + i18nDescription: 'Katex_Dollar_Syntax_Description' + }); +}); + +// --- +// generated by coffee-script 1.9.2 diff --git a/packages/rocketchat-lib/client/MessageAction.js b/packages/rocketchat-lib/client/MessageAction.js index be10a478362..db2ee342a49 100644 --- a/packages/rocketchat-lib/client/MessageAction.js +++ b/packages/rocketchat-lib/client/MessageAction.js @@ -177,14 +177,12 @@ Meteor.startup(function() { return chatMessages[Session.get('openedRoom')].confirmDeleteMsg(message); }, validation(message) { - if (RocketChat.models.Subscriptions.findOne({ - rid: message.rid - }) == null) { + if (RocketChat.models.Subscriptions.findOne({rid: message.rid}) == null) { return false; } const hasPermission = RocketChat.authz.hasAtLeastOnePermission('delete-message', message.rid); const isDeleteAllowed = RocketChat.settings.get('Message_AllowDeleting'); - const deleteOwn = message.u && message.u_id === Meteor.userId(); + const deleteOwn = message.u && message.u._id === Meteor.userId(); if (!(hasPermission || (isDeleteAllowed && deleteOwn))) { return; } diff --git a/packages/rocketchat-lib/client/lib/cachedCollection.js b/packages/rocketchat-lib/client/lib/cachedCollection.js index 23fd088e169..6a9d9db88e0 100644 --- a/packages/rocketchat-lib/client/lib/cachedCollection.js +++ b/packages/rocketchat-lib/client/lib/cachedCollection.js @@ -165,7 +165,7 @@ class CachedCollection { } localforage.getItem(this.name, (error, data) => { - if (data && (data.version < this.version || data.token !== this.getToken())) { + if (data && (data.version < this.version || data.token !== this.getToken() || this.getToken() === undefined)) { this.clearCache(); callback(false); return; diff --git a/packages/rocketchat-lib/client/lib/openRoom.coffee b/packages/rocketchat-lib/client/lib/openRoom.coffee index af932dfc5aa..3130cd82a3e 100644 --- a/packages/rocketchat-lib/client/lib/openRoom.coffee +++ b/packages/rocketchat-lib/client/lib/openRoom.coffee @@ -5,12 +5,13 @@ currentTracker = undefined Meteor.defer -> currentTracker = Tracker.autorun (c) -> - if RoomManager.open(type + name).ready() isnt true - BlazeLayout.render 'main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' } + user = Meteor.user() + if (user? and not user.username?) or (not user? and RocketChat.settings.get('Accounts_AllowAnonymousRead') is false) + BlazeLayout.render 'main' return - user = Meteor.user() - unless user?.username + if RoomManager.open(type + name).ready() isnt true + BlazeLayout.render 'main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' } return currentTracker = undefined diff --git a/packages/rocketchat-lib/client/lib/openRoom.js b/packages/rocketchat-lib/client/lib/openRoom.js new file mode 100644 index 00000000000..b283b51c08a --- /dev/null +++ b/packages/rocketchat-lib/client/lib/openRoom.js @@ -0,0 +1,98 @@ +/* globals fireGlobalEvent readMessage currentTracker*/ +currentTracker = undefined; + +function openRoom(type, name) { + Session.set('openedRoom', null); + + return Meteor.defer(() => + currentTracker = Tracker.autorun(function(c) { + const user = Meteor.user(); + if ((user && user.username == null) || user == null && RocketChat.settings.get('Accounts_AllowAnonymousAccess') === false) { + BlazeLayout.render('main'); + return; + } + + if (RoomManager.open(type + name).ready() !== true) { + BlazeLayout.render('main', { modal: RocketChat.Layout.isEmbedded(), center: 'loading' }); + return; + } + if (currentTracker) { + currentTracker = undefined; + } + c.stop(); + + const room = RocketChat.roomTypes.findRoom(type, name, user); + if (room == null) { + if (type === 'd') { + Meteor.call('createDirectMessage', name, function(err) { + if (!err) { + RoomManager.close(type + name); + return openRoom('d', name); + } else { + Session.set('roomNotFound', {type, name}); + BlazeLayout.render('main', {center: 'roomNotFound'}); + return; + } + }); + } else { + Meteor.call('getRoomByTypeAndName', type, name, function(err, record) { + if (err) { + Session.set('roomNotFound', {type, name}); + return BlazeLayout.render('main', {center: 'roomNotFound'}); + } else { + delete record.$loki; + RocketChat.models.Rooms.upsert({ _id: record._id }, _.omit(record, '_id')); + RoomManager.close(type + name); + return openRoom(type, name); + } + }); + } + return; + } + + const mainNode = document.querySelector('.main-content'); + if (mainNode) { + for (const child of Array.from(mainNode.children)) { + if (child) { mainNode.removeChild(child); } + } + const roomDom = RoomManager.getDomOfRoom(type + name, room._id); + mainNode.appendChild(roomDom); + if (roomDom.classList.contains('room-container')) { + roomDom.querySelector('.messages-box > .wrapper').scrollTop = roomDom.oldScrollTop; + } + } + + Session.set('openedRoom', room._id); + + fireGlobalEvent('room-opened', _.omit(room, 'usernames')); + + Session.set('editRoomTitle', false); + RoomManager.updateMentionsMarksOfRoom(type + name); + Meteor.setTimeout(() => readMessage.readNow(), 2000); + // KonchatNotification.removeRoomNotification(params._id) + + if (Meteor.Device.isDesktop() && window.chatMessages && window.chatMessages[room._id] != null) { + setTimeout(() => $('.message-form .input-message').focus(), 100); + } + + // update user's room subscription + const sub = ChatSubscription.findOne({rid: room._id}); + if (sub && sub.open === false) { + Meteor.call('openRoom', room._id, function(err) { + if (err) { + return handleError(err); + } + }); + } + + if (FlowRouter.getQueryParam('msg')) { + const msg = { _id: FlowRouter.getQueryParam('msg'), rid: room._id }; + RoomHistoryManager.getSurroundingMessages(msg); + } + + return RocketChat.callbacks.run('enter-room', sub); + }) + ); +} +export { openRoom }; +this.openRoom = openRoom; diff --git a/packages/rocketchat-lib/client/lib/roomExit.js b/packages/rocketchat-lib/client/lib/roomExit.js index 1612105dfe2..36f1260e342 100644 --- a/packages/rocketchat-lib/client/lib/roomExit.js +++ b/packages/rocketchat-lib/client/lib/roomExit.js @@ -12,7 +12,7 @@ this.roomExit = function() { if (mainNode == null) { return; } - return mainNode.children.forEach(child => { + return [...mainNode.children].forEach(child => { if (child == null) { return; } diff --git a/packages/rocketchat-lib/client/lib/roomTypes.coffee b/packages/rocketchat-lib/client/lib/roomTypes.coffee deleted file mode 100644 index 424636cd38a..00000000000 --- a/packages/rocketchat-lib/client/lib/roomTypes.coffee +++ /dev/null @@ -1,87 +0,0 @@ -RocketChat.roomTypes = new class roomTypesClient extends roomTypesCommon - checkCondition: (roomType) -> - return not roomType.condition? or roomType.condition() - - getTypes: -> - orderedTypes = [] - - _.sortBy(@roomTypesOrder, 'order').forEach (type) => - orderedTypes.push @roomTypes[type.identifier] - - return orderedTypes - - getIcon: (roomType) -> - return @roomTypes[roomType]?.icon - - getRoomName: (roomType, roomData) -> - return @roomTypes[roomType]?.roomName roomData - - getIdentifiers: (except) -> - except = [].concat except - 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 - - canSendMessage: (roomId) -> - return ChatSubscription.find({ rid: roomId }).count() > 0 - - readOnly: (roomId, user) -> - - fields = { ro: 1 } - - # if a user has been specified then we want to see if that user has been muted in the room - if user - fields.muted = 1 - - room = ChatRoom.findOne({ _id: roomId }, fields : fields) - - unless user - return room?.ro; - - userOwner = RoomRoles.findOne({ rid: roomId, "u._id": user._id, roles: 'owner' }, { fields: { _id: 1 } }) - - return room?.ro is true and Array.isArray(room?.muted) and room?.muted.indexOf(user.username) != -1 and !userOwner - - archived: (roomId) -> - fields = { archived: 1 } - - room = ChatRoom.findOne({ _id: roomId }, fields : fields) - - return room?.archived is true - - verifyCanSendMessage: (roomId) -> - room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) - return if not room?.t? - - roomType = room.t - - return @roomTypes[roomType]?.canSendMessage roomId if @roomTypes[roomType]?.canSendMessage? - - return @canSendMessage roomId - - verifyShowJoinLink: (roomId) -> - room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) - return if not room?.t? - - roomType = room.t - - if not @roomTypes[roomType]?.showJoinLink? - return false - - return @roomTypes[roomType].showJoinLink roomId - - getNotSubscribedTpl: (roomId) -> - room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) - return if not room?.t? - - roomType = room.t - - if not @roomTypes[roomType]?.notSubscribedTpl? - return false - - return @roomTypes[roomType].notSubscribedTpl diff --git a/packages/rocketchat-lib/client/lib/roomTypes.js b/packages/rocketchat-lib/client/lib/roomTypes.js new file mode 100644 index 00000000000..9cfc4158596 --- /dev/null +++ b/packages/rocketchat-lib/client/lib/roomTypes.js @@ -0,0 +1,115 @@ +import roomTypesCommon from '../../lib/roomTypesCommon'; + +RocketChat.roomTypes = new class extends roomTypesCommon { + checkCondition(roomType) { + return roomType.condition == null || roomType.condition(); + } + getTypes() { + return _.sortBy(this.roomTypesOrder, 'order').map((type) => this.roomTypes[type.identifier]); + } + getIcon(roomType) { + return this.roomTypes[roomType] && this.roomTypes[roomType].icon; + } + getRoomName(roomType, roomData) { + return this.roomTypes[roomType] && this.roomTypes[roomType].roomName && this.roomTypes[roomType].roomName(roomData); + } + getSecondaryRoomName(roomType, roomData) { + return this.roomTypes[roomType] && typeof this.roomTypes[roomType].secondaryRoomName === 'function' && this.roomTypes[roomType].secondaryRoomName(roomData); + } + getIdentifiers(e) { + const except = [].concat(e); + const list = _.reject(this.roomTypesOrder, (t) => except.indexOf(t.identifier) !== -1); + return _.map(list, (t) => t.identifier); + } + getUserStatus(roomType, roomId) { + this.roomTypes[roomType] && typeof this.roomTypes[roomType].getUserStatus === 'function' && this.roomTypes[roomType].getUserStatus(roomId); + } + findRoom(roomType, identifier, user) { + return this.roomTypes[roomType] && this.roomTypes[roomType].findRoom(identifier, user); + } + canSendMessage(roomId) { + return ChatSubscription.find({ + rid: roomId + }).count() > 0; + } + readOnly(roomId, user) { + const fields = { + ro: 1 + }; + if (user) { + fields.muted = 1; + } + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields + }); + if (!user) { + return room && room.ro; + } + /* globals RoomRoles */ + const userOwner = RoomRoles.findOne({ + rid: roomId, + 'u._id': user._id, + roles: 'owner' + }, { + fields: { + _id: 1 + } + }); + return room && (room.ro === true && Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !userOwner); + } + archived(roomId) { + const fields = { + archived: 1 + }; + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields + }); + return room && room.archived === true; + } + verifyCanSendMessage(roomId) { + const room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 }}); + + if (!room || !room.t) { + return; + } + + const roomType = room.t; + if (this.roomTypes[roomType] && this.roomTypes[roomType].canSendMessage) { + return this.roomTypes[roomType].canSendMessage(roomId); + } + return this.canSendMessage(roomId); + } + verifyShowJoinLink(roomId) { + const room = ChatRoom.findOne({ + _id: roomId + }, { + fields: { + t: 1 + } + }); + if (!room || !room.t) { + return; + } + const roomType = room.t; + if (this.roomTypes[roomType] && !this.roomTypes[roomType].showJoinLink) { + return false; + } + return this.roomTypes[roomType].showJoinLink(roomId); + } + getNotSubscribedTpl(roomId) { + const room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 }}); + if (!room || !room.t) { + return; + } + const roomType = room.t; + if (this.roomTypes[roomType] && !this.roomTypes[roomType].notSubscribedTpl) { + return false; + } + return this.roomTypes[roomType].notSubscribedTpl; + } + +}; diff --git a/packages/rocketchat-lib/client/methods/sendMessage.js b/packages/rocketchat-lib/client/methods/sendMessage.js index cc37a2611f5..85cce415c2a 100644 --- a/packages/rocketchat-lib/client/methods/sendMessage.js +++ b/packages/rocketchat-lib/client/methods/sendMessage.js @@ -3,11 +3,15 @@ Meteor.methods({ if (!Meteor.userId() || _.trim(message.msg) === '') { return false; } + const user = Meteor.user(); message.ts = isNaN(TimeSync.serverOffset()) ? new Date() : new Date(Date.now() + TimeSync.serverOffset()); message.u = { _id: Meteor.userId(), - username: Meteor.user().username + username: user.username }; + if (RocketChat.settings.get('UI_Use_Real_Name')) { + message.u.name = user.name; + } message.temp = true; message = RocketChat.callbacks.run('beforeSaveMessage', message); RocketChat.promises.run('onClientMessageReceived', message).then(function(message) { diff --git a/packages/rocketchat-lib/lib/callbacks.coffee b/packages/rocketchat-lib/lib/callbacks.coffee deleted file mode 100644 index d0e3feb8ff3..00000000000 --- a/packages/rocketchat-lib/lib/callbacks.coffee +++ /dev/null @@ -1,129 +0,0 @@ -# https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-lib/lib/callbacks.js - -### -# Callback hooks provide an easy way to add extra steps to common operations. -# @namespace RocketChat.callbacks -### -RocketChat.callbacks = {} - -if Meteor.isServer - RocketChat.callbacks.showTime = true - RocketChat.callbacks.showTotalTime = true -else - RocketChat.callbacks.showTime = false - RocketChat.callbacks.showTotalTime = false - -### -# Callback priorities -### -RocketChat.callbacks.priority = - HIGH: -1000 - MEDIUM: 0 - LOW: 1000 - -### -# Add a callback function to a hook -# @param {String} hook - The name of the hook -# @param {Function} callback - The callback function -### -RocketChat.callbacks.add = (hook, callback, priority, id) -> - # if callback array doesn't exist yet, initialize it - priority ?= RocketChat.callbacks.priority.MEDIUM - unless _.isNumber priority - priority = RocketChat.callbacks.priority.MEDIUM - callback.priority = priority - callback.id = id or Random.id() - RocketChat.callbacks[hook] ?= [] - - if RocketChat.callbacks.showTime is true - err = new Error - callback.stack = err.stack - - # if not id? - # console.log('Callback without id', callback.stack) - - # Avoid adding the same callback twice - for cb in RocketChat.callbacks[hook] - if cb.id is callback.id - return - - RocketChat.callbacks[hook].push callback - return - -### -# Remove a callback from a hook -# @param {string} hook - The name of the hook -# @param {string} id - The callback's id -### - -RocketChat.callbacks.remove = (hookName, id) -> - RocketChat.callbacks[hookName] = _.reject RocketChat.callbacks[hookName], (callback) -> - callback.id is id - return - -### -# Successively run all of a hook's callbacks on an item -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -# @returns {Object} Returns the item after it's been through all the callbacks for this hook -### - -RocketChat.callbacks.run = (hook, item, constant) -> - callbacks = RocketChat.callbacks[hook] - if !!callbacks?.length - if RocketChat.callbacks.showTotalTime is true - totalTime = 0 - - # if the hook exists, and contains callbacks to run - result = _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.callbacks.priority.MEDIUM).reduce (result, callback) -> - # console.log(callback.name); - if RocketChat.callbacks.showTime is true or RocketChat.callbacks.showTotalTime is true - time = Date.now() - - callbackResult = callback result, constant - - if RocketChat.callbacks.showTime is true or RocketChat.callbacks.showTotalTime is true - currentTime = Date.now() - time - totalTime += currentTime - if RocketChat.callbacks.showTime is true - if Meteor.isServer - RocketChat.statsTracker.timing('callbacks.time', currentTime, ["hook:#{hook}", "callback:#{callback.id}"]); - else - console.log String(currentTime), hook, callback.id, callback.stack?.split?('\n')[2]?.match(/\(.+\)/)?[0] - - return if typeof callbackResult == 'undefined' then result else callbackResult - , item - - if RocketChat.callbacks.showTotalTime is true - if Meteor.isServer - RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, ["hook:#{hook}"]); - else - console.log hook+':', totalTime - - return result - else - # else, just return the item unchanged - return item - -### -# Successively run all of a hook's callbacks on an item, in async mode (only works on server) -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -### - -RocketChat.callbacks.runAsync = (hook, item, constant) -> - callbacks = RocketChat.callbacks[hook] - if Meteor.isServer and !!callbacks?.length - # use defer to avoid holding up client - Meteor.defer -> - # run all post submit server callbacks on post object successively - _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.callbacks.priority.MEDIUM).forEach (callback) -> - # console.log(callback.name); - callback item, constant - return - return - else - return item - return diff --git a/packages/rocketchat-lib/lib/callbacks.js b/packages/rocketchat-lib/lib/callbacks.js new file mode 100644 index 00000000000..86f56a2eea8 --- /dev/null +++ b/packages/rocketchat-lib/lib/callbacks.js @@ -0,0 +1,131 @@ +/* +* Callback hooks provide an easy way to add extra steps to common operations. +* @namespace RocketChat.callbacks +*/ + +RocketChat.callbacks = {}; + +if (Meteor.isServer) { + RocketChat.callbacks.showTime = true; + RocketChat.callbacks.showTotalTime = true; +} else { + RocketChat.callbacks.showTime = false; + RocketChat.callbacks.showTotalTime = false; +} + + +/* +* Callback priorities +*/ + +RocketChat.callbacks.priority = { + HIGH: -1000, + MEDIUM: 0, + LOW: 1000 +}; + + +/* +* Add a callback function to a hook +* @param {String} hook - The name of the hook +* @param {Function} callback - The callback function +*/ + +RocketChat.callbacks.add = function(hook, callback, priority, id) { + if (priority == null) { + priority = RocketChat.callbacks.priority.MEDIUM; + } + if (!_.isNumber(priority)) { + priority = RocketChat.callbacks.priority.MEDIUM; + } + callback.priority = priority; + callback.id = id || Random.id(); + RocketChat.callbacks[hook] = RocketChat.callbacks[hook] || []; + if (RocketChat.callbacks.showTime === true) { + const err = new Error; + callback.stack = err.stack; + } + if (RocketChat.callbacks[hook].find((cb) => cb.id === callback.id)) { + return; + } + RocketChat.callbacks[hook].push(callback); +}; + + +/* +* Remove a callback from a hook +* @param {string} hook - The name of the hook +* @param {string} id - The callback's id +*/ + +RocketChat.callbacks.remove = function(hookName, id) { + RocketChat.callbacks[hookName] = _.reject(RocketChat.callbacks[hookName], (callback) => callback.id === id); +}; + + +/* +* Successively run all of a hook's callbacks on an item +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +* @returns {Object} Returns the item after it's been through all the callbacks for this hook +*/ + +RocketChat.callbacks.run = function(hook, item, constant) { + const callbacks = RocketChat.callbacks[hook]; + if (callbacks && callbacks.length) { + let totalTime = 0; + const result = _.sortBy(callbacks, function(callback) { + return callback.priority || RocketChat.callbacks.priority.MEDIUM; + }).reduce(function(result, callback) { + let time = 0; + if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) { + time = Date.now(); + } + const callbackResult = callback(result, constant); + if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) { + const currentTime = Date.now() - time; + totalTime += currentTime; + if (RocketChat.callbacks.showTime === true) { + if (Meteor.isServer) { + RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]); + } else { + let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n'); + stack = stack && stack[2] && (stack[2].match(/\(.+\)/)||[])[0]; + console.log(String(currentTime), hook, callback.id, stack); + } + } + } + return (typeof callbackResult === 'undefined') ? result : callbackResult; + }, item); + if (RocketChat.callbacks.showTotalTime === true) { + if (Meteor.isServer) { + RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]); + } else { + console.log(`${ hook }:`, totalTime); + } + } + return result; + } else { + return item; + } +}; + + +/* +* Successively run all of a hook's callbacks on an item, in async mode (only works on server) +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +*/ + +RocketChat.callbacks.runAsync = function(hook, item, constant) { + const callbacks = RocketChat.callbacks[hook]; + if (Meteor.isServer && callbacks && callbacks.length) { + Meteor.defer(function() { + _.sortBy(callbacks, (callback) => callback.priority || RocketChat.callbacks.priority.MEDIUM).forEach((callback) => callback(item, constant)); + }); + } else { + return item; + } +}; diff --git a/packages/rocketchat-lib/lib/promises.coffee b/packages/rocketchat-lib/lib/promises.coffee deleted file mode 100644 index a23ab907b73..00000000000 --- a/packages/rocketchat-lib/lib/promises.coffee +++ /dev/null @@ -1,93 +0,0 @@ -# https://github.com/TelescopeJS/Telescope/blob/master/packages/telescope-lib/lib/callbacks.js - -### -# Callback hooks provide an easy way to add extra steps to common operations. -# @namespace RocketChat.promises -### -RocketChat.promises = {} - -### -# Callback priorities -### -RocketChat.promises.priority = - HIGH: -1000 - MEDIUM: 0 - LOW: 1000 - -### -# Add a callback function to a hook -# @param {String} hook - The name of the hook -# @param {Function} callback - The callback function -### - -RocketChat.promises.add = (hook, callback, priority, id) -> - # if callback array doesn't exist yet, initialize it - priority ?= RocketChat.promises.priority.MEDIUM - unless _.isNumber priority - priority = RocketChat.promises.priority.MEDIUM - callback.priority = priority - callback.id = id or Random.id() - RocketChat.promises[hook] ?= [] - - # Avoid adding the same callback twice - for cb in RocketChat.promises[hook] - if cb.id is callback.id - return - - RocketChat.promises[hook].push callback - return - -### -# Remove a callback from a hook -# @param {string} hook - The name of the hook -# @param {string} id - The callback's id -### - -RocketChat.promises.remove = (hookName, id) -> - RocketChat.promises[hookName] = _.reject RocketChat.promises[hookName], (callback) -> - callback.id is id - return - -### -# Successively run all of a hook's callbacks on an item -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -# @returns {Object} Returns the item after it's been through all the callbacks for this hook -### - -RocketChat.promises.run = (hook, item, constant) -> - callbacks = RocketChat.promises[hook] - if !!callbacks?.length - # if the hook exists, and contains callbacks to run - callbacks = _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.promises.priority.MEDIUM) - return callbacks.reduce (previousPromise, callback) -> - return new Promise (resolve, reject) -> - previousPromise.then (result) -> - callback(result, constant).then(resolve, reject) - , Promise.resolve(item) - else - # else, just return the item unchanged - return Promise.resolve(item) - -### -# Successively run all of a hook's callbacks on an item, in async mode (only works on server) -# @param {String} hook - The name of the hook -# @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -# @param {Object} [constant] - An optional constant that will be passed along to each callback -### - -RocketChat.promises.runAsync = (hook, item, constant) -> - callbacks = RocketChat.promises[hook] - if Meteor.isServer and !!callbacks?.length - # use defer to avoid holding up client - Meteor.defer -> - # run all post submit server callbacks on post object successively - _.sortBy(callbacks, (callback) -> return callback.priority or RocketChat.promises.priority.MEDIUM).forEach (callback) -> - # console.log(callback.name); - callback item, constant - return - return - else - return item - return diff --git a/packages/rocketchat-lib/lib/promises.js b/packages/rocketchat-lib/lib/promises.js new file mode 100644 index 00000000000..1e040f172bc --- /dev/null +++ b/packages/rocketchat-lib/lib/promises.js @@ -0,0 +1,89 @@ + +/* +* Callback hooks provide an easy way to add extra steps to common operations. +* @namespace RocketChat.promises +*/ + +RocketChat.promises = {}; + + +/* +* Callback priorities +*/ + +RocketChat.promises.priority = { + HIGH: -1000, + MEDIUM: 0, + LOW: 1000 +}; + + +/* +* Add a callback function to a hook +* @param {String} hook - The name of the hook +* @param {Function} callback - The callback function +*/ + +RocketChat.promises.add = function(hook, callback, p = RocketChat.promises.priority.MEDIUM, id) { + const priority = !_.isNumber(p) ? RocketChat.promises.priority.MEDIUM : p; + callback.priority = priority; + callback.id = id || Random.id(); + RocketChat.promises[hook] = RocketChat.promises[hook] || []; + if (RocketChat.promises[hook].find(cb => cb.id === callback.id)) { + return; + } + RocketChat.promises[hook].push(callback); +}; + + +/* +* Remove a callback from a hook +* @param {string} hook - The name of the hook +* @param {string} id - The callback's id +*/ + +RocketChat.promises.remove = function(hookName, id) { + RocketChat.promises[hookName] = _.reject(RocketChat.promises[hookName], (callback) => callback.id === id); +}; + + +/* +* Successively run all of a hook's callbacks on an item +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +* @returns {Object} Returns the item after it's been through all the callbacks for this hook +*/ + +RocketChat.promises.run = function(hook, item, constant) { + let callbacks = RocketChat.promises[hook]; + if (callbacks == null || callbacks.length === 0) { + return Promise.resolve(item); + } + callbacks = _.sortBy(callbacks, (callback) => callback.priority || RocketChat.promises.priority.MEDIUM); + return callbacks.reduce(function(previousPromise, callback) { + return new Promise(function(resolve, reject) { + return previousPromise.then((result) => callback(result, constant).then(resolve, reject)); + }); + }, Promise.resolve(item)); +}; + + +/* +* Successively run all of a hook's callbacks on an item, in async mode (only works on server) +* @param {String} hook - The name of the hook +* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks +* @param {Object} [constant] - An optional constant that will be passed along to each callback +*/ + +RocketChat.promises.runAsync = function(hook, item, constant) { + const callbacks = RocketChat.promises[hook]; + if (!Meteor.isServer || callbacks == null || callbacks.length === 0) { + return item; + } + Meteor.defer(() => { + _.sortBy(callbacks, (callback) => callback.priority || RocketChat.promises.priority.MEDIUM).forEach(function(callback) { + callback(item, constant); + }); + }); +}; diff --git a/packages/rocketchat-lib/lib/roomTypesCommon.coffee b/packages/rocketchat-lib/lib/roomTypesCommon.coffee deleted file mode 100644 index a351dc25892..00000000000 --- a/packages/rocketchat-lib/lib/roomTypesCommon.coffee +++ /dev/null @@ -1,75 +0,0 @@ -class @roomTypesCommon - roomTypes: {} - roomTypesOrder: [] - mainOrder: 1 - - ### Adds a room type to app - @param identifier An identifier to the room type. If a real room, MUST BE the same of `db.rocketchat_room.t` field, if not, can be null - @param order Order number of the type - @param config - template: template name to render on sideNav - icon: icon class - route: - name: route name - action: route action function - ### - add: (identifier, order, config) -> - unless identifier? - identifier = Random.id() - - if @roomTypes[identifier]? - return false - - if not order? - order = @mainOrder + 10 - @mainOrder += 10 - - # @TODO validate config options - @roomTypesOrder.push - identifier: identifier - order: order - @roomTypes[identifier] = config - - if config.route?.path? and config.route?.name? and config.route?.action? - routeConfig = - name: config.route.name - action: config.route.action - - if Meteor.isClient - routeConfig.triggersExit = [ roomExit ] - - FlowRouter.route config.route.path, routeConfig - - hasCustomLink: (roomType) -> - return @roomTypes[roomType]?.route?.link? - - ### - @param roomType: room type (e.g.: c (for channels), d (for direct channels)) - @param subData: the user's subscription data - ### - getRouteLink: (roomType, subData) -> - 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.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/lib/roomTypesCommon.js b/packages/rocketchat-lib/lib/roomTypesCommon.js new file mode 100644 index 00000000000..1df20edae0b --- /dev/null +++ b/packages/rocketchat-lib/lib/roomTypesCommon.js @@ -0,0 +1,84 @@ +/* globals roomExit*/ +this.roomTypesCommon = class { + constructor() { + this.roomTypes = {}; + this.roomTypesOrder = []; + this.mainOrder = 1; + } + + /* Adds a room type to app + @param identifier An identifier to the room type. If a real room, MUST BE the same of `db.rocketchat_room.t` field, if not, can be null + @param order Order number of the type + @param config + template: template name to render on sideNav + icon: icon class + route: + name: route name + action: route action function + */ + + add(identifier = Random.id(), order, config) { + if (this.roomTypes[identifier] != null) { + return false; + } + if (order == null) { + order = this.mainOrder + 10; + this.mainOrder += 10; + } + this.roomTypesOrder.push({ + identifier, + order + }); + this.roomTypes[identifier] = config; + if (config.route && config.route.path && config.route.name && config.route.action) { + const routeConfig = { + name: config.route.name, + action: config.route.action + }; + if (Meteor.isClient) { + routeConfig.triggersExit = [roomExit]; + } + return FlowRouter.route(config.route.path, routeConfig); + } + } + + hasCustomLink(roomType) { + return this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link != null; + } + + /* + @param roomType: room type (e.g.: c (for channels), d (for direct channels)) + @param subData: the user's subscription data + */ + + getRouteLink(roomType, subData) { + if (this.roomTypes[roomType] == null) { + return false; + } + let routeData = {}; + if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) { + routeData = this.roomTypes[roomType].route.link(subData); + } else if (subData && subData.name) { + routeData = { + name: subData.name + }; + } + return FlowRouter.path(this.roomTypes[roomType].route.name, routeData); + } + + openRouteLink(roomType, subData, queryParams) { + if (this.roomTypes[roomType] == null) { + return false; + } + let routeData = {}; + if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) { + routeData = this.roomTypes[roomType].route.link(subData); + } else if (subData && subData.name) { + routeData = { + name: subData.name + }; + } + return FlowRouter.go(this.roomTypes[roomType].route.name, routeData, queryParams); + } +}; +export default this.roomTypesCommon; diff --git a/packages/rocketchat-lib/lib/settings.coffee b/packages/rocketchat-lib/lib/settings.coffee deleted file mode 100644 index cdc221064b3..00000000000 --- a/packages/rocketchat-lib/lib/settings.coffee +++ /dev/null @@ -1,83 +0,0 @@ -### -# RocketChat.settings holds all packages settings -# @namespace RocketChat.settings -### -RocketChat.settings = - callbacks: {} - regexCallbacks: {} - ts: new Date - - get: (_id, callback) -> - if callback? - RocketChat.settings.onload _id, callback - if _id is '*' and Meteor.settings? - for key, value of Meteor.settings - callback key, value - return - - if _.isRegExp(_id) - for key, value of Meteor.settings when _id.test(key) - callback key, value - return - - if Meteor.settings?[_id]? - callback _id, Meteor.settings?[_id] - else - if _.isRegExp(_id) - items = [] - for key, value of Meteor.settings when _id.test(key) - items.push - key: key - value: value - return items - - return Meteor.settings?[_id] - - set: (_id, value, callback) -> - Meteor.call 'saveSetting', _id, value, callback - - batchSet: (settings, callback) -> - - # async -> sync - # http://daemon.co.za/2012/04/simple-async-with-only-underscore/ - - save = (setting) -> - return (callback) -> - Meteor.call 'saveSetting', setting._id, setting.value, setting.editor, callback - - actions = _.map settings, (setting) -> save(setting) - _(actions).reduceRight(_.wrap, (err, success) -> return callback err, success)() - - load: (key, value, initialLoad) -> - if RocketChat.settings.callbacks[key]? - for callback in RocketChat.settings.callbacks[key] - callback key, value, initialLoad - - if RocketChat.settings.callbacks['*']? - for callback in RocketChat.settings.callbacks['*'] - callback key, value, initialLoad - - for cbKey, cbValue of RocketChat.settings.regexCallbacks - if cbValue.regex.test(key) - callback(key, value, initialLoad) for callback in cbValue.callbacks - - - onload: (key, callback) -> - # if key is '*' - # for key, value in Meteor.settings - # callback key, value, false - # else if Meteor.settings?[_id]? - # callback key, Meteor.settings[_id], false - - keys = [].concat key - - for k in keys - if _.isRegExp k - RocketChat.settings.regexCallbacks[k.source] ?= { - regex: k - callbacks: [] - } - RocketChat.settings.regexCallbacks[k.source].callbacks.push callback - else - RocketChat.settings.callbacks[k] ?= [] - RocketChat.settings.callbacks[k].push callback diff --git a/packages/rocketchat-lib/lib/settings.js b/packages/rocketchat-lib/lib/settings.js new file mode 100644 index 00000000000..9c14943dac6 --- /dev/null +++ b/packages/rocketchat-lib/lib/settings.js @@ -0,0 +1,99 @@ + +/* +* RocketChat.settings holds all packages settings +* @namespace RocketChat.settings +*/ +RocketChat.settings = { + callbacks: {}, + regexCallbacks: {}, + ts: new Date, + get(_id, callback) { + if (callback != null) { + RocketChat.settings.onload(_id, callback); + if (!Meteor.settings) { + return; + } + if (_id === '*') { + return Object.keys(Meteor.settings).forEach(key => { + const value = Meteor.settings[key]; + callback(key, value); + }); + } + if (_.isRegExp(_id) && Meteor.settings) { + return Object.keys(Meteor.settings).forEach(key => { + if (!_id.test(key)) { + return; + } + const value = Meteor.settings[key]; + callback(key, value); + }); + } + return Meteor.settings[_id] != null && callback(_id, Meteor.settings[_id]); + } else { + if (!Meteor.settings) { + return; + } + if (_.isRegExp(_id)) { + return Object.keys(Meteor.settings).reduce((items, key) => { + const value = Meteor.settings[key]; + if (_id.test(key)) { + items.push({ + key, + value + }); + } + return items; + }, []); + } + return Meteor.settings && Meteor.settings[_id]; + } + }, + set(_id, value, callback) { + return Meteor.call('saveSetting', _id, value, callback); + }, + batchSet(settings, callback) { + // async -> sync + // http://daemon.co.za/2012/04/simple-async-with-only-underscore/ + const save = function(setting) { + return function(callback) { + return Meteor.call('saveSetting', setting._id, setting.value, setting.editor, callback); + }; + }; + const actions = _.map(settings, (setting) => save(setting)); + return _(actions).reduceRight(_.wrap, (err, success) => callback(err, success))(); + }, + load(key, value, initialLoad) { + ['*', key].forEach(key => { + if (RocketChat.settings.callbacks[key]) { + RocketChat.settings.callbacks[key].forEach(callback => callback(key, value, initialLoad)); + } + }); + Object.keys(RocketChat.settings.regexCallbacks).forEach(cbKey => { + const cbValue = RocketChat.settings.regexCallbacks[cbKey]; + if (!cbValue.regex.test(key)) { + return; + } + cbValue.callbacks.forEach(callback => callback(key, value, initialLoad)); + }); + }, + onload(key, callback) { + // if key is '*' + // for key, value in Meteor.settings + // callback key, value, false + // else if Meteor.settings?[_id]? + // callback key, Meteor.settings[_id], false + const keys = [].concat(key); + keys.forEach(k => { + if (_.isRegExp(k)) { + RocketChat.settings.regexCallbacks[name = k.source] = RocketChat.settings.regexCallbacks[name = k.source] || { + regex: k, + callbacks: [] + }; + RocketChat.settings.regexCallbacks[k.source].callbacks.push(callback); + } else { + RocketChat.settings.callbacks[k] = RocketChat.settings.callbacks[k] || []; + RocketChat.settings.callbacks[k].push(callback); + } + }); + } +}; diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 53654548b23..3d7f8402927 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -52,12 +52,12 @@ Package.onUse(function(api) { // COMMON LIB api.addFiles('lib/getURL.js'); - api.addFiles('lib/settings.coffee'); - api.addFiles('lib/callbacks.coffee'); + api.addFiles('lib/settings.js'); + api.addFiles('lib/callbacks.js'); api.addFiles('lib/fileUploadRestrictions.js'); api.addFiles('lib/placeholders.js'); - api.addFiles('lib/promises.coffee'); - api.addFiles('lib/roomTypesCommon.coffee'); + api.addFiles('lib/promises.js'); + api.addFiles('lib/roomTypesCommon.js'); api.addFiles('lib/slashCommand.js'); api.addFiles('lib/Message.js'); api.addFiles('lib/MessageTypes.js'); @@ -134,6 +134,7 @@ Package.onUse(function(api) { api.addFiles('server/methods/checkRegistrationSecretURL.js', 'server'); api.addFiles('server/methods/cleanChannelHistory.js', 'server'); api.addFiles('server/methods/createChannel.js', 'server'); + api.addFiles('server/methods/createToken.js', 'server'); api.addFiles('server/methods/createPrivateGroup.js', 'server'); api.addFiles('server/methods/deleteMessage.js', 'server'); api.addFiles('server/methods/deleteUserOwnAccount.js', 'server'); @@ -178,10 +179,10 @@ Package.onUse(function(api) { api.addFiles('client/lib/TabBar.js', 'client'); api.addFiles('client/lib/RocketChatTabBar.js', 'client'); api.addFiles('client/lib/cachedCollection.js', 'client'); - api.addFiles('client/lib/openRoom.coffee', 'client'); + api.addFiles('client/lib/openRoom.js', 'client'); api.addFiles('client/lib/roomExit.js', 'client'); api.addFiles('client/lib/settings.js', 'client'); - api.addFiles('client/lib/roomTypes.coffee', 'client'); + api.addFiles('client/lib/roomTypes.js', 'client'); api.addFiles('client/lib/userRoles.js', 'client'); api.addFiles('client/lib/Layout.js', 'client'); diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-lib/rocketchat.info index 2f1d07807a2..ce43333a4eb 100644 --- a/packages/rocketchat-lib/rocketchat.info +++ b/packages/rocketchat-lib/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "0.55.0-develop" + "version": "0.56.0-develop" } diff --git a/packages/rocketchat-lib/server/functions/addUserToRoom.js b/packages/rocketchat-lib/server/functions/addUserToRoom.js index b27acb1f679..47ae76aeaab 100644 --- a/packages/rocketchat-lib/server/functions/addUserToRoom.js +++ b/packages/rocketchat-lib/server/functions/addUserToRoom.js @@ -27,8 +27,7 @@ RocketChat.addUserToRoom = function(rid, user, inviter, silenced) { ts: now, u: { _id: inviter._id, - username: inviter.username, - name: inviter.name + username: inviter.username } }); } else { diff --git a/packages/rocketchat-lib/server/functions/sendMessage.coffee b/packages/rocketchat-lib/server/functions/sendMessage.coffee index e25bf08cb8d..bdce87faba4 100644 --- a/packages/rocketchat-lib/server/functions/sendMessage.coffee +++ b/packages/rocketchat-lib/server/functions/sendMessage.coffee @@ -5,7 +5,7 @@ RocketChat.sendMessage = (user, message, room, upsert = false) -> unless message.ts? message.ts = new Date() - message.u = _.pick user, ['_id','username', 'name'] + message.u = _.pick user, ['_id','username'] if not Match.test(message.msg, String) message.msg = '' diff --git a/packages/rocketchat-lib/server/functions/setEmail.js b/packages/rocketchat-lib/server/functions/setEmail.js index 280e22425b1..e30f5942579 100644 --- a/packages/rocketchat-lib/server/functions/setEmail.js +++ b/packages/rocketchat-lib/server/functions/setEmail.js @@ -29,5 +29,5 @@ RocketChat._setEmail = function(userId, email) { }; RocketChat.setEmail = RocketChat.RateLimiter.limitFunction(RocketChat._setEmail, 1, 60000, { - 0(userId) { return !userId || !RocketChat.authz.hasPermission(userId, 'edit-other-user-info'); } // Administrators have permission to change others emails, so don't limit those + 0() { return !Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'edit-other-user-info'); } // Administrators have permission to change others emails, so don't limit those }); diff --git a/packages/rocketchat-lib/server/functions/setRealName.js b/packages/rocketchat-lib/server/functions/setRealName.js index 93d7545e779..359287b9b49 100644 --- a/packages/rocketchat-lib/server/functions/setRealName.js +++ b/packages/rocketchat-lib/server/functions/setRealName.js @@ -11,16 +11,18 @@ RocketChat._setRealName = function(userId, name) { return user; } - const previousName = user.name; - - if (previousName) { - RocketChat.models.Messages.updateAllNamesByUserId(user._id, name); - RocketChat.models.Subscriptions.setRealNameForDirectRoomsWithUsername(user.username, name); - } - // Set new name RocketChat.models.Users.setName(user._id, name); user.name = name; + + if (RocketChat.settings.get('UI_Use_Real_Name') === true) { + RocketChat.Notifications.notifyLogged('Users:NameChanged', { + _id: user._id, + name: user.name, + username: user.username + }); + } + return user; }; diff --git a/packages/rocketchat-lib/server/functions/setUsername.coffee b/packages/rocketchat-lib/server/functions/setUsername.coffee index 66163088f33..a3afa4a49e9 100644 --- a/packages/rocketchat-lib/server/functions/setUsername.coffee +++ b/packages/rocketchat-lib/server/functions/setUsername.coffee @@ -74,4 +74,4 @@ RocketChat._setUsername = (userId, username) -> return user RocketChat.setUsername = RocketChat.RateLimiter.limitFunction RocketChat._setUsername, 1, 60000, - 0: (userId) -> return not userId or not RocketChat.authz.hasPermission(userId, 'edit-other-user-info') # Administrators have permission to change others usernames, so don't limit those + 0: () -> return not Meteor.userId() or not RocketChat.authz.hasPermission(Meteor.userId(), 'edit-other-user-info') # Administrators have permission to change others usernames, so don't limit those diff --git a/packages/rocketchat-lib/server/functions/settings.coffee b/packages/rocketchat-lib/server/functions/settings.coffee index f8348261c5f..1098cd7941d 100644 --- a/packages/rocketchat-lib/server/functions/settings.coffee +++ b/packages/rocketchat-lib/server/functions/settings.coffee @@ -17,8 +17,9 @@ RocketChat.settings._sorter = {} RocketChat.settings.add = (_id, value, options = {}) -> # console.log '[functions] RocketChat.settings.add -> '.green, 'arguments:', arguments - if not _id or not value? - return false + if not _id or + not value? and not process?.env?['OVERWRITE_SETTING_' + _id]? + return false RocketChat.settings._sorter[options.group] ?= 0 diff --git a/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js b/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js index 5c7f20a11f9..8ed92b4eced 100644 --- a/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendEmailOnMessage.js @@ -64,20 +64,24 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { messageHTML = messageHTML.replace(/\n/gm, '
    '); RocketChat.models.Subscriptions.findWithSendEmailByRoomId(room._id).forEach((sub) => { - switch (sub.emailNotifications) { - case 'all': - usersToSendEmail[sub.u._id] = 'force'; - break; - case 'mentions': - if (usersToSendEmail[sub.u._id]) { + if (sub.disableNotifications) { + delete usersToSendEmail[sub.u._id]; + } else { + switch (sub.emailNotifications) { + case 'all': usersToSendEmail[sub.u._id] = 'force'; - } - break; - case 'nothing': - delete usersToSendEmail[sub.u._id]; - break; - case 'default': - break; + break; + case 'mentions': + if (usersToSendEmail[sub.u._id]) { + usersToSendEmail[sub.u._id] = 'force'; + } + break; + case 'nothing': + delete usersToSendEmail[sub.u._id]; + break; + case 'default': + break; + } } }); diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 85ab61fb111..ea6ef470118 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -66,15 +66,20 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { 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') { + if (subscription.disableNotifications) { settings.dontNotifyDesktopUsers.push(subscription.u._id); - } - if (subscription.mobilePushNotifications === 'all') { - settings.alwaysNotifyMobileUsers.push(subscription.u._id); - } else if (subscription.mobilePushNotifications === 'nothing') { settings.dontNotifyMobileUsers.push(subscription.u._id); + } else { + if (subscription.desktopNotifications === 'all') { + settings.alwaysNotifyDesktopUsers.push(subscription.u._id); + } else if (subscription.desktopNotifications === 'nothing') { + settings.dontNotifyDesktopUsers.push(subscription.u._id); + } + if (subscription.mobilePushNotifications === 'all') { + settings.alwaysNotifyMobileUsers.push(subscription.u._id); + } else if (subscription.mobilePushNotifications === 'nothing') { + settings.dontNotifyMobileUsers.push(subscription.u._id); + } } settings.desktopNotificationDurations[subscription.u._id] = subscription.desktopNotificationDuration; }); diff --git a/packages/rocketchat-lib/server/methods/archiveRoom.js b/packages/rocketchat-lib/server/methods/archiveRoom.js index 75df0fe4d8d..9f79b9539af 100644 --- a/packages/rocketchat-lib/server/methods/archiveRoom.js +++ b/packages/rocketchat-lib/server/methods/archiveRoom.js @@ -17,6 +17,10 @@ Meteor.methods({ throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'archiveRoom' }); } + if (room.t === 'd') { + throw new Meteor.Error('error-direct-message-room', 'Direct Messages can not be archived', { method: 'archiveRoom' }); + } + return RocketChat.archiveRoom(rid); } }); diff --git a/packages/rocketchat-lib/server/methods/createToken.js b/packages/rocketchat-lib/server/methods/createToken.js new file mode 100644 index 00000000000..29879093c1c --- /dev/null +++ b/packages/rocketchat-lib/server/methods/createToken.js @@ -0,0 +1,13 @@ +Meteor.methods({ + createToken(userId) { + if (Meteor.userId() !== userId && !RocketChat.authz.hasPermission(Meteor.userId(), 'user-generate-access-token')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'createToken' }); + } + const token = Accounts._generateStampedLoginToken(); + Accounts._insertLoginToken(userId, token); + return { + userId, + authToken: token.token + }; + } +}); diff --git a/packages/rocketchat-lib/server/methods/deleteMessage.js b/packages/rocketchat-lib/server/methods/deleteMessage.js index 896520de21f..73dd607fafc 100644 --- a/packages/rocketchat-lib/server/methods/deleteMessage.js +++ b/packages/rocketchat-lib/server/methods/deleteMessage.js @@ -32,16 +32,16 @@ Meteor.methods({ action: 'Delete_message' }); } - let msgTs; - let currentTsDiff; const blockDeleteInMinutes = RocketChat.settings.get('Message_AllowDeleting_BlockDeleteInMinutes'); - if ((blockDeleteInMinutes != null) && blockDeleteInMinutes !== 0) { - if (originalMessage.ts != null) { - msgTs = moment(originalMessage.ts); + if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) { + if (originalMessage.ts == null) { + return; } - if (msgTs != null) { - currentTsDiff = moment().diff(msgTs, 'minutes'); + const msgTs = moment(originalMessage.ts); + if (msgTs == null) { + return; } + const currentTsDiff = moment().diff(msgTs, 'minutes'); if (currentTsDiff > blockDeleteInMinutes) { throw new Meteor.Error('error-message-deleting-blocked', 'Message deleting is blocked', { method: 'deleteMessage' diff --git a/packages/rocketchat-lib/server/methods/getChannelHistory.js b/packages/rocketchat-lib/server/methods/getChannelHistory.js index 03958a40fae..e2f40ebe748 100644 --- a/packages/rocketchat-lib/server/methods/getChannelHistory.js +++ b/packages/rocketchat-lib/server/methods/getChannelHistory.js @@ -49,8 +49,14 @@ Meteor.methods({ records = RocketChat.models.Messages.findVisibleByRoomIdBetweenTimestamps(rid, oldest, latest, options).fetch(); } + const UI_Use_Real_Name = RocketChat.settings.get('UI_Use_Real_Name') === true; + const messages = _.map(records, (message) => { message.starred = _.findWhere(message.starred, { _id: fromUserId }); + if (message.u && message.u._id && UI_Use_Real_Name) { + const user = RocketChat.models.Users.findOneById(message.u._id); + message.u.name = user && user.name; + } return message; }); diff --git a/packages/rocketchat-lib/server/methods/getRoomRoles.js b/packages/rocketchat-lib/server/methods/getRoomRoles.js index 18ccaed6dbe..4c7dc6c105f 100644 --- a/packages/rocketchat-lib/server/methods/getRoomRoles.js +++ b/packages/rocketchat-lib/server/methods/getRoomRoles.js @@ -3,7 +3,7 @@ Meteor.methods({ check(rid, String); - if (!Meteor.userId()) { + if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomRoles' }); } diff --git a/packages/rocketchat-lib/server/methods/robotMethods.js b/packages/rocketchat-lib/server/methods/robotMethods.js index 2a983bfc776..e14fbd287b0 100644 --- a/packages/rocketchat-lib/server/methods/robotMethods.js +++ b/packages/rocketchat-lib/server/methods/robotMethods.js @@ -20,6 +20,6 @@ Meteor.methods({ }); } const cursor = RocketChat.models[model][method].apply(RocketChat.models[model], args); - return cursor && cursor.fetch && cursor.fetch(); + return cursor && cursor.fetch ? cursor.fetch() : cursor; } }); diff --git a/packages/rocketchat-lib/server/methods/setRealName.js b/packages/rocketchat-lib/server/methods/setRealName.js index 467bb979822..ee0ba56bf63 100644 --- a/packages/rocketchat-lib/server/methods/setRealName.js +++ b/packages/rocketchat-lib/server/methods/setRealName.js @@ -7,16 +7,6 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setRealName' }); } - const user = Meteor.user(); - - if (user.name === name) { - return name; - } - - if (_.trim(name)) { - name = _.trim(name); - } - if (!RocketChat.setRealName(Meteor.userId(), name)) { throw new Meteor.Error('error-could-not-change-name', 'Could not change name', { method: 'setRealName' }); } diff --git a/packages/rocketchat-lib/server/models/Messages.coffee b/packages/rocketchat-lib/server/models/Messages.coffee index 524133ae7b0..8ac7b4efcd7 100644 --- a/packages/rocketchat-lib/server/models/Messages.coffee +++ b/packages/rocketchat-lib/server/models/Messages.coffee @@ -320,16 +320,6 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base return @update query, update - updateAllNamesByUserId: (userId, name) -> - query = - 'u._id': userId - - update = - $set: - "u.name": name - - return @update query, update, { multi: true } - updateUserStarById: (_id, userId, starred) -> query = _id: _id @@ -390,7 +380,6 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base u: _id: user._id username: user.username - name: user.name groupable: false _.extend record, extraData diff --git a/packages/rocketchat-lib/server/models/Rooms.coffee b/packages/rocketchat-lib/server/models/Rooms.coffee index 1e11997cef5..1d58e578e11 100644 --- a/packages/rocketchat-lib/server/models/Rooms.coffee +++ b/packages/rocketchat-lib/server/models/Rooms.coffee @@ -188,6 +188,22 @@ class ModelRooms extends RocketChat.models._Base return @find query, options + findByNameAndType: (name, type, options) -> + query = + t: type + name: name + + return @find query, options + + findByNameAndTypeNotDefault: (name, type, options) -> + query = + t: type + name: name + default: + $ne: true + + return @find query, options + findByNameAndTypeNotContainingUsername: (name, type, username, options) -> query = t: type diff --git a/packages/rocketchat-lib/server/models/Subscriptions.coffee b/packages/rocketchat-lib/server/models/Subscriptions.coffee index 7b9aa80e605..95f12bff51f 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.coffee +++ b/packages/rocketchat-lib/server/models/Subscriptions.coffee @@ -21,6 +21,7 @@ class ModelSubscriptions extends RocketChat.models._Base this.cache.ensureIndex('rid', 'array') this.cache.ensureIndex('u._id', 'array') + this.cache.ensureIndex('name', 'array') this.cache.ensureIndex(['rid', 'u._id'], 'unique') this.cache.ensureIndex(['name', 'u._id'], 'unique') @@ -242,17 +243,6 @@ class ModelSubscriptions extends RocketChat.models._Base return @update query, update, { multi: true } - setRealNameForDirectRoomsWithUsername: (username, name) -> - query = - name: username - t: "d" - - update = - $set: - fname: name - - return @update query, update, { multi: true } - setNameForDirectRoomsWithOldName: (oldName, name) -> query = name: oldName diff --git a/packages/rocketchat-lib/server/models/Users.coffee b/packages/rocketchat-lib/server/models/Users.coffee index 8eef5f4ab51..83519874f9f 100644 --- a/packages/rocketchat-lib/server/models/Users.coffee +++ b/packages/rocketchat-lib/server/models/Users.coffee @@ -10,6 +10,8 @@ class ModelUsers extends RocketChat.models._Base @tryEnsureIndex { 'statusConnection': 1 }, { sparse: 1 } @tryEnsureIndex { 'type': 1 } + this.cache.ensureIndex('username', 'unique') + findOneByImportId: (_id, options) -> return @findOne { importIds: _id }, options @@ -128,7 +130,7 @@ class ModelUsers extends RocketChat.models._Base ] } { - username: { $nin: exceptions } + username: { $exists: true, $nin: exceptions } } ] diff --git a/packages/rocketchat-lib/server/models/_BaseCache.js b/packages/rocketchat-lib/server/models/_BaseCache.js index f31a0230d3c..ea7f6e77abc 100644 --- a/packages/rocketchat-lib/server/models/_BaseCache.js +++ b/packages/rocketchat-lib/server/models/_BaseCache.js @@ -230,18 +230,24 @@ class ModelsBaseCache extends EventEmitter { localRecord[field] = []; } + if (typeof link.where === 'function' && link.where(localRecord, record) === false) { + continue; + } + + let mutableRecord = record; + if (typeof link.transform === 'function') { - record = link.transform(localRecord, record); + mutableRecord = link.transform(localRecord, mutableRecord); } if (multi === true) { - localRecord[field].push(record); + localRecord[field].push(mutableRecord); } else { - localRecord[field] = record; + localRecord[field] = mutableRecord; } - this.emit(`join:${ field }:inserted`, localRecord, record); - this.emit(`join:${ field }:changed`, 'inserted', localRecord, record); + this.emit(`join:${ field }:inserted`, localRecord, mutableRecord); + this.emit(`join:${ field }:changed`, 'inserted', localRecord, mutableRecord); } } @@ -255,6 +261,10 @@ class ModelsBaseCache extends EventEmitter { for (let i = 0; i < records.length; i++) { let record = records[i]; + if (typeof link.where === 'function' && link.where(localRecord, record) === false) { + continue; + } + if (typeof link.transform === 'function') { record = link.transform(localRecord, record); } @@ -753,6 +763,7 @@ class ModelsBaseCache extends EventEmitter { console.error('Cache.updateDiffById: No record', this.collectionName, id, diff); return; } + this.removeFromAllIndexes(record); const updatedFields = _.without(Object.keys(diff), ...this.ignoreUpdatedFields); @@ -767,6 +778,7 @@ class ModelsBaseCache extends EventEmitter { } this.collection.update(record); + this.addToAllIndexes(record); if (updatedFields.length > 0) { this.emit('updated', record, diff); @@ -782,6 +794,8 @@ class ModelsBaseCache extends EventEmitter { } } + this.removeFromAllIndexes(record); + const topLevelFields = Object.keys(update).map(field => field.split('.')[0]); const updatedFields = _.without(topLevelFields, ...this.ignoreUpdatedFields); @@ -904,6 +918,7 @@ class ModelsBaseCache extends EventEmitter { } this.collection.update(record); + this.addToAllIndexes(record); if (updatedFields.length > 0) { this.emit('updated', record, record); diff --git a/packages/rocketchat-lib/server/startup/cache/CacheLoad.js b/packages/rocketchat-lib/server/startup/cache/CacheLoad.js index ac56be59900..8f555fc454c 100644 --- a/packages/rocketchat-lib/server/startup/cache/CacheLoad.js +++ b/packages/rocketchat-lib/server/startup/cache/CacheLoad.js @@ -32,6 +32,28 @@ RocketChat.models.Subscriptions.cache.hasOne('Users', { } }); +RocketChat.models.Subscriptions.cache.hasOne('Users', { + field: 'fname', + link: { + local: 'name', + remote: 'username', + where(subscription/*, user*/) { + return subscription.t === 'd'; + }, + transform(subscription, user) { + if (user == null || subscription == null) { + return undefined; + } + // Prevent client cache for old subscriptions with new names + // Cuz when a user change his name, the subscription's _updateAt + // will not change + if (subscription._updatedAt < user._updatedAt) { + subscription._updatedAt = user._updatedAt; + } + return user.name; + } + } +}); RocketChat.models.Users.cache.load(); RocketChat.models.Rooms.cache.load(); diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index 87143f62c4b..f222176bb38 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -8,6 +8,18 @@ RocketChat.settings.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), { // if you add a node to the i18n.json with the same setting name but with `_Description` it will automatically work. RocketChat.settings.addGroup('Accounts', function() { + this.add('Accounts_AllowAnonymousRead', false, { + type: 'boolean', + public: true + }); + this.add('Accounts_AllowAnonymousWrite', false, { + type: 'boolean', + public: true, + enableQuery: { + _id: 'Accounts_AllowAnonymousRead', + value: true + } + }); this.add('Accounts_AllowDeleteOwnAccount', false, { type: 'boolean', 'public': true, @@ -58,7 +70,11 @@ RocketChat.settings.addGroup('Accounts', function() { type: 'boolean', 'public': true }); + this.section('Registration', function() { + this.add('Accounts_DefaultUsernamePrefixSuggestion', 'user', { + type: 'string' + }); this.add('Accounts_RequireNameForSignUp', true, { type: 'boolean', 'public': true @@ -141,6 +157,7 @@ RocketChat.settings.addGroup('Accounts', function() { i18nLabel: 'Custom_Fields' }); }); + this.section('Avatar', function() { this.add('Accounts_AvatarResize', true, { type: 'boolean' diff --git a/packages/rocketchat-lib/startup/defaultRoomTypes.js b/packages/rocketchat-lib/startup/defaultRoomTypes.js index 400c31fc692..40fe79c9daf 100644 --- a/packages/rocketchat-lib/startup/defaultRoomTypes.js +++ b/packages/rocketchat-lib/startup/defaultRoomTypes.js @@ -1,5 +1,4 @@ /* globals openRoom */ - RocketChat.roomTypes.add(null, 0, { template: 'starredRooms', icon: 'icon-star' @@ -25,11 +24,11 @@ RocketChat.roomTypes.add('c', 10, { }, roomName(roomData) { - return { name: roomData.name }; + return roomData.name; }, condition() { - return RocketChat.authz.hasAtLeastOnePermission(['view-c-room', 'view-joined-room']); + return RocketChat.authz.hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || RocketChat.settings.get('Accounts_AllowAnonymousRead') === true; }, showJoinLink(roomId) { @@ -64,7 +63,22 @@ RocketChat.roomTypes.add('d', 20, { }, roomName(roomData) { - return ChatSubscription.findOne({ rid: roomData._id }, { fields: { name: 1, fname: 1 } }); + const subscription = ChatSubscription.findOne({ rid: roomData._id }, { fields: { name: 1, fname: 1 } }); + if (!subscription) { + return ''; + } + if (RocketChat.settings.get('UI_Use_Real_Name') && subscription.fname) { + return subscription.fname; + } + + return subscription.name; + }, + + secondaryRoomName(roomData) { + if (RocketChat.settings.get('UI_Use_Real_Name')) { + const subscription = ChatSubscription.findOne({ rid: roomData._id }, { fields: { name: 1 } }); + return subscription && subscription.name; + } }, condition() { @@ -99,7 +113,7 @@ RocketChat.roomTypes.add('p', 30, { }, roomName(roomData) { - return { name: roomData.name }; + return roomData.name; }, condition() { diff --git a/packages/rocketchat-livechat/app/.meteor/packages b/packages/rocketchat-livechat/app/.meteor/packages index 7cd0da14163..e777a77fc02 100644 --- a/packages/rocketchat-livechat/app/.meteor/packages +++ b/packages/rocketchat-livechat/app/.meteor/packages @@ -5,19 +5,19 @@ # but you can also edit it by hand. meteor@1.6.1 -webapp@1.3.13 +webapp@1.3.15 logging@1.1.17 -tracker@1.1.2 +tracker@1.1.3 deps@1.0.12 session@1.1.7 ddp@1.2.5 livedata@1.0.18 -mongo@1.1.15 +mongo@1.1.17 blaze ui spacebars templating -check@1.2.4 +check@1.2.5 underscore@1.0.10 jquery@1.11.10 random@1.0.10 @@ -32,11 +32,11 @@ underscorestring:underscore.string momentjs:moment mizzao:timesync reactive-var@1.0.11 -accounts-password@1.3.4 +accounts-password@1.3.6 tap:i18n smoral:sweetalert -ecmascript@0.6.3 +ecmascript@0.7.3 es5-shim@4.6.15 -standard-minifier-css@1.3.3 -standard-minifier-js@1.2.2 -shell-server@0.2.2 +standard-minifier-css@1.3.4 +standard-minifier-js@2.0.0 +shell-server@0.2.3 diff --git a/packages/rocketchat-livechat/app/.meteor/release b/packages/rocketchat-livechat/app/.meteor/release index e6940fd82f2..fb6f3bc15e2 100644 --- a/packages/rocketchat-livechat/app/.meteor/release +++ b/packages/rocketchat-livechat/app/.meteor/release @@ -1 +1 @@ -METEOR@1.4.3.1 +METEOR@1.4.4.2 diff --git a/packages/rocketchat-livechat/app/.meteor/versions b/packages/rocketchat-livechat/app/.meteor/versions index e69abb2200b..5d13dcd9ea3 100644 --- a/packages/rocketchat-livechat/app/.meteor/versions +++ b/packages/rocketchat-livechat/app/.meteor/versions @@ -1,36 +1,36 @@ -accounts-base@1.2.14 -accounts-password@1.3.4 +accounts-base@1.2.17 +accounts-password@1.3.6 aldeed:simple-schema@1.5.3 allow-deny@1.0.5 -babel-compiler@6.14.1 +babel-compiler@6.18.2 babel-runtime@1.0.1 base64@1.0.10 binary-heap@1.0.10 -blaze@2.3.0 +blaze@2.3.2 blaze-tools@1.0.10 boilerplate-generator@1.0.11 caching-compiler@1.1.9 -caching-html-compiler@1.1.0 +caching-html-compiler@1.1.2 callback-hook@1.0.10 cfs:http-methods@0.0.32 check@1.2.5 coffeescript@1.12.3_1 ddp@1.2.5 -ddp-client@1.3.3 +ddp-client@1.3.4 ddp-common@1.2.8 -ddp-rate-limiter@1.0.6 -ddp-server@1.3.13 +ddp-rate-limiter@1.0.7 +ddp-server@1.3.14 deps@1.0.12 diff-sequence@1.0.7 -ecmascript@0.6.3 +ecmascript@0.7.3 ecmascript-runtime@0.3.15 ejson@1.0.13 -email@1.1.18 +email@1.2.1 es5-shim@4.6.15 geojson-utils@1.0.10 html-tools@1.0.11 htmljs@1.0.11 -http@1.2.11 +http@1.2.12 id-map@1.0.9 jquery@1.11.10 kadira:blaze-layout@2.3.0 @@ -44,22 +44,22 @@ mdg:validation-error@0.5.1 meteor@1.6.1 meteorspark:util@0.2.0 minifier-css@1.2.16 -minifier-js@1.2.18 -minimongo@1.0.20 -mizzao:timesync@0.4.0 -modules@0.7.9 -modules-runtime@0.7.9 -momentjs:moment@2.17.1 -mongo@1.1.15 +minifier-js@2.0.0 +minimongo@1.0.23 +mizzao:timesync@0.5.0 +modules@0.8.2 +modules-runtime@0.7.10 +momentjs:moment@2.18.1 +mongo@1.1.17 mongo-id@1.0.6 npm-bcrypt@0.9.2 -npm-mongo@2.2.16_1 -observe-sequence@1.0.15 +npm-mongo@2.2.24 +observe-sequence@1.0.16 ordered-dict@1.0.9 promise@0.8.8 raix:eventemitter@0.1.3 random@1.0.10 -rate-limit@1.0.6 +rate-limit@1.0.8 reactive-dict@1.1.8 reactive-var@1.0.11 retry@1.0.9 @@ -68,22 +68,22 @@ routepolicy@1.0.12 service-configuration@1.0.11 session@1.1.7 sha@1.0.9 -shell-server@0.2.2 +shell-server@0.2.3 smoral:sweetalert@1.1.1 -spacebars@1.0.13 -spacebars-compiler@1.1.0 +spacebars@1.0.15 +spacebars-compiler@1.1.2 srp@1.0.10 standard-minifier-css@1.3.4 -standard-minifier-js@1.2.3 +standard-minifier-js@2.0.0 tap:i18n@1.8.2 -templating@1.3.0 -templating-compiler@1.3.0 -templating-runtime@1.3.0 -templating-tools@1.1.0 -tracker@1.1.2 -ui@1.0.12 +templating@1.3.2 +templating-compiler@1.3.2 +templating-runtime@1.3.2 +templating-tools@1.1.2 +tracker@1.1.3 +ui@1.0.13 underscore@1.0.10 underscorestring:underscore.string@3.3.4 url@1.1.0 -webapp@1.3.13 +webapp@1.3.15 webapp-hashing@1.0.9 diff --git a/packages/rocketchat-livechat/app/client/stylesheets/main.less b/packages/rocketchat-livechat/app/client/stylesheets/main.less index a92b5a0a4e0..afad4f33414 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/main.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/main.less @@ -1,5 +1,4 @@ @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"; @@ -258,6 +257,12 @@ input:focus { border-radius: 6px; padding: 5px; float: left; + word-wrap: break-word; + word-break: break-word; + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; &::before { border-style: solid; @@ -285,11 +290,10 @@ input:focus { display: block; position: absolute; top: -30px; - left: 0; + left: calc(~'50% - 70px'); font-size: 10px; font-weight: 600; text-align: center; - .calc(left, ~'50% - 70px'); color: @secondary-font-color; z-index: 1; padding: 0 10px; @@ -471,10 +475,10 @@ input:focus { left: 50%; z-index: 5; transition: transform 0.3s ease-out; - .transform(translateY(-40px)); + transform: translateY(-40px); &.not { - .transform(translateY(100%)); + transform: translateY(100%); } } @@ -486,10 +490,10 @@ input:focus { padding: 5px; z-index: 8; transition: transform 0.2s ease-out; - .transform(translateY(100%)); + transform: translateY(100%); &.show { - .transform(translateY(0)); + transform: translateY(0); } } } @@ -582,12 +586,12 @@ input:focus { position: absolute; z-index: 200; transition: transform 0.15s ease, visibility 0.15s ease, opacity 0.15s ease; - .transform(translateY(30px)); + transform: translateY(30px); opacity: 0; visibility: hidden; &.show { - .transform(translateY(0px)); + transform: translateY(0); opacity: 1; display: block; visibility: visible; @@ -815,13 +819,13 @@ input:focus { text-align: center; opacity: 0; visibility: hidden; - .transform(translateY(50px)); + transform: translateY(50px); transition: opacity 0.175s ease-out, transform 0.175s ease-out, visibility 0.175s ease-out; &.visible { opacity: 1; visibility: visible; - .transform(translateY(0px)); + transform: translateY(0); } .end-call { 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 8aa3df96f1d..04261b85969 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/utils/_keyframes.import.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/utils/_keyframes.import.less @@ -117,13 +117,13 @@ 1% { opacity: 0; visibility: visible; - .transform(translateY(-150px)); + transform: translateY(-150px); } 100% { opacity: 1; visibility: visible; - .transform(translateY(0)); + transform: translateY(0); } } @@ -155,7 +155,7 @@ 99% { opacity: 0; visibility: visible; - .transform(translateY(150px)); + transform: translateY(150px); } 100% { diff --git a/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less b/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less deleted file mode 100644 index 0979dd5275e..00000000000 --- a/packages/rocketchat-livechat/app/client/stylesheets/utils/_lesshat.import.less +++ /dev/null @@ -1,85 +0,0 @@ -// 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-livechat/app/i18n/bg.i18n.json b/packages/rocketchat-livechat/app/i18n/bg.i18n.json index 060a3ee2788..2a6ec769e46 100644 --- a/packages/rocketchat-livechat/app/i18n/bg.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/bg.i18n.json @@ -1,6 +1,7 @@ { "Are_you_sure_do_you_want_end_this_chat": "Сигурен ли си че искаш да прекратиш този чат?", "Cancel": "Отказ", + "Change": "Промени", "Chat_ended": "Край на чата", "Close_menu": "Затваряне на менюто", "Conversation_finished": "Разговорът завърши", diff --git a/packages/rocketchat-livechat/app/i18n/de.i18n.json b/packages/rocketchat-livechat/app/i18n/de.i18n.json index 66986eb71b6..2474b11a94a 100644 --- a/packages/rocketchat-livechat/app/i18n/de.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/de.i18n.json @@ -3,6 +3,7 @@ "Appearance": "Erscheinungsbild", "Are_you_sure_do_you_want_end_this_chat": "Sind Sie sich sicher diesen Chat zu beenden?", "Cancel": "Abbrechen", + "Change": "Ändern", "Chat_ended": "Chat beendet!", "Close_menu": "Menü schließen", "Conversation_finished": "Gespräch beendet", diff --git a/packages/rocketchat-livechat/app/i18n/it.i18n.json b/packages/rocketchat-livechat/app/i18n/it.i18n.json index 330e89f9a3d..f333178ee3e 100644 --- a/packages/rocketchat-livechat/app/i18n/it.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/it.i18n.json @@ -2,8 +2,11 @@ "Additional_Feedback": "Feedback aggiuntivi", "Appearance": "Aspetto", "Are_you_sure_do_you_want_end_this_chat": "Sei sicuro di voler chiudere questa chat?", + "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "Sei sicuro di voler terminare la conversazione e cambiare dipartimento?", "Cancel": "Cancella", + "Change": "Cambia", "Chat_ended": "Chat chiusa!", + "Choose_a_new_department": "Seleziona un nuovo dipartimento", "Close_menu": "Chiudi menu", "Conversation_finished": "conversazione finito", "End_chat": "Chiusura chat", @@ -16,6 +19,7 @@ "No": "No", "Options": "Opzioni", "Please_answer_survey": "Hai un momento per rispondere a un rapido sondaggio su questa chat?", + "Please_choose_a_department": "Prego scegliere un dipartimento", "Please_fill_name_and_email": "Compila nome e indirizzo e-mail", "Powered_by": "Powered by", "Request_video_chat": "Richiedi video chat", diff --git a/packages/rocketchat-livechat/app/i18n/ko.i18n.json b/packages/rocketchat-livechat/app/i18n/ko.i18n.json index ba628fdff09..569097d1f04 100644 --- a/packages/rocketchat-livechat/app/i18n/ko.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/ko.i18n.json @@ -2,35 +2,44 @@ "Additional_Feedback": "추가 의견", "Appearance": "모양", "Are_you_sure_do_you_want_end_this_chat": "이 채팅을 정말 끝내시겠습니까?", + "Are_you_sure_do_you_want_end_this_chat_and_switch_department": "정말 이 채팅을 종료하고 부서를 변경하겠습니까?", "Cancel": "취소", - "Chat_ended": "채팅 끝남!", + "Change": "변경", + "Chat_ended": "채팅 종료", + "Choose_a_new_department": "새로운 부서를 선택하세요.", "Close_menu": "메뉴 닫기", "Conversation_finished": "대화 종료됨", "End_chat": "채팅 끝남", - "How_friendly_was_the_chat_agent": "채팅 에이전트는 어떻게 친했다?", - "How_knowledgeable_was_the_chat_agent": "채팅 에이전트는 어떻게 지식인가?", - "How_responsive_was_the_chat_agent": "채팅 에이전트는 어떻게 반응이었다?", - "How_satisfied_were_you_with_this_chat": "이 채팅에 당신을 어떻게 만족?", + "How_friendly_was_the_chat_agent": "채팅 담당자는 얼마나 친절했나요?", + "How_knowledgeable_was_the_chat_agent": "채팅 에이전트의 기반 지식이 풍부했나요?", + "How_responsive_was_the_chat_agent": "채팅 담당자는 얼마나 빠르게 응답했나요?", + "How_satisfied_were_you_with_this_chat": "채팅에 얼마나 만족했나요?", "Installation": "설치", "New_messages": "새 메시지", + "No": "아니오", "Options": "옵션", "Please_answer_survey": "이 채팅에 대한 간단한 설문 조사에 응답하기 위해 잠시 시간을내어 주시기 바랍니다", + "Please_choose_a_department": "부서를 선택하세요.", "Please_fill_name_and_email": "이름과 이메일을 입력하세요", "Powered_by": "에 의해 구동", "Request_video_chat": "비디오채팅 요청", "Select_a_department": "부서를 선택해주세요", + "Switch_department": "부서 변경", + "Department_switched": "부서가 변경되었습니다.", "Send": "전송", "Skip": "건너뛰기", "Start_Chat": "채팅 시작", "Survey": "설문", - "Survey_instructions": "당신이 완전히 만족하는 의미를 완전히 만족하지 의미에 대한 만족도에 따라 1 각 질문에 평가 5.", + "Survey_instructions": "당신의 만족도를 평가해 주세요. 매우불만은 1, 매우 만족은 5 입니다.", "Thank_you_for_your_feedback": "의견을 보내 주셔서 감사합니다", "Thanks_We_ll_get_back_to_you_soon": "감사합니다! 곧 다시 연락드리겠습니다", + "transcript_sent": "채팅 내용을 발송했습니다.", "Type_your_email": "이메일을 입력", "Type_your_message": "메시지를 입력", "Type_your_name": "당신의 이름을 입력", "User_joined": "사용자 가입", "User_left": "사용자 왼쪽", "We_are_offline_Sorry_for_the_inconvenience": "우리는 오프라인 상태입니다. 불편을 드려 죄송합니다.", + "Yes": "예", "You_must_complete_all_fields": "모든 필드를 작성해야합니다" } \ No newline at end of file diff --git a/packages/rocketchat-livechat/app/package.json b/packages/rocketchat-livechat/app/package.json index de148775e58..e0e9add70d3 100644 --- a/packages/rocketchat-livechat/app/package.json +++ b/packages/rocketchat-livechat/app/package.json @@ -20,11 +20,11 @@ "email": "support@rocket.chat" }, "dependencies": { - "autolinker": "^1.4.2", - "jquery": "^3.1.1", + "autolinker": "^1.4.3", + "jquery": "^3.2.1", "babel-runtime": "^6.23.0", "bcrypt": "^1.0.2", - "moment": "^2.17.1", + "moment": "^2.18.1", "toastr": "^2.1.2" } } diff --git a/packages/rocketchat-livechat/client/stylesheets/lesshat.less b/packages/rocketchat-livechat/client/stylesheets/lesshat.less deleted file mode 100644 index 7c9f0ae86a2..00000000000 --- a/packages/rocketchat-livechat/client/stylesheets/lesshat.less +++ /dev/null @@ -1,17 +0,0 @@ -// 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 7e268decb3f..3d928e9b278 100644 --- a/packages/rocketchat-livechat/client/stylesheets/livechat.less +++ b/packages/rocketchat-livechat/client/stylesheets/livechat.less @@ -1,5 +1,3 @@ -@import "lesshat.less"; - @header-min-height: 30px; @footer-min-height: 55px; @link-font-color: #008ce3; @@ -237,11 +235,10 @@ display: block; position: absolute; top: -30px; - left: 0; + left: calc(~'50% - 70px'); font-size: 10px; font-weight: 600; text-align: center; - .calc(left, ~'50% - 70px'); color: @secondary-font-color; z-index: 10; padding: 0 10px; @@ -424,10 +421,10 @@ left: 50%; z-index: 5; transition: transform 0.3s ease-out; - .transform(translateY(-40px)); + transform: translateY(-40px); &.not { - .transform(translateY(100%)); + transform: translateY(100%); } } @@ -439,10 +436,10 @@ padding: 5px; z-index: 8; transition: transform 0.2s ease-out; - .transform(translateY(100%)); + transform: translateY(100%); &.show { - .transform(translateY(0)); + transform: translateY(0); } } } diff --git a/packages/rocketchat-logger/server.js b/packages/rocketchat-logger/server.js index 564751f6f7e..0b64612c4a4 100644 --- a/packages/rocketchat-logger/server.js +++ b/packages/rocketchat-logger/server.js @@ -78,7 +78,7 @@ const defaultTypes = { class _Logger { constructor(name, config = {}) { - self = this; + const self = this; this.name = name; this.config = Object.assign({}, config); @@ -141,12 +141,12 @@ class _Logger { _.each(this.config.sections, (name, section) => { this[section] = {}; _.each(defaultTypes, (typeConfig, type) => { - self[section][type] = () => self[type].apply({__section: name}, arguments); - self[section][`${ type }_box`] = () => self[`${ type }_box`].apply({__section: name}, arguments); + self[section][type] = (...args) => this[type].apply({__section: name}, args); + self[section][`${ type }_box`] = (...args) => this[`${ type }_box`].apply({__section: name}, args); }); _.each(this.config.methods, (typeConfig, method) => { - self[section][method] = () => self[method].apply({__section: name}, arguments); - self[section][`${ method }_box`] = () => self[`${ method }_box`].apply({__section: name}, arguments); + self[section][method] = (...args) => self[method].apply({__section: name}, args); + self[section][`${ method }_box`] = (...args) => self[`${ method }_box`].apply({__section: name}, args); }); }); } @@ -323,29 +323,32 @@ SystemLogger = new Logger('System', { // eslint-disable-line no-undef }); -class StdOut extends EventEmitter { +const StdOut = new class extends EventEmitter { constructor() { super(); const write = process.stdout.write; this.queue = []; - process.stdout.write = (string) => { - write.apply(process.stdout, arguments); + process.stdout.write = (...args) => { + write.apply(process.stdout, args); const date = new Date; - string = processString(string, date); + const string = processString(args[0], date); const item = { id: Random.id(), string, ts: date }; this.queue.push(item); - const limit = RocketChat.settings.get('Log_View_Limit'); - if (limit && this.queue.length > limit) { - this.queue.shift(); + + if (typeof RocketChat !== 'undefined') { + const limit = RocketChat.settings.get('Log_View_Limit'); + if (limit && this.queue.length > limit) { + this.queue.shift(); + } } this.emit('write', string, item); }; } -} +}; Meteor.publish('stdout', function() { diff --git a/packages/rocketchat-mailer/client/router.coffee b/packages/rocketchat-mailer/client/router.coffee deleted file mode 100644 index 0314ec8fd1e..00000000000 --- a/packages/rocketchat-mailer/client/router.coffee +++ /dev/null @@ -1,10 +0,0 @@ -FlowRouter.route '/mailer', - name: 'mailer' - action: -> - BlazeLayout.render 'main', {center: 'mailer'} - -FlowRouter.route '/mailer/unsubscribe/:_id/:createdAt', - name: 'mailer-unsubscribe' - action: (params) -> - Meteor.call 'Mailer:unsubscribe', params._id, params.createdAt - BlazeLayout.render 'mailerUnsubscribe' diff --git a/packages/rocketchat-mailer/client/router.js b/packages/rocketchat-mailer/client/router.js new file mode 100644 index 00000000000..e2a9e672803 --- /dev/null +++ b/packages/rocketchat-mailer/client/router.js @@ -0,0 +1,16 @@ +FlowRouter.route('/mailer', { + name: 'mailer', + action() { + return BlazeLayout.render('main', { + center: 'mailer' + }); + } +}); + +FlowRouter.route('/mailer/unsubscribe/:_id/:createdAt', { + name: 'mailer-unsubscribe', + action(params) { + Meteor.call('Mailer:unsubscribe', params._id, params.createdAt); + return BlazeLayout.render('mailerUnsubscribe'); + } +}); diff --git a/packages/rocketchat-mailer/client/startup.coffee b/packages/rocketchat-mailer/client/startup.coffee deleted file mode 100644 index 9b4dd084726..00000000000 --- a/packages/rocketchat-mailer/client/startup.coffee +++ /dev/null @@ -1,5 +0,0 @@ -RocketChat.AdminBox.addOption - href: 'mailer' - i18nLabel: 'Mailer' - permissionGranted: -> - return RocketChat.authz.hasAllPermission('access-mailer') diff --git a/packages/rocketchat-mailer/client/startup.js b/packages/rocketchat-mailer/client/startup.js new file mode 100644 index 00000000000..728ebddf8c1 --- /dev/null +++ b/packages/rocketchat-mailer/client/startup.js @@ -0,0 +1,7 @@ +RocketChat.AdminBox.addOption({ + href: 'mailer', + i18nLabel: 'Mailer', + permissionGranted() { + return RocketChat.authz.hasAllPermission('access-mailer'); + } +}); diff --git a/packages/rocketchat-mailer/client/views/mailer.coffee b/packages/rocketchat-mailer/client/views/mailer.coffee deleted file mode 100644 index 9f484b5a5e3..00000000000 --- a/packages/rocketchat-mailer/client/views/mailer.coffee +++ /dev/null @@ -1,25 +0,0 @@ -import toastr from 'toastr' -Template.mailer.helpers - fromEmail: -> - return RocketChat.settings.get 'From_Email' - -Template.mailer.events - 'click .send': (e, t) -> - e.preventDefault() - from = $(t.find('[name=from]')).val() - subject = $(t.find('[name=subject]')).val() - body = $(t.find('[name=body]')).val() - dryrun = $(t.find('[name=dryrun]:checked')).val() - query = $(t.find('[name=query]')).val() - - unless from - toastr.error TAPi18n.__('error-invalid-from-address') - return - - if body.indexOf('[unsubscribe]') is -1 - toastr.error TAPi18n.__('error-missing-unsubscribe-link') - return - - Meteor.call 'Mailer.sendMail', from, subject, body, dryrun, query, (err) -> - return handleError(err) if err - toastr.success TAPi18n.__('The_emails_are_being_sent') diff --git a/packages/rocketchat-mailer/client/views/mailer.js b/packages/rocketchat-mailer/client/views/mailer.js new file mode 100644 index 00000000000..731df5aaa3e --- /dev/null +++ b/packages/rocketchat-mailer/client/views/mailer.js @@ -0,0 +1,31 @@ +import toastr from 'toastr'; +Template.mailer.helpers({ + fromEmail() { + return RocketChat.settings.get('From_Email'); + } +}); + +Template.mailer.events({ + 'click .send'(e, t) { + e.preventDefault(); + const from = $(t.find('[name=from]')).val(); + const subject = $(t.find('[name=subject]')).val(); + const body = $(t.find('[name=body]')).val(); + const dryrun = $(t.find('[name=dryrun]:checked')).val(); + const query = $(t.find('[name=query]')).val(); + if (!from) { + toastr.error(TAPi18n.__('error-invalid-from-address')); + return; + } + if (body.indexOf('[unsubscribe]') === -1) { + toastr.error(TAPi18n.__('error-missing-unsubscribe-link')); + return; + } + return Meteor.call('Mailer.sendMail', from, subject, body, dryrun, query, function(err) { + if (err) { + return handleError(err); + } + return toastr.success(TAPi18n.__('The_emails_are_being_sent')); + }); + } +}); diff --git a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.coffee b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.coffee deleted file mode 100644 index d8a7a1840e0..00000000000 --- a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.coffee +++ /dev/null @@ -1,2 +0,0 @@ -Template.mailerUnsubscribe.onRendered -> - $('#initial-page-loading').remove() diff --git a/packages/rocketchat-mailer/client/views/mailerUnsubscribe.js b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.js new file mode 100644 index 00000000000..8012c4deb51 --- /dev/null +++ b/packages/rocketchat-mailer/client/views/mailerUnsubscribe.js @@ -0,0 +1,3 @@ +Template.mailerUnsubscribe.onRendered(function() { + return $('#initial-page-loading').remove(); +}); diff --git a/packages/rocketchat-mailer/lib/Mailer.coffee b/packages/rocketchat-mailer/lib/Mailer.coffee deleted file mode 100644 index 8158d6d741d..00000000000 --- a/packages/rocketchat-mailer/lib/Mailer.coffee +++ /dev/null @@ -1 +0,0 @@ -Mailer = {} diff --git a/packages/rocketchat-mailer/lib/Mailer.js b/packages/rocketchat-mailer/lib/Mailer.js new file mode 100644 index 00000000000..e7e30cfebe4 --- /dev/null +++ b/packages/rocketchat-mailer/lib/Mailer.js @@ -0,0 +1 @@ +Mailer = {};//eslint-disable-line diff --git a/packages/rocketchat-mailer/package.js b/packages/rocketchat-mailer/package.js index 30da5c981e5..ffe0a7e317d 100644 --- a/packages/rocketchat-mailer/package.js +++ b/packages/rocketchat-mailer/package.js @@ -7,7 +7,6 @@ Package.describe({ Package.onUse(function(api) { api.use([ 'ecmascript', - 'coffeescript', 'ddp-rate-limiter', 'kadira:flow-router', 'rocketchat:lib', @@ -16,24 +15,24 @@ Package.onUse(function(api) { api.use('templating', 'client'); - api.addFiles('lib/Mailer.coffee'); + api.addFiles('lib/Mailer.js'); api.addFiles([ - 'client/startup.coffee', - 'client/router.coffee', + 'client/startup.js', + 'client/router.js', 'client/views/mailer.html', - 'client/views/mailer.coffee', + 'client/views/mailer.js', 'client/views/mailerUnsubscribe.html', - 'client/views/mailerUnsubscribe.coffee' + 'client/views/mailerUnsubscribe.js' ], 'client'); api.addFiles([ - 'server/startup.coffee', - 'server/models/Users.coffee', - 'server/functions/sendMail.coffee', - 'server/functions/unsubscribe.coffee', - 'server/methods/sendMail.coffee', - 'server/methods/unsubscribe.coffee' + 'server/startup.js', + 'server/models/Users.js', + 'server/functions/sendMail.js', + 'server/functions/unsubscribe.js', + 'server/methods/sendMail.js', + 'server/methods/unsubscribe.js' ], 'server'); api.export('Mailer'); diff --git a/packages/rocketchat-mailer/server/functions/sendMail.coffee b/packages/rocketchat-mailer/server/functions/sendMail.coffee deleted file mode 100644 index d6fa86beb87..00000000000 --- a/packages/rocketchat-mailer/server/functions/sendMail.coffee +++ /dev/null @@ -1,63 +0,0 @@ -Mailer.sendMail = (from, subject, body, dryrun, query) -> - - rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/ - # rfcMailPattern = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - - unless rfcMailPatternWithName.test from - throw new Meteor.Error 'error-invalid-from-address', 'Invalid from address', { function: 'Mailer.sendMail' } - - if body.indexOf('[unsubscribe]') is -1 - throw new Meteor.Error 'error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', { function: 'Mailer.sendMail' } - - header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); - footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); - - userQuery = { "mailer.unsubscribed": { $exists: 0 } } - if query - userQuery = { $and: [ userQuery, EJSON.parse(query) ] } - - if dryrun - Meteor.users.find({ "emails.address": from }).forEach (user) -> - # Meteor.users.find({ "username": /\.rocket\.team/ }).forEach (user) -> - email = user.emails?[0]?.address - - html = RocketChat.placeholders.replace(body, { - unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, createdAt: user.createdAt.getTime() })), - name: user.name, - email: email - }); - - email = "#{user.name} <#{email}>" - - if rfcMailPatternWithName.test email - Meteor.defer -> - Email.send - to: email - from: from - subject: subject - html: header + html + footer - - console.log 'Sending email to ' + email - - else - Meteor.users.find({ "mailer.unsubscribed": { $exists: 0 } }).forEach (user) -> - # Meteor.users.find({ "username": /\.rocket\.team/ }).forEach (user) -> - email = user.emails?[0]?.address - - html = RocketChat.placeholders.replace(body, { - unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, createdAt: user.createdAt.getTime() })), - name: user.name, - email: email - }); - - email = "#{user.name} <#{email}>" - - if rfcMailPatternWithName.test email - Meteor.defer -> - Email.send - to: email - from: from - subject: subject - html: header + html + footer - - console.log 'Sending email to ' + email diff --git a/packages/rocketchat-mailer/server/functions/sendMail.js b/packages/rocketchat-mailer/server/functions/sendMail.js new file mode 100644 index 00000000000..b8cd2e9b49b --- /dev/null +++ b/packages/rocketchat-mailer/server/functions/sendMail.js @@ -0,0 +1,80 @@ +/*globals Mailer */ +Mailer.sendMail = function(from, subject, body, dryrun, query) { + + const rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/; + if (!rfcMailPatternWithName.test(from)) { + throw new Meteor.Error('error-invalid-from-address', 'Invalid from address', { + 'function': 'Mailer.sendMail' + }); + } + if (body.indexOf('[unsubscribe]') === -1) { + throw new Meteor.Error('error-missing-unsubscribe-link', 'You must provide the [unsubscribe] link.', { + 'function': 'Mailer.sendMail' + }); + } + const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); + const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); + + let userQuery = { 'mailer.unsubscribed': { $exists: 0 } }; + if (query) { + userQuery = { $and: [ userQuery, EJSON.parse(query) ] }; + } + + if (dryrun) { + return Meteor.users.find({ + 'emails.address': from + }).forEach((user) => { + let email = undefined; + if (user.emails && user.emails[0] && user.emails[0].address) { + email = user.emails[0].address; + } + const html = RocketChat.placeholders.replace(body, { + unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { + _id: user._id, + createdAt: user.createdAt.getTime() + })), + name: user.name, + email + }); + email = `${ user.name } <${ email }>`; + if (rfcMailPatternWithName.test(email)) { + Meteor.defer(function() { + return Email.send({ + to: email, + from, + subject, + html: header + html + footer + }); + }); + return console.log(`Sending email to ${ email }`); + } + }); + } else { + return Meteor.users.find(userQuery).forEach(function(user) { + let email = undefined; + if (user.emails && user.emails[0] && user.emails[0].address) { + email = user.emails[0].address; + } + const html = RocketChat.placeholders.replace(body, { + unsubscribe: Meteor.absoluteUrl(FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { + _id: user._id, + createdAt: user.createdAt.getTime() + })), + name: user.name, + email + }); + email = `${ user.name } <${ email }>`; + if (rfcMailPatternWithName.test(email)) { + Meteor.defer(function() { + return Email.send({ + to: email, + from, + subject, + html: header + html + footer + }); + }); + return console.log(`Sending email to ${ email }`); + } + }); + } +}; diff --git a/packages/rocketchat-mailer/server/functions/unsubscribe.coffee b/packages/rocketchat-mailer/server/functions/unsubscribe.coffee deleted file mode 100644 index 955a0ec95fb..00000000000 --- a/packages/rocketchat-mailer/server/functions/unsubscribe.coffee +++ /dev/null @@ -1,4 +0,0 @@ -Mailer.unsubscribe = (_id, createdAt) -> - if _id and createdAt - return RocketChat.models.Users.RocketMailUnsubscribe(_id, createdAt) == 1 - return false diff --git a/packages/rocketchat-mailer/server/functions/unsubscribe.js b/packages/rocketchat-mailer/server/functions/unsubscribe.js new file mode 100644 index 00000000000..4e0bbc838ad --- /dev/null +++ b/packages/rocketchat-mailer/server/functions/unsubscribe.js @@ -0,0 +1,7 @@ +/* globals Mailer */ +Mailer.unsubscribe = function(_id, createdAt) { + if (_id && createdAt) { + return RocketChat.models.Users.rocketMailUnsubscribe(_id, createdAt) === 1; + } + return false; +}; diff --git a/packages/rocketchat-mailer/server/methods/sendMail.coffee b/packages/rocketchat-mailer/server/methods/sendMail.coffee deleted file mode 100644 index 17b8946353f..00000000000 --- a/packages/rocketchat-mailer/server/methods/sendMail.coffee +++ /dev/null @@ -1,16 +0,0 @@ -Meteor.methods - 'Mailer.sendMail': (from, subject, body, dryrun, query) -> - if not Meteor.userId() - throw new Meteor.Error 'error-invalid-user', "Invalid user", { method: 'Mailer.sendMail' } - - unless RocketChat.authz.hasRole( Meteor.userId(), 'admin') is true - throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'Mailer.sendMail' } - - return Mailer.sendMail from, subject, body, dryrun, query - -# Limit setting username once per minute -# DDPRateLimiter.addRule -# type: 'method' -# name: 'Mailer.sendMail' -# connectionId: -> return true -# , 1, 60000 diff --git a/packages/rocketchat-mailer/server/methods/sendMail.js b/packages/rocketchat-mailer/server/methods/sendMail.js new file mode 100644 index 00000000000..c45b394b01b --- /dev/null +++ b/packages/rocketchat-mailer/server/methods/sendMail.js @@ -0,0 +1,25 @@ +/*globals Mailer */ +Meteor.methods({ + 'Mailer.sendMail'(from, subject, body, dryrun, query) { + const userId = Meteor.userId(); + if (!userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'Mailer.sendMail' + }); + } + if (RocketChat.authz.hasRole(userId, 'admin') !== true) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'Mailer.sendMail' + }); + } + return Mailer.sendMail(from, subject, body, dryrun, query); + } +}); + + +//Limit setting username once per minute +//DDPRateLimiter.addRule +// type: 'method' +// name: 'Mailer.sendMail' +// connectionId: -> return true +// , 1, 60000 diff --git a/packages/rocketchat-mailer/server/methods/unsubscribe.coffee b/packages/rocketchat-mailer/server/methods/unsubscribe.coffee deleted file mode 100644 index 88e5d7a1494..00000000000 --- a/packages/rocketchat-mailer/server/methods/unsubscribe.coffee +++ /dev/null @@ -1,10 +0,0 @@ -Meteor.methods - 'Mailer:unsubscribe': (_id, createdAt) -> - return Mailer.unsubscribe _id, createdAt - -# Limit setting username once per minute -DDPRateLimiter.addRule - type: 'method' - name: 'Mailer:unsubscribe' - connectionId: -> return true -, 1, 60000 diff --git a/packages/rocketchat-mailer/server/methods/unsubscribe.js b/packages/rocketchat-mailer/server/methods/unsubscribe.js new file mode 100644 index 00000000000..914c80e7b14 --- /dev/null +++ b/packages/rocketchat-mailer/server/methods/unsubscribe.js @@ -0,0 +1,14 @@ +/*globals Mailer */ +Meteor.methods({ + 'Mailer:unsubscribe'(_id, createdAt) { + return Mailer.unsubscribe(_id, createdAt); + } +}); + +DDPRateLimiter.addRule({ + type: 'method', + name: 'Mailer:unsubscribe', + connectionId() { + return true; + } +}, 1, 60000); diff --git a/packages/rocketchat-mailer/server/models/Users.coffee b/packages/rocketchat-mailer/server/models/Users.coffee deleted file mode 100644 index 7622b890d48..00000000000 --- a/packages/rocketchat-mailer/server/models/Users.coffee +++ /dev/null @@ -1,17 +0,0 @@ -# Extends model Users - -RocketChat.models.Users.RocketMailUnsubscribe = (_id, createdAt) -> - - query = - _id: _id - createdAt: new Date(parseInt createdAt) - - update = - $set: - "mailer.unsubscribed": true - - affectedRows = @update query, update - - console.log '[Mailer:Unsubscribe]', _id, createdAt, new Date(parseInt createdAt), affectedRows - - return affectedRows diff --git a/packages/rocketchat-mailer/server/models/Users.js b/packages/rocketchat-mailer/server/models/Users.js new file mode 100644 index 00000000000..9649db1972f --- /dev/null +++ b/packages/rocketchat-mailer/server/models/Users.js @@ -0,0 +1,14 @@ +RocketChat.models.Users.rocketMailUnsubscribe = function(_id, createdAt) { + const query = { + _id, + createdAt: new Date(parseInt(createdAt)) + }; + const update = { + $set: { + 'mailer.unsubscribed': true + } + }; + const affectedRows = this.update(query, update); + console.log('[Mailer:Unsubscribe]', _id, createdAt, new Date(parseInt(createdAt)), affectedRows); + return affectedRows; +}; diff --git a/packages/rocketchat-mailer/server/startup.coffee b/packages/rocketchat-mailer/server/startup.coffee deleted file mode 100644 index acf55f73c49..00000000000 --- a/packages/rocketchat-mailer/server/startup.coffee +++ /dev/null @@ -1,2 +0,0 @@ -Meteor.startup -> - RocketChat.models.Permissions.upsert( 'access-mailer', { $setOnInsert : { _id: 'access-mailer', roles : ['admin'] } }) diff --git a/packages/rocketchat-mailer/server/startup.js b/packages/rocketchat-mailer/server/startup.js new file mode 100644 index 00000000000..e1ef976a18f --- /dev/null +++ b/packages/rocketchat-mailer/server/startup.js @@ -0,0 +1,8 @@ +Meteor.startup(function() { + return RocketChat.models.Permissions.upsert('access-mailer', { + $setOnInsert: { + _id: 'access-mailer', + roles: ['admin'] + } + }); +}); diff --git a/packages/rocketchat-markdown/markdown.coffee b/packages/rocketchat-markdown/markdown.coffee deleted file mode 100644 index f4911d3f528..00000000000 --- a/packages/rocketchat-markdown/markdown.coffee +++ /dev/null @@ -1,86 +0,0 @@ -### -# Markdown is a named function that will parse markdown syntax -# @param {Object} message - The message object -### - -class Markdown - constructor: (message) -> - msg = message - - if not _.isString message - if _.trim message?.html - msg = message.html - else - return message - - schemes = RocketChat.settings.get('Markdown_SupportSchemesForLink').split(',').join('|') - - # Support ![alt text](http://image url) - msg = msg.replace new RegExp("!\\[([^\\]]+)\\]\\(((?:#{schemes}):\\/\\/[^\\)]+)\\)", 'gm'), (match, title, url) -> - target = if url.indexOf(Meteor.absoluteUrl()) is 0 then '' else '_blank' - return '
    ' - - # Support [Text](http://link) - msg = msg.replace new RegExp("\\[([^\\]]+)\\]\\(((?:#{schemes}):\\/\\/[^\\)]+)\\)", 'gm'), (match, title, url) -> - target = if url.indexOf(Meteor.absoluteUrl()) is 0 then '' else '_blank' - return '' + _.escapeHTML(title) + '' - - # Support - msg = msg.replace new RegExp("(?:<|<)((?:#{schemes}):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)", 'gm'), (match, url, title) -> - target = if url.indexOf(Meteor.absoluteUrl()) is 0 then '' else '_blank' - return '' + _.escapeHTML(title) + '' - - if RocketChat.settings.get('Markdown_Headers') - # Support # Text for h1 - msg = msg.replace(/^# (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    ') - - # Support # Text for h2 - msg = msg.replace(/^## (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    ') - - # Support # Text for h3 - msg = msg.replace(/^### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    ') - - # Support # Text for h4 - msg = msg.replace(/^#### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    ') - - # Support *text* to make bold - msg = msg.replace(/(^|>|[ >_~`])\*{1,2}([^\*\r\n]+)\*{1,2}([<_~`]|\B|\b|$)/gm, '$1*$2*$3') - - # Support _text_ to make italics - msg = msg.replace(/(^|>|[ >*~`])\_([^\_\r\n]+)\_([<*~`]|\B|\b|$)/gm, '$1_$2_$3') - - # Support ~text~ to strike through text - msg = msg.replace(/(^|>|[ >_*`])\~{1,2}([^~\r\n]+)\~{1,2}([<_*`]|\B|\b|$)/gm, '$1~$2~$3') - - # Support for block quote - # >>> - # Text - # <<< - msg = msg.replace(/(?:>){3}\n+([\s\S]*?)\n+(?:<){3}/g, '
    >>>$1<<<
    ') - - # Support >Text for quote - msg = msg.replace(/^>(.*)$/gm, '
    >$1
    ') - - # Remove white-space around blockquote (prevent
    ). Because blockquote is block element. - msg = msg.replace(/\s*
    /gm, '
    ') - msg = msg.replace(/<\/blockquote>\s*/gm, '
    ') - - # Remove new-line between blockquotes. - msg = msg.replace(/<\/blockquote>\n
    - return RocketChat.Markdown text diff --git a/packages/rocketchat-markdown/markdown.js b/packages/rocketchat-markdown/markdown.js new file mode 100644 index 00000000000..365a0126dd3 --- /dev/null +++ b/packages/rocketchat-markdown/markdown.js @@ -0,0 +1,93 @@ +/* + * Markdown is a named function that will parse markdown syntax + * @param {Object} message - The message object + */ + +class MarkdownClass { + parse(text) { + return this.parseNotEscaped(_.escapeHTML(text)); + } + + parseNotEscaped(msg) { + const schemes = RocketChat.settings.get('Markdown_SupportSchemesForLink').split(',').join('|'); + + // Support ![alt text](http://image url) + msg = msg.replace(new RegExp(`!\\[([^\\]]+)\\]\\(((?:${ schemes }):\\/\\/[^\\)]+)\\)`, 'gm'), function(match, title, url) { + const target = url.indexOf(Meteor.absoluteUrl()) === 0 ? '' : '_blank'; + return `
    `; + }); + + // Support [Text](http://link) + msg = msg.replace(new RegExp(`\\[([^\\]]+)\\]\\(((?:${ schemes }):\\/\\/[^\\)]+)\\)`, 'gm'), function(match, title, url) { + const target = url.indexOf(Meteor.absoluteUrl()) === 0 ? '' : '_blank'; + return `${ _.escapeHTML(title) }`; + }); + + // Support + msg = msg.replace(new RegExp(`(?:<|<)((?:${ schemes }):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)`, 'gm'), (match, url, title) => { + const target = url.indexOf(Meteor.absoluteUrl()) === 0 ? '' : '_blank'; + return `${ _.escapeHTML(title) }`; + }); + + if (RocketChat.settings.get('Markdown_Headers')) { + // Support # Text for h1 + msg = msg.replace(/^# (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    '); + + // Support # Text for h2 + msg = msg.replace(/^## (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    '); + + // Support # Text for h3 + msg = msg.replace(/^### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    '); + + // Support # Text for h4 + msg = msg.replace(/^#### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm, '

    $1

    '); + } + + // Support *text* to make bold + msg = msg.replace(/(^|>|[ >_~`])\*{1,2}([^\*\r\n]+)\*{1,2}([<_~`]|\B|\b|$)/gm, '$1*$2*$3'); + + // Support _text_ to make italics + msg = msg.replace(/(^|>|[ >*~`])\_([^\_\r\n]+)\_([<*~`]|\B|\b|$)/gm, '$1_$2_$3'); + + // Support ~text~ to strike through text + msg = msg.replace(/(^|>|[ >_*`])\~{1,2}([^~\r\n]+)\~{1,2}([<_*`]|\B|\b|$)/gm, '$1~$2~$3'); + + // Support for block quote + // >>> + // Text + // <<< + msg = msg.replace(/(?:>){3}\n+([\s\S]*?)\n+(?:<){3}/g, '
    >>>$1<<<
    '); + + // Support >Text for quote + msg = msg.replace(/^>(.*)$/gm, '
    >$1
    '); + + // Remove white-space around blockquote (prevent
    ). Because blockquote is block element. + msg = msg.replace(/\s*
    /gm, '
    '); + msg = msg.replace(/<\/blockquote>\s*/gm, '
    '); + + // Remove new-line between blockquotes. + msg = msg.replace(/<\/blockquote>\n
    { + if (_.trim(message != null ? message.html : undefined)) { + message.html = Markdown.parseNotEscaped(message.html); + } + + return message; +}; + +RocketChat.callbacks.add('renderMessage', MarkdownMessage, RocketChat.callbacks.priority.HIGH, 'markdown'); + +if (Meteor.isClient) { + Blaze.registerHelper('RocketChatMarkdown', text => Markdown.parse(text)); +} diff --git a/packages/rocketchat-markdown/markdowncode.coffee b/packages/rocketchat-markdown/markdowncode.coffee deleted file mode 100644 index c16e3fbedae..00000000000 --- a/packages/rocketchat-markdown/markdowncode.coffee +++ /dev/null @@ -1,88 +0,0 @@ -### -# MarkdownCode is a named function that will parse `inline code` and ```codeblock``` syntaxes -# @param {Object} message - The message object -### -import hljs from 'highlight.js'; - -class MarkdownCode - constructor: (message) -> - - if s.trim message.html - message.tokens ?= [] - - MarkdownCode.handle_codeblocks message - MarkdownCode.handle_inlinecode message - - console.log 'Markdown', message if window?.rocketDebug - - return message - - @handle_inlinecode: (message) -> - # Support `text` - message.html = message.html.replace /(^|>|[ >_*~])\`([^`\r\n]+)\`([<_*~]|\B|\b|$)/gm, (match, p1, p2, p3, offset, text) -> - token = "=&=#{Random.id()}=&=" - - message.tokens.push - token: token - text: "#{p1}`#{p2}`#{p3}" - noHtml: match - - return token - - - @handle_codeblocks: (message) -> - # 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(/(^.*)(```(?:[a-zA-Z]+)?(?:(?:.|\n)*?)```)(.*\n?)$/gm) - - for part, index in msgParts - # Verify if this part is code - codeMatch = part.match(/^```(.*[\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: "
    ```
    " + result.value + "
    ```
    " - noHtml: "```\n#{s.stripTags(result.value)}\n```" - - msgParts[index] = token - else - msgParts[index] = part - - # Re-mount message - message.html = msgParts.join('') - -RocketChat.MarkdownCode = MarkdownCode - -# MarkdownCode gets higher priority over Markdown so it's possible place a callback in between (katex for exmaple) -RocketChat.callbacks.add 'renderMessage', MarkdownCode, RocketChat.callbacks.priority.HIGH - 2, 'markdowncode' diff --git a/packages/rocketchat-markdown/markdowncode.js b/packages/rocketchat-markdown/markdowncode.js new file mode 100644 index 00000000000..1ff5ca80f78 --- /dev/null +++ b/packages/rocketchat-markdown/markdowncode.js @@ -0,0 +1,112 @@ +/* + * MarkdownCode is a named function that will parse `inline code` and ```codeblock``` syntaxes + * @param {Object} message - The message object + */ +import hljs from 'highlight.js'; + +class MarkdownCode { + constructor(message) { + + if (s.trim(message.html)) { + if (message.tokens == null) { + message.tokens = []; + } + + MarkdownCode.handle_codeblocks(message); + MarkdownCode.handle_inlinecode(message); + + if (window && window.rocketDebug) { + console.log('Markdown', message); + } + } + + return message; + } + + static handle_inlinecode(message) { + // Support `text` + return message.html = message.html.replace(/(^|>|[ >_*~])\`([^`\r\n]+)\`([<_*~]|\B|\b|$)/gm, (match, p1, p2, p3) => { + const token = `=!=${ Random.id() }=!=`; + + message.tokens.push({ + token, + text: `${ p1 }\`${ p2 }\`${ p3 }`, + noHtml: match + }); + + return token; + }); + } + + static handle_codeblocks(message) { + // Count occurencies of ``` + const 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 + const msgParts = message.html.split(/(^.*)(```(?:[a-zA-Z]+)?(?:(?:.|\n)*?)```)(.*\n?)$/gm); + + for (let index = 0; index < msgParts.length; index++) { + // Verify if this part is code + const part = msgParts[index]; + const codeMatch = part.match(/^```(.*[\n\ ]?)([\s\S]*?)```+?$/); + + if (codeMatch != null) { + // Process highlight if this part is code + let code; + let lang; + let result; + const singleLine = codeMatch[0].indexOf('\n') === -1; + + if (singleLine) { + lang = ''; + code = _.unescapeHTML(codeMatch[1] + codeMatch[2]); + } else { + lang = codeMatch[1]; + code = _.unescapeHTML(codeMatch[2]); + } + + if (s.trim(lang) === '') { + lang = ''; + } + + if (!Array.from(hljs.listLanguages()).includes(s.trim(lang))) { + result = hljs.highlightAuto((lang + code)); + } else { + result = hljs.highlight(s.trim(lang), code); + } + + const token = `=!=${ Random.id() }=!=`; + + message.tokens.push({ + highlight: true, + token, + text: `
    \`\`\`
    ${ result.value }
    \`\`\`
    `, + noHtml: `\`\`\`\n${ s.stripTags(result.value) }\n\`\`\`` + }); + + msgParts[index] = token; + } else { + msgParts[index] = part; + } + } + + // Re-mount message + return message.html = msgParts.join(''); + } + } +} + +RocketChat.MarkdownCode = MarkdownCode; + +const MarkdownCodeCB = (message) => new MarkdownCode(message); + +// MarkdownCode gets higher priority over Markdown so it's possible place a callback in between (katex for exmaple) +RocketChat.callbacks.add('renderMessage', MarkdownCodeCB, RocketChat.callbacks.priority.HIGH - 2, 'markdowncode'); diff --git a/packages/rocketchat-markdown/package.js b/packages/rocketchat-markdown/package.js index a329c83095d..4c24cce6976 100644 --- a/packages/rocketchat-markdown/package.js +++ b/packages/rocketchat-markdown/package.js @@ -7,7 +7,6 @@ Package.describe({ Package.onUse(function(api) { api.use([ - 'coffeescript', 'ecmascript', 'underscore', 'templating', @@ -15,18 +14,17 @@ Package.onUse(function(api) { 'rocketchat:lib' ]); - api.addFiles('settings.coffee', 'server'); - api.addFiles('markdown.coffee'); - api.addFiles('markdowncode.coffee'); + api.addFiles('settings.js', 'server'); + api.addFiles('markdown.js'); + api.addFiles('markdowncode.js'); }); Package.onTest(function(api) { api.use([ - 'coffeescript', 'sanjo:jasmine@0.20.2', 'rocketchat:lib', 'rocketchat:markdown' ]); - api.addFiles('tests/jasmine/client/unit/markdown.spec.coffee', 'client'); + api.addFiles('tests/jasmine/client/unit/markdown.spec.js', 'client'); }); diff --git a/packages/rocketchat-markdown/settings.coffee b/packages/rocketchat-markdown/settings.coffee deleted file mode 100644 index 443962e251f..00000000000 --- a/packages/rocketchat-markdown/settings.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Meteor.startup -> - RocketChat.settings.add 'Markdown_Headers', false, {type: 'boolean', group: 'Message', section: 'Markdown', public: true} - RocketChat.settings.add 'Markdown_SupportSchemesForLink', 'http,https', {type: 'string', group: 'Message', section: 'Markdown', public: true, i18nDescription: 'Markdown_SupportSchemesForLink_Description'} diff --git a/packages/rocketchat-markdown/settings.js b/packages/rocketchat-markdown/settings.js new file mode 100644 index 00000000000..db5844392b5 --- /dev/null +++ b/packages/rocketchat-markdown/settings.js @@ -0,0 +1,16 @@ +Meteor.startup(() => { + RocketChat.settings.add('Markdown_Headers', false, { + type: 'boolean', + group: 'Message', + section: 'Markdown', + public: true + }); + + return RocketChat.settings.add('Markdown_SupportSchemesForLink', 'http,https', { + type: 'string', + group: 'Message', + section: 'Markdown', + public: true, + i18nDescription: 'Markdown_SupportSchemesForLink_Description' + }); +}); diff --git a/packages/rocketchat-markdown/tests/jasmine/client/unit/markdown.spec.coffee b/packages/rocketchat-markdown/tests/jasmine/client/unit/markdown.spec.coffee deleted file mode 100644 index f154266a873..00000000000 --- a/packages/rocketchat-markdown/tests/jasmine/client/unit/markdown.spec.coffee +++ /dev/null @@ -1,4 +0,0 @@ -describe 'rocketchat:markdown Client', -> - - it 'should exist', -> - expect(RocketChat.Markdown).toBeDefined() diff --git a/packages/rocketchat-mentions-flextab/client/actionButton.coffee b/packages/rocketchat-mentions-flextab/client/actionButton.coffee deleted file mode 100644 index 84d350c9680..00000000000 --- a/packages/rocketchat-mentions-flextab/client/actionButton.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Meteor.startup -> - RocketChat.MessageAction.addButton - id: 'jump-to-message' - icon: 'icon-right-hand' - i18nLabel: 'Jump_to_message' - context: [ - 'mentions' - ] - action: (event, instance) -> - message = @_arguments[1] - RocketChat.MessageAction.hideDropDown() - RoomHistoryManager.getSurroundingMessages(message, 50) - - validation: (message) -> - return message.mentionedList is true - - order: 100 diff --git a/packages/rocketchat-mentions-flextab/client/actionButton.js b/packages/rocketchat-mentions-flextab/client/actionButton.js new file mode 100644 index 00000000000..9f233569363 --- /dev/null +++ b/packages/rocketchat-mentions-flextab/client/actionButton.js @@ -0,0 +1,17 @@ +Meteor.startup(function() { + return RocketChat.MessageAction.addButton({ + id: 'jump-to-message', + icon: 'icon-right-hand', + i18nLabel: 'Jump_to_message', + context: ['mentions'], + action() { + const message = this._arguments[1]; + RocketChat.MessageAction.hideDropDown(); + return RoomHistoryManager.getSurroundingMessages(message, 50); + }, + validation(message) { + return message.mentionedList === true; + }, + order: 100 + }); +}); diff --git a/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.coffee b/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.coffee deleted file mode 100644 index 98325aa643a..00000000000 --- a/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.coffee +++ /dev/null @@ -1 +0,0 @@ -@MentionedMessage = new Mongo.Collection 'rocketchat_mentioned_message' diff --git a/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.js b/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.js new file mode 100644 index 00000000000..fffbdd257eb --- /dev/null +++ b/packages/rocketchat-mentions-flextab/client/lib/MentionedMessage.js @@ -0,0 +1 @@ +this.MentionedMessage = new Mongo.Collection('rocketchat_mentioned_message'); diff --git a/packages/rocketchat-mentions-flextab/client/tabBar.coffee b/packages/rocketchat-mentions-flextab/client/tabBar.js similarity index 64% rename from packages/rocketchat-mentions-flextab/client/tabBar.coffee rename to packages/rocketchat-mentions-flextab/client/tabBar.js index 70b75724e52..272b29ede31 100644 --- a/packages/rocketchat-mentions-flextab/client/tabBar.coffee +++ b/packages/rocketchat-mentions-flextab/client/tabBar.js @@ -1,9 +1,10 @@ -Meteor.startup -> - RocketChat.TabBar.addButton({ +Meteor.startup(function() { + return RocketChat.TabBar.addButton({ groups: ['channel', 'group'], id: 'mentions', i18nTitle: 'Mentions', icon: 'icon-at', template: 'mentionsFlexTab', order: 3 - }) + }); +}); diff --git a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee deleted file mode 100644 index 0d6fab78837..00000000000 --- a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.coffee +++ /dev/null @@ -1,39 +0,0 @@ -Template.mentionsFlexTab.helpers - hasMessages: -> - return MentionedMessage.find({ rid: @rid }, { sort: { ts: -1 } }).count() > 0 - - messages: -> - return MentionedMessage.find { rid: @rid }, { sort: { ts: -1 } } - - message: -> - return _.extend(this, { customClass: 'mentions' }) - - hasMore: -> - return Template.instance().hasMore.get() - -Template.mentionsFlexTab.onCreated -> - @hasMore = new ReactiveVar true - @limit = new ReactiveVar 50 - @autorun => - @subscribe 'mentionedMessages', @data.rid, @limit.get(), => - if MentionedMessage.find({ rid: @data.rid }).count() < @limit.get() - @hasMore.set false - -Template.mentionsFlexTab.events - 'click .message-cog': (e, t) -> - e.stopPropagation() - e.preventDefault() - message_id = $(e.currentTarget).closest('.message').attr('id') - RocketChat.MessageAction.hideDropDown() - t.$("\##{message_id} .message-dropdown").remove() - message = MentionedMessage.findOne message_id - actions = RocketChat.MessageAction.getButtons message, 'mentions' - el = Blaze.toHTMLWithData Template.messageDropdown, { actions: actions } - t.$("\##{message_id} .message-cog-container").append el - dropDown = t.$("\##{message_id} .message-dropdown") - dropDown.show() - - 'scroll .content': _.throttle (e, instance) -> - if e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get() - instance.limit.set(instance.limit.get() + 50) - , 200 diff --git a/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.js b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.js new file mode 100644 index 00000000000..a929120d159 --- /dev/null +++ b/packages/rocketchat-mentions-flextab/client/views/mentionsFlexTab.js @@ -0,0 +1,65 @@ +/*globals MentionedMessage */ +Template.mentionsFlexTab.helpers({ + hasMessages() { + return MentionedMessage.find({ + rid: this.rid + }, { + sort: { + ts: -1 + } + }).count() > 0; + }, + messages() { + return MentionedMessage.find({ + rid: this.rid + }, { + sort: { + ts: -1 + } + }); + }, + message() { + return _.extend(this, { + customClass: 'mentions' + }); + }, + hasMore() { + return Template.instance().hasMore.get(); + } +}); + +Template.mentionsFlexTab.onCreated(function() { + this.hasMore = new ReactiveVar(true); + this.limit = new ReactiveVar(50); + return this.autorun(() => { + const mentionedMessageFind = MentionedMessage.find({ rid: this.data.rid }); + return this.subscribe('mentionedMessages', this.data.rid, this.limit.get(), () => { + if (mentionedMessageFind.count() < this.limit.get()) { + return this.hasMore.set(false); + } + }); + }); +}); + +Template.mentionsFlexTab.events({ + 'click .message-cog'(e, t) { + e.stopPropagation(); + e.preventDefault(); + const message_id = $(e.currentTarget).closest('.message').attr('id'); + RocketChat.MessageAction.hideDropDown(); + t.$(`\#${ message_id } .message-dropdown`).remove(); + const message = MentionedMessage.findOne(message_id); + const actions = RocketChat.MessageAction.getButtons(message, 'mentions'); + const el = Blaze.toHTMLWithData(Template.messageDropdown, { + actions + }); + t.$(`\#${ message_id } .message-cog-container`).append(el); + const dropDown = t.$(`\#${ message_id } .message-dropdown`); + return dropDown.show(); + }, + 'scroll .content': _.throttle(function(e, instance) { + if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) { + return instance.limit.set(instance.limit.get() + 50); + } + }, 200) +}); diff --git a/packages/rocketchat-mentions-flextab/package.js b/packages/rocketchat-mentions-flextab/package.js index 127ecc9f699..4f38223f73a 100644 --- a/packages/rocketchat-mentions-flextab/package.js +++ b/packages/rocketchat-mentions-flextab/package.js @@ -9,7 +9,6 @@ Package.onUse(function(api) { api.use([ 'mongo', 'ecmascript', - 'coffeescript', 'underscore', 'less', 'rocketchat:lib' @@ -18,15 +17,15 @@ Package.onUse(function(api) { api.use('templating', 'client'); api.addFiles([ - 'client/lib/MentionedMessage.coffee', + 'client/lib/MentionedMessage.js', 'client/views/stylesheets/mentionsFlexTab.less', 'client/views/mentionsFlexTab.html', - 'client/views/mentionsFlexTab.coffee', - 'client/actionButton.coffee', - 'client/tabBar.coffee' + 'client/views/mentionsFlexTab.js', + 'client/actionButton.js', + 'client/tabBar.js' ], 'client'); api.addFiles([ - 'server/publications/mentionedMessages.coffee' + 'server/publications/mentionedMessages.js' ], 'server'); }); diff --git a/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.coffee b/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.coffee deleted file mode 100644 index 72a557e2df8..00000000000 --- a/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Meteor.publish 'mentionedMessages', (rid, limit=50) -> - unless this.userId - return this.ready() - - publication = @ - - user = RocketChat.models.Users.findOneById this.userId - unless user - return this.ready() - - cursorHandle = RocketChat.models.Messages.findVisibleByMentionAndRoomId(user.username, rid, { sort: { ts: -1 }, limit: limit }).observeChanges - added: (_id, record) -> - record.mentionedList = true - publication.added('rocketchat_mentioned_message', _id, record) - - changed: (_id, record) -> - record.mentionedList = true - publication.changed('rocketchat_mentioned_message', _id, record) - - removed: (_id) -> - publication.removed('rocketchat_mentioned_message', _id) - - @ready() - @onStop -> - cursorHandle.stop() diff --git a/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.js b/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.js new file mode 100644 index 00000000000..994454f9fd8 --- /dev/null +++ b/packages/rocketchat-mentions-flextab/server/publications/mentionedMessages.js @@ -0,0 +1,32 @@ +Meteor.publish('mentionedMessages', function(rid, limit = 50) { + if (!this.userId) { + return this.ready(); + } + const publication = this; + const user = RocketChat.models.Users.findOneById(this.userId); + if (!user) { + return this.ready(); + } + const cursorHandle = RocketChat.models.Messages.findVisibleByMentionAndRoomId(user.username, rid, { + sort: { + ts: -1 + }, + limit + }).observeChanges({ + added(_id, record) { + record.mentionedList = true; + return publication.added('rocketchat_mentioned_message', _id, record); + }, + changed(_id, record) { + record.mentionedList = true; + return publication.changed('rocketchat_mentioned_message', _id, record); + }, + removed(_id) { + return publication.removed('rocketchat_mentioned_message', _id); + } + }); + this.ready(); + return this.onStop(function() { + return cursorHandle.stop(); + }); +}); diff --git a/packages/rocketchat-message-pin/client/actionButton.coffee b/packages/rocketchat-message-pin/client/actionButton.coffee deleted file mode 100644 index 4b5861f5bbc..00000000000 --- a/packages/rocketchat-message-pin/client/actionButton.coffee +++ /dev/null @@ -1,87 +0,0 @@ -import toastr from 'toastr' -Meteor.startup -> - RocketChat.MessageAction.addButton - id: 'pin-message' - icon: 'icon-pin' - i18nLabel: 'Pin_Message' - context: [ - 'pinned' - 'message' - 'message-mobile' - ] - action: (event, instance) -> - message = @_arguments[1] - message.pinned = true - Meteor.call 'pinMessage', message, (error, result) -> - if error - return handleError(error) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - else if message.pinned or not RocketChat.settings.get('Message_AllowPinning') - return false - - return RocketChat.authz.hasAtLeastOnePermission 'pin-message', message.rid - order: 20 - - RocketChat.MessageAction.addButton - id: 'unpin-message' - icon: 'icon-pin rotate-45' - i18nLabel: 'Unpin_Message' - context: [ - 'pinned' - 'message' - 'message-mobile' - ] - action: (event, instance) -> - message = @_arguments[1] - message.pinned = false - Meteor.call 'unpinMessage', message, (error, result) -> - if error - return handleError(error) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - else if not message.pinned or not RocketChat.settings.get('Message_AllowPinning') - return false - - return RocketChat.authz.hasAtLeastOnePermission 'pin-message', message.rid - order: 21 - - RocketChat.MessageAction.addButton - id: 'jump-to-pin-message' - icon: 'icon-right-hand' - i18nLabel: 'Jump_to_message' - context: [ - 'pinned' - ] - action: (event, instance) -> - message = @_arguments[1] - RocketChat.MessageAction.hideDropDown() - RoomHistoryManager.getSurroundingMessages(message, 50) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - return true - order: 100 - - RocketChat.MessageAction.addButton - id: 'permalink-pinned' - icon: 'icon-link' - i18nLabel: 'Permalink' - classes: 'clipboard' - context: [ - 'pinned' - ] - action: (event, instance) -> - message = @_arguments[1] - RocketChat.MessageAction.hideDropDown() - $(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id)); - toastr.success(TAPi18n.__('Copied')) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - return true - order: 101 diff --git a/packages/rocketchat-message-pin/client/actionButton.js b/packages/rocketchat-message-pin/client/actionButton.js new file mode 100644 index 00000000000..826f33f9fbe --- /dev/null +++ b/packages/rocketchat-message-pin/client/actionButton.js @@ -0,0 +1,97 @@ +import toastr from 'toastr'; + +Meteor.startup(function() { + RocketChat.MessageAction.addButton({ + id: 'pin-message', + icon: 'icon-pin', + i18nLabel: 'Pin_Message', + context: ['pinned', 'message', 'message-mobile'], + + action() { + const message = this._arguments[1]; + message.pinned = true; + return Meteor.call('pinMessage', message, function(error) { + if (error) { + return handleError(error); + } + }); + }, + + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } else if (message.pinned || !RocketChat.settings.get('Message_AllowPinning')) { + return false; + } + return RocketChat.authz.hasAtLeastOnePermission('pin-message', message.rid); + }, + order: 20 + }); + + RocketChat.MessageAction.addButton({ + id: 'unpin-message', + icon: 'icon-pin rotate-45', + i18nLabel: 'Unpin_Message', + context: ['pinned', 'message', 'message-mobile'], + action() { + const message = this._arguments[1]; + message.pinned = false; + return Meteor.call('unpinMessage', message, function(error) { + if (error) { + return handleError(error); + } + }); + }, + + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } else if (!message.pinned || !RocketChat.settings.get('Message_AllowPinning')) { + return false; + } + return RocketChat.authz.hasAtLeastOnePermission('pin-message', message.rid); + }, + order: 21 + }); + + RocketChat.MessageAction.addButton({ + id: 'jump-to-pin-message', + icon: 'icon-right-hand', + i18nLabel: 'Jump_to_message', + context: ['pinned'], + action() { + const message = this._arguments[1]; + RocketChat.MessageAction.hideDropDown(); + return RoomHistoryManager.getSurroundingMessages(message, 50); + }, + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + return true; + }, + order: 100 + }); + + return RocketChat.MessageAction.addButton({ + id: 'permalink-pinned', + icon: 'icon-link', + i18nLabel: 'Permalink', + classes: 'clipboard', + context: ['pinned'], + action() { + const message = this._arguments[1]; + RocketChat.MessageAction.hideDropDown(); + $(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id)); + return toastr.success(TAPi18n.__('Copied')); + }, + + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + return true; + }, + order: 101 + }); +}); diff --git a/packages/rocketchat-message-pin/client/lib/PinnedMessage.coffee b/packages/rocketchat-message-pin/client/lib/PinnedMessage.coffee deleted file mode 100644 index 4b4849c8acd..00000000000 --- a/packages/rocketchat-message-pin/client/lib/PinnedMessage.coffee +++ /dev/null @@ -1 +0,0 @@ -@PinnedMessage = new Mongo.Collection 'rocketchat_pinned_message' diff --git a/packages/rocketchat-message-pin/client/lib/PinnedMessage.js b/packages/rocketchat-message-pin/client/lib/PinnedMessage.js new file mode 100644 index 00000000000..a6c9b072f91 --- /dev/null +++ b/packages/rocketchat-message-pin/client/lib/PinnedMessage.js @@ -0,0 +1 @@ +this.PinnedMessage = new Mongo.Collection('rocketchat_pinned_message'); diff --git a/packages/rocketchat-message-pin/client/pinMessage.coffee b/packages/rocketchat-message-pin/client/pinMessage.coffee deleted file mode 100644 index 0151c21ab63..00000000000 --- a/packages/rocketchat-message-pin/client/pinMessage.coffee +++ /dev/null @@ -1,30 +0,0 @@ -Meteor.methods - pinMessage: (message) -> - if not Meteor.userId() - return false - - if not RocketChat.settings.get 'Message_AllowPinning' - return false - - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - ChatMessage.update - _id: message._id - , - $set: { pinned: true } - - unpinMessage: (message) -> - if not Meteor.userId() - return false - - if not RocketChat.settings.get 'Message_AllowPinning' - return false - - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - ChatMessage.update - _id: message._id - , - $set: { pinned: false } diff --git a/packages/rocketchat-message-pin/client/pinMessage.js b/packages/rocketchat-message-pin/client/pinMessage.js new file mode 100644 index 00000000000..f863ac6009e --- /dev/null +++ b/packages/rocketchat-message-pin/client/pinMessage.js @@ -0,0 +1,38 @@ +Meteor.methods({ + pinMessage(message) { + if (!Meteor.userId()) { + return false; + } + if (!RocketChat.settings.get('Message_AllowPinning')) { + return false; + } + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + return ChatMessage.update({ + _id: message._id + }, { + $set: { + pinned: true + } + }); + }, + unpinMessage(message) { + if (!Meteor.userId()) { + return false; + } + if (!RocketChat.settings.get('Message_AllowPinning')) { + return false; + } + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + return ChatMessage.update({ + _id: message._id + }, { + $set: { + pinned: false + } + }); + } +}); diff --git a/packages/rocketchat-message-pin/client/tabBar.coffee b/packages/rocketchat-message-pin/client/tabBar.coffee deleted file mode 100644 index f96377abdad..00000000000 --- a/packages/rocketchat-message-pin/client/tabBar.coffee +++ /dev/null @@ -1,13 +0,0 @@ -Meteor.startup -> - Tracker.autorun -> - if RocketChat.settings.get 'Message_AllowPinning' - RocketChat.TabBar.addButton({ - groups: ['channel', 'group', 'direct'], - id: 'pinned-messages', - i18nTitle: 'Pinned_Messages', - icon: 'icon-pin', - template: 'pinnedMessages', - order: 10 - }) - else - RocketChat.TabBar.removeButton 'pinned-messages' diff --git a/packages/rocketchat-message-pin/client/tabBar.js b/packages/rocketchat-message-pin/client/tabBar.js new file mode 100644 index 00000000000..253d4f22b21 --- /dev/null +++ b/packages/rocketchat-message-pin/client/tabBar.js @@ -0,0 +1,16 @@ +Meteor.startup(function() { + return Tracker.autorun(function() { + if (RocketChat.settings.get('Message_AllowPinning')) { + return RocketChat.TabBar.addButton({ + groups: ['channel', 'group', 'direct'], + id: 'pinned-messages', + i18nTitle: 'Pinned_Messages', + icon: 'icon-pin', + template: 'pinnedMessages', + order: 10 + }); + } else { + return RocketChat.TabBar.removeButton('pinned-messages'); + } + }); +}); diff --git a/packages/rocketchat-message-pin/client/views/pinnedMessages.coffee b/packages/rocketchat-message-pin/client/views/pinnedMessages.coffee deleted file mode 100644 index f99ec368d47..00000000000 --- a/packages/rocketchat-message-pin/client/views/pinnedMessages.coffee +++ /dev/null @@ -1,40 +0,0 @@ -Template.pinnedMessages.helpers - hasMessages: -> - return PinnedMessage.find({ rid: @rid }, { sort: { ts: -1 } }).count() > 0 - - messages: -> - return PinnedMessage.find { rid: @rid }, { sort: { ts: -1 } } - - message: -> - return _.extend(this, { customClass: 'pinned' }) - - hasMore: -> - return Template.instance().hasMore.get() - -Template.pinnedMessages.onCreated -> - @hasMore = new ReactiveVar true - @limit = new ReactiveVar 50 - @autorun => - data = Template.currentData() - @subscribe 'pinnedMessages', data.rid, @limit.get(), => - if PinnedMessage.find({ rid: data.rid }).count() < @limit.get() - @hasMore.set false - -Template.pinnedMessages.events - 'click .message-cog': (e, t) -> - e.stopPropagation() - e.preventDefault() - message_id = $(e.currentTarget).closest('.message').attr('id') - RocketChat.MessageAction.hideDropDown() - t.$("\##{message_id} .message-dropdown").remove() - message = PinnedMessage.findOne message_id - actions = RocketChat.MessageAction.getButtons message, 'pinned' - el = Blaze.toHTMLWithData Template.messageDropdown, { actions: actions } - t.$("\##{message_id} .message-cog-container").append el - dropDown = t.$("\##{message_id} .message-dropdown") - dropDown.show() - - 'scroll .content': _.throttle (e, instance) -> - if e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get() - instance.limit.set(instance.limit.get() + 50) - , 200 diff --git a/packages/rocketchat-message-pin/client/views/pinnedMessages.js b/packages/rocketchat-message-pin/client/views/pinnedMessages.js new file mode 100644 index 00000000000..b6a4a38ce2b --- /dev/null +++ b/packages/rocketchat-message-pin/client/views/pinnedMessages.js @@ -0,0 +1,67 @@ +/* globals PinnedMessage */ +Template.pinnedMessages.helpers({ + hasMessages() { + return PinnedMessage.find({ + rid: this.rid + }, { + sort: { + ts: -1 + } + }).count() > 0; + }, + messages() { + return PinnedMessage.find({ + rid: this.rid + }, { + sort: { + ts: -1 + } + }); + }, + message() { + return _.extend(this, { + customClass: 'pinned' + }); + }, + hasMore() { + return Template.instance().hasMore.get(); + } +}); + +Template.pinnedMessages.onCreated(function() { + this.hasMore = new ReactiveVar(true); + this.limit = new ReactiveVar(50); + return this.autorun(() => { + const data = Template.currentData(); + return this.subscribe('pinnedMessages', data.rid, this.limit.get(), () => { + if (PinnedMessage.find({ + rid: data.rid + }).count() < this.limit.get()) { + return this.hasMore.set(false); + } + }); + }); +}); + +Template.pinnedMessages.events({ + 'click .message-cog'(e, t) { + e.stopPropagation(); + e.preventDefault(); + const message_id = $(e.currentTarget).closest('.message').attr('id'); + RocketChat.MessageAction.hideDropDown(); + t.$(`\#${ message_id } .message-dropdown`).remove(); + const message = PinnedMessage.findOne(message_id); + const actions = RocketChat.MessageAction.getButtons(message, 'pinned'); + const el = Blaze.toHTMLWithData(Template.messageDropdown, { + actions + }); + t.$(`\#${ message_id } .message-cog-container`).append(el); + const dropDown = t.$(`\#${ message_id } .message-dropdown`); + return dropDown.show(); + }, + 'scroll .content': _.throttle(function(e, instance) { + if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight && instance.hasMore.get()) { + return instance.limit.set(instance.limit.get() + 50); + } + }, 200) +}); diff --git a/packages/rocketchat-message-pin/package.js b/packages/rocketchat-message-pin/package.js index 30414b03156..b9daa1c4ef9 100644 --- a/packages/rocketchat-message-pin/package.js +++ b/packages/rocketchat-message-pin/package.js @@ -7,7 +7,6 @@ Package.describe({ Package.onUse(function(api) { api.use([ 'mongo', - 'coffeescript', 'ecmascript', 'underscore', 'less', @@ -17,20 +16,20 @@ Package.onUse(function(api) { api.use('templating', 'client'); api.addFiles([ - 'client/lib/PinnedMessage.coffee', - 'client/actionButton.coffee', + 'client/lib/PinnedMessage.js', + 'client/actionButton.js', 'client/messageType.js', - 'client/pinMessage.coffee', - 'client/tabBar.coffee', + 'client/pinMessage.js', + 'client/tabBar.js', 'client/views/pinnedMessages.html', - 'client/views/pinnedMessages.coffee', + 'client/views/pinnedMessages.js', 'client/views/stylesheets/messagepin.less' ], 'client'); api.addFiles([ - 'server/settings.coffee', - 'server/pinMessage.coffee', - 'server/publications/pinnedMessages.coffee', - 'server/startup/indexes.coffee' + 'server/settings.js', + 'server/pinMessage.js', + 'server/publications/pinnedMessages.js', + 'server/startup/indexes.js' ], 'server'); }); diff --git a/packages/rocketchat-message-pin/server/pinMessage.coffee b/packages/rocketchat-message-pin/server/pinMessage.coffee deleted file mode 100644 index f2049c58465..00000000000 --- a/packages/rocketchat-message-pin/server/pinMessage.coffee +++ /dev/null @@ -1,77 +0,0 @@ -Meteor.methods - pinMessage: (message, pinnedAt) -> - if not Meteor.userId() - throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'pinMessage' }) - - if not RocketChat.settings.get 'Message_AllowPinning' - throw new Meteor.Error 'error-action-not-allowed', 'Message pinning not allowed', { method: 'pinMessage', action: 'Message_pinning' } - - room = RocketChat.models.Rooms.findOneById(message.rid) - - 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() - - originalMessage.pinned = true - originalMessage.pinnedAt = pinnedAt || Date.now - originalMessage.pinnedBy = - _id: Meteor.userId() - username: me.username - - originalMessage = RocketChat.callbacks.run 'beforeSaveMessage', originalMessage - - RocketChat.models.Messages.setPinnedByIdAndUserId originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned - - RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser 'message_pinned', originalMessage.rid, '', me, - attachments: [ - "text" : originalMessage.msg - "author_name" : originalMessage.u.username, - "author_icon" : getAvatarUrlFromUsername(originalMessage.u.username), - "ts" : originalMessage.ts - ] - - unpinMessage: (message) -> - if not Meteor.userId() - throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'unpinMessage' }) - - if not RocketChat.settings.get 'Message_AllowPinning' - throw new Meteor.Error 'error-action-not-allowed', 'Message pinning not allowed', { method: 'unpinMessage', action: 'Message_pinning' } - - room = RocketChat.models.Rooms.findOneById(message.rid) - - 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 originalMessage._id - - me = RocketChat.models.Users.findOneById Meteor.userId() - - originalMessage.pinned = false - originalMessage.pinnedBy = - _id: Meteor.userId() - username: me.username - - originalMessage = RocketChat.callbacks.run 'beforeSaveMessage', originalMessage - - RocketChat.models.Messages.setPinnedByIdAndUserId originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned - - - # Meteor.defer -> - # RocketChat.callbacks.run 'afterSaveMessage', RocketChat.models.Messages.findOneById(message.id) diff --git a/packages/rocketchat-message-pin/server/pinMessage.js b/packages/rocketchat-message-pin/server/pinMessage.js new file mode 100644 index 00000000000..defb7727b51 --- /dev/null +++ b/packages/rocketchat-message-pin/server/pinMessage.js @@ -0,0 +1,88 @@ +Meteor.methods({ + pinMessage(message, pinnedAt) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'pinMessage' + }); + } + if (!RocketChat.settings.get('Message_AllowPinning')) { + throw new Meteor.Error('error-action-not-allowed', 'Message pinning not allowed', { + method: 'pinMessage', + action: 'Message_pinning' + }); + } + const room = RocketChat.models.Rooms.findOneById(message.rid); + if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + return false; + } + let originalMessage = RocketChat.models.Messages.findOneById(message._id); + if (originalMessage == null || originalMessage._id == null) { + 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); + } + const me = RocketChat.models.Users.findOneById(Meteor.userId()); + originalMessage.pinned = true; + originalMessage.pinnedAt = pinnedAt || Date.now; + originalMessage.pinnedBy = { + _id: Meteor.userId(), + username: me.username + }; + originalMessage = RocketChat.callbacks.run('beforeSaveMessage', originalMessage); + RocketChat.models.Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned); + return RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser('message_pinned', originalMessage.rid, '', me, { + attachments: [ + { + 'text': originalMessage.msg, + 'author_name': originalMessage.u.username, + 'author_icon': getAvatarUrlFromUsername(originalMessage.u.username), + 'ts': originalMessage.ts + } + ] + }); + }, + unpinMessage(message) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'unpinMessage' + }); + } + if (!RocketChat.settings.get('Message_AllowPinning')) { + throw new Meteor.Error('error-action-not-allowed', 'Message pinning not allowed', { + method: 'unpinMessage', + action: 'Message_pinning' + }); + } + const room = RocketChat.models.Rooms.findOneById(message.rid); + + if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + return false; + } + + let originalMessage = RocketChat.models.Messages.findOneById(message._id); + + if (originalMessage == null || originalMessage._id == null) { + 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(originalMessage._id); + } + const me = RocketChat.models.Users.findOneById(Meteor.userId()); + originalMessage.pinned = false; + originalMessage.pinnedBy = { + _id: Meteor.userId(), + username: me.username + }; + originalMessage = RocketChat.callbacks.run('beforeSaveMessage', originalMessage); + return RocketChat.models.Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned); + } +}); diff --git a/packages/rocketchat-message-pin/server/publications/pinnedMessages.coffee b/packages/rocketchat-message-pin/server/publications/pinnedMessages.coffee deleted file mode 100644 index efa77f81456..00000000000 --- a/packages/rocketchat-message-pin/server/publications/pinnedMessages.coffee +++ /dev/null @@ -1,23 +0,0 @@ -Meteor.publish 'pinnedMessages', (rid, limit=50) -> - unless this.userId - return this.ready() - - publication = @ - - user = RocketChat.models.Users.findOneById this.userId - unless user - return this.ready() - - cursorHandle = RocketChat.models.Messages.findPinnedByRoom(rid, { sort: { ts: -1 }, limit: limit }).observeChanges - added: (_id, record) -> - publication.added('rocketchat_pinned_message', _id, record) - - changed: (_id, record) -> - publication.changed('rocketchat_pinned_message', _id, record) - - removed: (_id) -> - publication.removed('rocketchat_pinned_message', _id) - - @ready() - @onStop -> - cursorHandle.stop() diff --git a/packages/rocketchat-message-pin/server/publications/pinnedMessages.js b/packages/rocketchat-message-pin/server/publications/pinnedMessages.js new file mode 100644 index 00000000000..e6cda8b3b34 --- /dev/null +++ b/packages/rocketchat-message-pin/server/publications/pinnedMessages.js @@ -0,0 +1,26 @@ +Meteor.publish('pinnedMessages', function(rid, limit = 50) { + if (!this.userId) { + return this.ready(); + } + const publication = this; + + const user = RocketChat.models.Users.findOneById(this.userId); + if (!user) { + return this.ready(); + } + const cursorHandle = RocketChat.models.Messages.findPinnedByRoom(rid, { sort: { ts: -1 }, limit }).observeChanges({ + added(_id, record) { + return publication.added('rocketchat_pinned_message', _id, record); + }, + changed(_id, record) { + return publication.changed('rocketchat_pinned_message', _id, record); + }, + removed(_id) { + return publication.removed('rocketchat_pinned_message', _id); + } + }); + this.ready(); + return this.onStop(function() { + return cursorHandle.stop(); + }); +}); diff --git a/packages/rocketchat-message-pin/server/settings.coffee b/packages/rocketchat-message-pin/server/settings.coffee deleted file mode 100644 index 29181cffd2a..00000000000 --- a/packages/rocketchat-message-pin/server/settings.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Meteor.startup -> - RocketChat.settings.add 'Message_AllowPinning', true, { type: 'boolean', group: 'Message', public: true } - RocketChat.models.Permissions.upsert('pin-message', { $setOnInsert: { roles: ['owner', 'moderator', 'admin'] } }); diff --git a/packages/rocketchat-message-pin/server/settings.js b/packages/rocketchat-message-pin/server/settings.js new file mode 100644 index 00000000000..01ccaafbc5a --- /dev/null +++ b/packages/rocketchat-message-pin/server/settings.js @@ -0,0 +1,12 @@ +Meteor.startup(function() { + RocketChat.settings.add('Message_AllowPinning', true, { + type: 'boolean', + group: 'Message', + 'public': true + }); + return RocketChat.models.Permissions.upsert('pin-message', { + $setOnInsert: { + roles: ['owner', 'moderator', 'admin'] + } + }); +}); diff --git a/packages/rocketchat-message-pin/server/startup/indexes.coffee b/packages/rocketchat-message-pin/server/startup/indexes.coffee deleted file mode 100644 index c008b0c3657..00000000000 --- a/packages/rocketchat-message-pin/server/startup/indexes.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Meteor.startup -> - Meteor.defer -> - RocketChat.models.Messages.tryEnsureIndex { 'pinnedBy._id': 1 }, { sparse: 1 } diff --git a/packages/rocketchat-message-pin/server/startup/indexes.js b/packages/rocketchat-message-pin/server/startup/indexes.js new file mode 100644 index 00000000000..bd3f4103554 --- /dev/null +++ b/packages/rocketchat-message-pin/server/startup/indexes.js @@ -0,0 +1,9 @@ +Meteor.startup(function() { + return Meteor.defer(function() { + return RocketChat.models.Messages.tryEnsureIndex({ + 'pinnedBy._id': 1 + }, { + sparse: 1 + }); + }); +}); diff --git a/packages/rocketchat-message-star/client/actionButton.coffee b/packages/rocketchat-message-star/client/actionButton.coffee deleted file mode 100644 index c0b873a6db8..00000000000 --- a/packages/rocketchat-message-star/client/actionButton.coffee +++ /dev/null @@ -1,83 +0,0 @@ -import toastr from 'toastr' -Meteor.startup -> - RocketChat.MessageAction.addButton - id: 'star-message' - icon: 'icon-star-empty' - i18nLabel: 'Star_Message' - context: [ - 'starred' - 'message' - 'message-mobile' - ] - action: (event, instance) -> - message = @_arguments[1] - message.starred = Meteor.userId() - Meteor.call 'starMessage', message, (error, result) -> - if error - return handleError(error) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - return RocketChat.settings.get('Message_AllowStarring') and not message.starred - order: 10 - - RocketChat.MessageAction.addButton - id: 'unstar-message' - icon: 'icon-star' - i18nLabel: 'Unstar_Message' - context: [ - 'starred' - 'message' - 'message-mobile' - ] - action: (event, instance) -> - message = @_arguments[1] - message.starred = false - Meteor.call 'starMessage', message, (error, result) -> - if error - return handleError(error) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - return RocketChat.settings.get('Message_AllowStarring') and message.starred - order: 10 - - RocketChat.MessageAction.addButton - id: 'jump-to-star-message' - icon: 'icon-right-hand' - i18nLabel: 'Jump_to_message' - context: [ - 'starred' - ] - action: (event, instance) -> - message = @_arguments[1] - RocketChat.MessageAction.hideDropDown() - RoomHistoryManager.getSurroundingMessages(message, 50) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - return true - order: 100 - - RocketChat.MessageAction.addButton - id: 'permalink-star' - icon: 'icon-link' - i18nLabel: 'Permalink' - classes: 'clipboard' - context: [ - 'starred' - ] - action: (event, instance) -> - message = @_arguments[1] - RocketChat.MessageAction.hideDropDown() - $(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id)); - toastr.success(TAPi18n.__('Copied')) - validation: (message) -> - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - return true - order: 101 diff --git a/packages/rocketchat-message-star/client/actionButton.js b/packages/rocketchat-message-star/client/actionButton.js new file mode 100644 index 00000000000..64faa7b24ba --- /dev/null +++ b/packages/rocketchat-message-star/client/actionButton.js @@ -0,0 +1,87 @@ +import toastr from 'toastr'; +Meteor.startup(function() { + RocketChat.MessageAction.addButton({ + id: 'star-message', + icon: 'icon-star-empty', + i18nLabel: 'Star_Message', + context: ['starred', 'message', 'message-mobile'], + action() { + const message = this._arguments[1]; + message.starred = Meteor.userId(); + return Meteor.call('starMessage', message, function(error) { + if (error) { + return handleError(error); + } + }); + }, + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null && RocketChat.settings.get('Message_AllowStarring')) { + return false; + } + + return !_.findWhere(message.starred, {_id: Meteor.userId()}); + }, + order: 10 + }); + RocketChat.MessageAction.addButton({ + id: 'unstar-message', + icon: 'icon-star', + i18nLabel: 'Unstar_Message', + context: ['starred', 'message', 'message-mobile'], + action() { + const message = this._arguments[1]; + message.starred = false; + return Meteor.call('starMessage', message, function(error) { + if (error) { + return handleError(error); + } + }); + }, + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null && RocketChat.settings.get('Message_AllowStarring')) { + return false; + } + + return Boolean(_.findWhere(message.starred, {_id: Meteor.userId()})); + }, + order: 10 + }); + RocketChat.MessageAction.addButton({ + id: 'jump-to-star-message', + icon: 'icon-right-hand', + i18nLabel: 'Jump_to_message', + context: ['starred'], + action() { + const message = this._arguments[1]; + RocketChat.MessageAction.hideDropDown(); + return RoomHistoryManager.getSurroundingMessages(message, 50); + }, + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + return true; + }, + order: 100 + }); + return RocketChat.MessageAction.addButton({ + id: 'permalink-star', + icon: 'icon-link', + i18nLabel: 'Permalink', + classes: 'clipboard', + context: ['starred'], + action() { + const message = this._arguments[1]; + RocketChat.MessageAction.hideDropDown(); + $(event.currentTarget).attr('data-clipboard-text', RocketChat.MessageAction.getPermaLink(message._id)); + return toastr.success(TAPi18n.__('Copied')); + }, + validation(message) { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + return true; + }, + order: 101 + }); +}); diff --git a/packages/rocketchat-message-star/client/lib/StarredMessage.coffee b/packages/rocketchat-message-star/client/lib/StarredMessage.coffee deleted file mode 100644 index 10798a3a464..00000000000 --- a/packages/rocketchat-message-star/client/lib/StarredMessage.coffee +++ /dev/null @@ -1 +0,0 @@ -@StarredMessage = new Mongo.Collection 'rocketchat_starred_message' diff --git a/packages/rocketchat-message-star/client/lib/StarredMessage.js b/packages/rocketchat-message-star/client/lib/StarredMessage.js new file mode 100644 index 00000000000..cd014935ac3 --- /dev/null +++ b/packages/rocketchat-message-star/client/lib/StarredMessage.js @@ -0,0 +1 @@ +this.StarredMessage = new Mongo.Collection('rocketchat_starred_message'); diff --git a/packages/rocketchat-message-star/client/starMessage.coffee b/packages/rocketchat-message-star/client/starMessage.coffee deleted file mode 100644 index d6b22b9488a..00000000000 --- a/packages/rocketchat-message-star/client/starMessage.coffee +++ /dev/null @@ -1,15 +0,0 @@ -Meteor.methods - starMessage: (message) -> - if not Meteor.userId() - return false - - if not RocketChat.models.Subscriptions.findOne({ rid: message.rid })? - return false - - if not RocketChat.settings.get 'Message_AllowStarring' - return false - - ChatMessage.update - _id: message._id - , - $set: { starred: !!message.starred } diff --git a/packages/rocketchat-message-star/client/starMessage.js b/packages/rocketchat-message-star/client/starMessage.js new file mode 100644 index 00000000000..cae539c7eaa --- /dev/null +++ b/packages/rocketchat-message-star/client/starMessage.js @@ -0,0 +1,20 @@ +Meteor.methods({ + starMessage(message) { + if (!Meteor.userId()) { + return false; + } + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + if (!RocketChat.settings.get('Message_AllowStarring')) { + return false; + } + return ChatMessage.update({ + _id: message._id + }, { + $set: { + starred: !!message.starred + } + }); + } +}); diff --git a/packages/rocketchat-message-star/client/tabBar.coffee b/packages/rocketchat-message-star/client/tabBar.js similarity index 68% rename from packages/rocketchat-message-star/client/tabBar.coffee rename to packages/rocketchat-message-star/client/tabBar.js index cab66321912..85f32f93349 100644 --- a/packages/rocketchat-message-star/client/tabBar.coffee +++ b/packages/rocketchat-message-star/client/tabBar.js @@ -1,9 +1,10 @@ -Meteor.startup -> - RocketChat.TabBar.addButton({ +Meteor.startup(function() { + return RocketChat.TabBar.addButton({ groups: ['channel', 'group', 'direct'], id: 'starred-messages', i18nTitle: 'Starred_Messages', icon: 'icon-star', template: 'starredMessages', order: 3 - }) + }); +}); diff --git a/packages/rocketchat-message-star/client/views/starredMessages.coffee b/packages/rocketchat-message-star/client/views/starredMessages.coffee deleted file mode 100644 index 9b34d626936..00000000000 --- a/packages/rocketchat-message-star/client/views/starredMessages.coffee +++ /dev/null @@ -1,40 +0,0 @@ -Template.starredMessages.helpers - hasMessages: -> - return StarredMessage.find({ rid: @rid }, { sort: { ts: -1 } }).count() > 0 - - messages: -> - return StarredMessage.find { rid: @rid }, { sort: { ts: -1 } } - - message: -> - return _.extend(this, { customClass: 'starred' }) - - hasMore: -> - return Template.instance().hasMore.get() - -Template.starredMessages.onCreated -> - @hasMore = new ReactiveVar true - @limit = new ReactiveVar 50 - @autorun => - sub = @subscribe 'starredMessages', @data.rid, @limit.get() - if sub.ready() - if StarredMessage.find({ rid: @data.rid }).count() < @limit.get() - @hasMore.set false - -Template.starredMessages.events - 'click .message-cog': (e, t) -> - e.stopPropagation() - e.preventDefault() - message_id = $(e.currentTarget).closest('.message').attr('id') - RocketChat.MessageAction.hideDropDown() - t.$("\##{message_id} .message-dropdown").remove() - message = StarredMessage.findOne message_id - actions = RocketChat.MessageAction.getButtons message, 'starred' - el = Blaze.toHTMLWithData Template.messageDropdown, { actions: actions } - t.$("\##{message_id} .message-cog-container").append el - dropDown = t.$("\##{message_id} .message-dropdown") - dropDown.show() - - 'scroll .content': _.throttle (e, instance) -> - if e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight - instance.limit.set(instance.limit.get() + 50) - , 200 diff --git a/packages/rocketchat-message-star/client/views/starredMessages.js b/packages/rocketchat-message-star/client/views/starredMessages.js new file mode 100644 index 00000000000..eed8d3d0bac --- /dev/null +++ b/packages/rocketchat-message-star/client/views/starredMessages.js @@ -0,0 +1,66 @@ +/*globals StarredMessage */ +Template.starredMessages.helpers({ + hasMessages() { + return StarredMessage.find({ + rid: this.rid + }, { + sort: { + ts: -1 + } + }).count() > 0; + }, + messages() { + return StarredMessage.find({ + rid: this.rid + }, { + sort: { + ts: -1 + } + }); + }, + message() { + return _.extend(this, { + customClass: 'starred' + }); + }, + hasMore() { + return Template.instance().hasMore.get(); + } +}); + +Template.starredMessages.onCreated(function() { + this.hasMore = new ReactiveVar(true); + this.limit = new ReactiveVar(50); + this.autorun(() => { + const sub = this.subscribe('starredMessages', this.data.rid, this.limit.get()); + const findStarredMessage = StarredMessage.find({ rid: this.data.rid }); + if (sub.ready()) { + if (findStarredMessage.count() < this.limit.get()) { + return this.hasMore.set(false); + } + } + }); +}); + +Template.starredMessages.events({ + 'click .message-cog'(e, t) { + e.stopPropagation(); + e.preventDefault(); + const message_id = $(e.currentTarget).closest('.message').attr('id'); + RocketChat.MessageAction.hideDropDown(); + t.$(`\#${ message_id } .message-dropdown`).remove(); + const message = StarredMessage.findOne(message_id); + const actions = RocketChat.MessageAction.getButtons(message, 'starred'); + const el = Blaze.toHTMLWithData(Template.messageDropdown, { + actions + }); + t.$(`\#${ message_id } .message-cog-container`).append(el); + const dropDown = t.$(`\#${ message_id } .message-dropdown`); + return dropDown.show(); + }, + 'scroll .content': _.throttle(function(e, instance) { + if (e.target.scrollTop >= e.target.scrollHeight - e.target.clientHeight) { + return instance.limit.set(instance.limit.get() + 50); + } + }, 200) +}); diff --git a/packages/rocketchat-message-star/package.js b/packages/rocketchat-message-star/package.js index c9bcefca8cb..b0e1f3e6b6b 100644 --- a/packages/rocketchat-message-star/package.js +++ b/packages/rocketchat-message-star/package.js @@ -8,7 +8,6 @@ Package.describe({ Package.onUse(function(api) { api.use([ 'mongo', - 'coffeescript', 'ecmascript', 'underscore', 'less', @@ -18,19 +17,19 @@ Package.onUse(function(api) { api.use('templating', 'client'); api.addFiles([ - 'client/lib/StarredMessage.coffee', - 'client/actionButton.coffee', - 'client/starMessage.coffee', - 'client/tabBar.coffee', + 'client/lib/StarredMessage.js', + 'client/actionButton.js', + 'client/starMessage.js', + 'client/tabBar.js', 'client/views/starredMessages.html', - 'client/views/starredMessages.coffee', + 'client/views/starredMessages.js', 'client/views/stylesheets/messagestar.less' ], 'client'); api.addFiles([ - 'server/settings.coffee', - 'server/starMessage.coffee', - 'server/publications/starredMessages.coffee', - 'server/startup/indexes.coffee' + 'server/settings.js', + 'server/starMessage.js', + 'server/publications/starredMessages.js', + 'server/startup/indexes.js' ], 'server'); }); diff --git a/packages/rocketchat-message-star/server/publications/starredMessages.coffee b/packages/rocketchat-message-star/server/publications/starredMessages.coffee deleted file mode 100644 index 51d664d7653..00000000000 --- a/packages/rocketchat-message-star/server/publications/starredMessages.coffee +++ /dev/null @@ -1,23 +0,0 @@ -Meteor.publish 'starredMessages', (rid, limit=50) -> - unless this.userId - return this.ready() - - publication = @ - - user = RocketChat.models.Users.findOneById this.userId - unless user - return this.ready() - - cursorHandle = RocketChat.models.Messages.findStarredByUserAtRoom(this.userId, rid, { sort: { ts: -1 }, limit: limit }).observeChanges - added: (_id, record) -> - publication.added('rocketchat_starred_message', _id, record) - - changed: (_id, record) -> - publication.changed('rocketchat_starred_message', _id, record) - - removed: (_id) -> - publication.removed('rocketchat_starred_message', _id) - - @ready() - @onStop -> - cursorHandle.stop() diff --git a/packages/rocketchat-message-star/server/publications/starredMessages.js b/packages/rocketchat-message-star/server/publications/starredMessages.js new file mode 100644 index 00000000000..15446bebeef --- /dev/null +++ b/packages/rocketchat-message-star/server/publications/starredMessages.js @@ -0,0 +1,30 @@ +Meteor.publish('starredMessages', function(rid, limit = 50) { + if (!this.userId) { + return this.ready(); + } + const publication = this; + const user = RocketChat.models.Users.findOneById(this.userId); + if (!user) { + return this.ready(); + } + const cursorHandle = RocketChat.models.Messages.findStarredByUserAtRoom(this.userId, rid, { + sort: { + ts: -1 + }, + limit + }).observeChanges({ + added(_id, record) { + return publication.added('rocketchat_starred_message', _id, record); + }, + changed(_id, record) { + return publication.changed('rocketchat_starred_message', _id, record); + }, + removed(_id) { + return publication.removed('rocketchat_starred_message', _id); + } + }); + this.ready(); + return this.onStop(function() { + return cursorHandle.stop(); + }); +}); diff --git a/packages/rocketchat-message-star/server/settings.coffee b/packages/rocketchat-message-star/server/settings.coffee deleted file mode 100644 index de6bb651c8f..00000000000 --- a/packages/rocketchat-message-star/server/settings.coffee +++ /dev/null @@ -1,2 +0,0 @@ -Meteor.startup -> - RocketChat.settings.add 'Message_AllowStarring', true, { type: 'boolean', group: 'Message', public: true } diff --git a/packages/rocketchat-message-star/server/settings.js b/packages/rocketchat-message-star/server/settings.js new file mode 100644 index 00000000000..4a8fa47af71 --- /dev/null +++ b/packages/rocketchat-message-star/server/settings.js @@ -0,0 +1,7 @@ +Meteor.startup(function() { + return RocketChat.settings.add('Message_AllowStarring', true, { + type: 'boolean', + group: 'Message', + 'public': true + }); +}); diff --git a/packages/rocketchat-message-star/server/starMessage.coffee b/packages/rocketchat-message-star/server/starMessage.coffee deleted file mode 100644 index 030fa908162..00000000000 --- a/packages/rocketchat-message-star/server/starMessage.coffee +++ /dev/null @@ -1,14 +0,0 @@ -Meteor.methods - starMessage: (message) -> - if not Meteor.userId() - throw new Meteor.Error('error-invalid-user', "Invalid user", { method: 'starMessage' }) - - if not RocketChat.settings.get 'Message_AllowStarring' - throw new Meteor.Error 'error-action-not-allowed', 'Message starring not allowed', { method: 'pinMessage', action: 'Message_starring' } - - room = RocketChat.models.Rooms.findOneById(message.rid) - - if Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) is -1 - return false - - RocketChat.models.Messages.updateUserStarById(message._id, Meteor.userId(), message.starred) diff --git a/packages/rocketchat-message-star/server/starMessage.js b/packages/rocketchat-message-star/server/starMessage.js new file mode 100644 index 00000000000..3ed7ee3bd8b --- /dev/null +++ b/packages/rocketchat-message-star/server/starMessage.js @@ -0,0 +1,21 @@ +Meteor.methods({ + starMessage(message) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'starMessage' + }); + } + if (!RocketChat.settings.get('Message_AllowStarring')) { + throw new Meteor.Error('error-action-not-allowed', 'Message starring not allowed', { + method: 'pinMessage', + action: 'Message_starring' + }); + } + const room = RocketChat.models.Rooms.findOneById(message.rid); + if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + return false; + } + return RocketChat.models.Messages.updateUserStarById(message._id, Meteor.userId(), message.starred); + } +}); + diff --git a/packages/rocketchat-message-star/server/startup/indexes.coffee b/packages/rocketchat-message-star/server/startup/indexes.coffee deleted file mode 100644 index 519eb19b596..00000000000 --- a/packages/rocketchat-message-star/server/startup/indexes.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Meteor.startup -> - Meteor.defer -> - RocketChat.models.Messages.tryEnsureIndex { 'starred._id': 1 }, { sparse: 1 } diff --git a/packages/rocketchat-message-star/server/startup/indexes.js b/packages/rocketchat-message-star/server/startup/indexes.js new file mode 100644 index 00000000000..f1946d6f4eb --- /dev/null +++ b/packages/rocketchat-message-star/server/startup/indexes.js @@ -0,0 +1,9 @@ +Meteor.startup(function() { + return Meteor.defer(function() { + return RocketChat.models.Messages.tryEnsureIndex({ + 'starred._id': 1 + }, { + sparse: 1 + }); + }); +}); diff --git a/packages/rocketchat-oembed/client/baseWidget.coffee b/packages/rocketchat-oembed/client/baseWidget.coffee deleted file mode 100644 index 09a090712d9..00000000000 --- a/packages/rocketchat-oembed/client/baseWidget.coffee +++ /dev/null @@ -1,21 +0,0 @@ -Template.oembedBaseWidget.helpers - template: -> - if this._overrideTemplate - return this._overrideTemplate - - if this.headers?.contentType?.match(/image\/.*/)? - return 'oembedImageWidget' - - if this.headers?.contentType?.match(/audio\/.*/)? - return 'oembedAudioWidget' - - if this.headers?.contentType?.match(/video\/.*/)? or this.meta?.twitterPlayerStreamContentType?.match(/video\/.*/)? - return 'oembedVideoWidget' - - if this.meta?.oembedHtml? - return 'oembedFrameWidget' - - if this.meta?.sandstorm?.grain? - return 'oembedSandstormGrain' - - return 'oembedUrlWidget' diff --git a/packages/rocketchat-oembed/client/baseWidget.js b/packages/rocketchat-oembed/client/baseWidget.js new file mode 100644 index 00000000000..1b8c95278b2 --- /dev/null +++ b/packages/rocketchat-oembed/client/baseWidget.js @@ -0,0 +1,28 @@ +Template.oembedBaseWidget.helpers({ + template() { + let contentType; + if (this.headers) { + contentType = this.headers.contentType; + } + + if (this._overrideTemplate) { + return this._overrideTemplate; + } + if (this.headers && contentType && contentType.match(/image\/.*/)) { + return 'oembedImageWidget'; + } + if (this.headers && contentType && contentType.match(/audio\/.*/)) { + return 'oembedAudioWidget'; + } + if ((this.headers && contentType && contentType.match(/video\/.*/)) || (this.meta && this.meta.twitterPlayerStreamContentType && this.meta.twitterPlayerStreamContentType.match(/video\/.*/))) { + return 'oembedVideoWidget'; + } + if (this.meta && this.meta.oembedHtml) { + return 'oembedFrameWidget'; + } + if (this.meta && this.meta.sandstorm && this.meta.sandstorm.grain) { + return 'oembedSandstormGrain'; + } + return 'oembedUrlWidget'; + } +}); diff --git a/packages/rocketchat-oembed/client/oembedAudioWidget.coffee b/packages/rocketchat-oembed/client/oembedAudioWidget.coffee deleted file mode 100644 index 03756dc6da5..00000000000 --- a/packages/rocketchat-oembed/client/oembedAudioWidget.coffee +++ /dev/null @@ -1,7 +0,0 @@ -Template.oembedAudioWidget.helpers - - collapsed: -> - if this.collapsed? - return this.collapsed - else - return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true diff --git a/packages/rocketchat-oembed/client/oembedAudioWidget.js b/packages/rocketchat-oembed/client/oembedAudioWidget.js new file mode 100644 index 00000000000..23bc9a4d7c1 --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedAudioWidget.js @@ -0,0 +1,10 @@ +Template.oembedAudioWidget.helpers({ + collapsed() { + if (this.collapsed) { + return this.collapsed; + } else { + const user = Meteor.user(); + return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true; + } + } +}); diff --git a/packages/rocketchat-oembed/client/oembedFrameWidget.coffee b/packages/rocketchat-oembed/client/oembedFrameWidget.coffee deleted file mode 100644 index d589454c1e8..00000000000 --- a/packages/rocketchat-oembed/client/oembedFrameWidget.coffee +++ /dev/null @@ -1,7 +0,0 @@ -Template.oembedFrameWidget.helpers - - collapsed: -> - if this.collapsed? - return this.collapsed - else - return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true diff --git a/packages/rocketchat-oembed/client/oembedFrameWidget.js b/packages/rocketchat-oembed/client/oembedFrameWidget.js new file mode 100644 index 00000000000..e47f62edca6 --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedFrameWidget.js @@ -0,0 +1,10 @@ +Template.oembedFrameWidget.helpers({ + collapsed() { + if (this.collapsed) { + return this.collapsed; + } else { + const user = Meteor.user(); + return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true; + } + } +}); diff --git a/packages/rocketchat-oembed/client/oembedImageWidget.coffee b/packages/rocketchat-oembed/client/oembedImageWidget.coffee deleted file mode 100644 index 79444211117..00000000000 --- a/packages/rocketchat-oembed/client/oembedImageWidget.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Template.oembedImageWidget.helpers - loadImage: -> - - if Meteor.user()?.settings?.preferences?.autoImageLoad is false and this.downloadImages? is not true - return false - - if Meteor.Device.isPhone() and Meteor.user()?.settings?.preferences?.saveMobileBandwidth and this.downloadImages? is not true - return false - - return true - - - collapsed: -> - if this.collapsed? - return this.collapsed - else - return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true diff --git a/packages/rocketchat-oembed/client/oembedImageWidget.js b/packages/rocketchat-oembed/client/oembedImageWidget.js new file mode 100644 index 00000000000..82c71ae1f41 --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedImageWidget.js @@ -0,0 +1,21 @@ +Template.oembedImageWidget.helpers({ + loadImage() { + const user = Meteor.user(); + + if (user && user.settings && user.settings.preferences && user.settings.preferences.autoImageLoad === false && this.downloadImages == null) { + return false; + } + if (Meteor.Device.isPhone() && user && user.settings && user.settings.preferences && user.settings.preferences.saveMobileBandwidth && this.downloadImages == null) { + return false; + } + return true; + }, + collapsed() { + if (this.collapsed != null) { + return this.collapsed; + } else { + const user = Meteor.user(); + return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true; + } + } +}); diff --git a/packages/rocketchat-oembed/client/oembedSandstormGrain.coffee b/packages/rocketchat-oembed/client/oembedSandstormGrain.coffee deleted file mode 100644 index fd78824a49b..00000000000 --- a/packages/rocketchat-oembed/client/oembedSandstormGrain.coffee +++ /dev/null @@ -1,17 +0,0 @@ -Template.oembedSandstormGrain.helpers - token: -> - return @meta.sandstorm.grain.token - appTitle: -> - return @meta.sandstorm.grain.appTitle.defaultText - grainTitle: -> - return @meta.sandstorm.grain.grainTitle - appIconUrl: -> - return @meta.sandstorm.grain.appIconUrl - descriptor: -> - return @meta.sandstorm.grain.descriptor -window.sandstormOembed = (e) -> - e = e or window.event - src = e.target or e.srcElement - token = src.getAttribute "data-token" - descriptor = src.getAttribute "data-descriptor" - Meteor.call "sandstormOffer", token, descriptor diff --git a/packages/rocketchat-oembed/client/oembedSandstormGrain.js b/packages/rocketchat-oembed/client/oembedSandstormGrain.js new file mode 100644 index 00000000000..0b1fbd548fd --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedSandstormGrain.js @@ -0,0 +1,25 @@ +Template.oembedSandstormGrain.helpers({ + token() { + return this.meta.sandstorm.grain.token; + }, + appTitle() { + return this.meta.sandstorm.grain.appTitle.defaultText; + }, + grainTitle() { + return this.meta.sandstorm.grain.grainTitle; + }, + appIconUrl() { + return this.meta.sandstorm.grain.appIconUrl; + }, + descriptor() { + return this.meta.sandstorm.grain.descriptor; + } +}); + +window.sandstormOembed = function(e) { + e = e || window.event; + const src = e.target || e.srcElement; + const token = src.getAttribute('data-token'); + const descriptor = src.getAttribute('data-descriptor'); + return Meteor.call('sandstormOffer', token, descriptor); +}; diff --git a/packages/rocketchat-oembed/client/oembedUrlWidget.coffee b/packages/rocketchat-oembed/client/oembedUrlWidget.coffee deleted file mode 100644 index 5b160cfb235..00000000000 --- a/packages/rocketchat-oembed/client/oembedUrlWidget.coffee +++ /dev/null @@ -1,57 +0,0 @@ -getTitle = (self) -> - if not self.meta? - return - - return self.meta.ogTitle or self.meta.twitterTitle or self.meta.title or self.meta.pageTitle - -getDescription = (self) -> - if not self.meta? - return - - description = self.meta.ogDescription or self.meta.twitterDescription or self.meta.description - if not description? - return - - return _.unescape description.replace /(^[“\s]*)|([”\s]*$)/g, '' - - -Template.oembedUrlWidget.helpers - description: -> - description = getDescription this - return Blaze._escape(description) if _.isString description - - title: -> - title = getTitle this - return Blaze._escape(title) if _.isString title - - target: -> - if not this.parsedUrl?.host || !document?.location?.host || this.parsedUrl.host isnt document.location.host - return '_blank' - - image: -> - if not this.meta? - return - - decodedOgImage = @meta.ogImage?.replace?(/&/g, '&') - - url = this.meta.msapplicationTileImage or decodedOgImage or this.meta.twitterImage - - 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 - - show: -> - return getDescription(this)? or getTitle(this)? - - collapsed: -> - if this.collapsed? - return this.collapsed - else - return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true diff --git a/packages/rocketchat-oembed/client/oembedUrlWidget.js b/packages/rocketchat-oembed/client/oembedUrlWidget.js new file mode 100644 index 00000000000..110f0c96e70 --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedUrlWidget.js @@ -0,0 +1,67 @@ +const getTitle = function(self) { + if (self.meta == null) { + return; + } + return self.meta.ogTitle || self.meta.twitterTitle || self.meta.title || self.meta.pageTitle; +}; + +const getDescription = function(self) { + if (self.meta == null) { + return; + } + const description = self.meta.ogDescription || self.meta.twitterDescription || self.meta.description; + if (description == null) { + return; + } + return _.unescape(description.replace(/(^[“\s]*)|([”\s]*$)/g, '')); +}; + +Template.oembedUrlWidget.helpers({ + description() { + const description = getDescription(this); + if (_.isString(description)) { + return Blaze._escape(description); + } + }, + title() { + const title = getTitle(this); + if (_.isString(title)) { + return Blaze._escape(title); + } + }, + target() { + if (!(this.parsedUrl && this.parsedUrl.host) || !(document && document.location && document.location.host) || (this.parsedUrl && this.parsedUrl.host !== document.location.host)) { + return '_blank'; + } + }, + image() { + if (this.meta == null) { + return; + } + let decodedOgImage; + if (this.meta.ogImage && this.meta.ogImage.replace) { + decodedOgImage = this.meta.ogImage.replace(/&/g, '&'); + } + let url = this.meta.msapplicationTileImage || decodedOgImage || this.meta.twitterImage; + if (url == null) { + return; + } + if (url.indexOf('//') === 0) { + url = `${ this.parsedUrl.protocol }${ url }`; + } else if (url.indexOf('/') === 0 && (this.parsedUrl && this.parsedUrl.host)) { + url = `${ this.parsedUrl.protocol }//${ this.parsedUrl.host }${ url }`; + } + return url; + }, + show() { + return (getDescription(this) != null) || (getTitle(this) != null); + }, + collapsed() { + if (this.collapsed != null) { + return this.collapsed; + } else { + const user = Meteor.user(); + return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true; + } + } +}); diff --git a/packages/rocketchat-oembed/client/oembedVideoWidget.coffee b/packages/rocketchat-oembed/client/oembedVideoWidget.coffee deleted file mode 100644 index 399e57e57de..00000000000 --- a/packages/rocketchat-oembed/client/oembedVideoWidget.coffee +++ /dev/null @@ -1,22 +0,0 @@ -getTitle = (self) -> - if not self.meta? - return - - return self.meta.ogTitle or self.meta.twitterTitle or self.meta.title or self.meta.pageTitle - - -Template.oembedVideoWidget.helpers - url: -> - return @meta?.twitterPlayerStream or @url - - contentType: -> - return @meta?.twitterPlayerStreamContentType or @headers?.contentType - - title: -> - return getTitle @ - - collapsed: -> - if this.collapsed? - return this.collapsed - else - return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true diff --git a/packages/rocketchat-oembed/client/oembedVideoWidget.js b/packages/rocketchat-oembed/client/oembedVideoWidget.js new file mode 100644 index 00000000000..a825ab5c976 --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedVideoWidget.js @@ -0,0 +1,35 @@ +const getTitle = function(self) { + if (self.meta == null) { + return; + } + return self.meta.ogTitle || self.meta.twitterTitle || self.meta.title || self.meta.pageTitle; +}; + +Template.oembedVideoWidget.helpers({ + url() { + if (this.meta && this.meta.twitterPlayerStream) { + return this.meta.twitterPlayerStream; + } else if (this.url) { + return this.url; + } + }, + contentType() { + if (this.meta && this.meta.twitterPlayerStreamContentType) { + return this.meta.twitterPlayerStreamContentType; + } else if (this.headers && this.headers.contentType) { + return this.headers.contentType; + } + }, + title() { + return getTitle(this); + }, + collapsed() { + if (this.collapsed) { + return this.collapsed; + } else { + const user = Meteor.user(); + return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true; + } + } + +}); diff --git a/packages/rocketchat-oembed/client/oembedYoutubeWidget.coffee b/packages/rocketchat-oembed/client/oembedYoutubeWidget.coffee deleted file mode 100644 index c848a17f455..00000000000 --- a/packages/rocketchat-oembed/client/oembedYoutubeWidget.coffee +++ /dev/null @@ -1,7 +0,0 @@ -Template.oembedYoutubeWidget.helpers - - collapsed: -> - if this.collapsed? - return this.collapsed - else - return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true diff --git a/packages/rocketchat-oembed/client/oembedYoutubeWidget.js b/packages/rocketchat-oembed/client/oembedYoutubeWidget.js new file mode 100644 index 00000000000..0e2ce33d4e4 --- /dev/null +++ b/packages/rocketchat-oembed/client/oembedYoutubeWidget.js @@ -0,0 +1,10 @@ +Template.oembedYoutubeWidget.helpers({ + collapsed() { + if (this.collapsed) { + return this.collapsed; + } else { + const user = Meteor.user(); + return user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault === true; + } + } +}); diff --git a/packages/rocketchat-oembed/package.js b/packages/rocketchat-oembed/package.js index 093b324f689..9f776d12a20 100644 --- a/packages/rocketchat-oembed/package.js +++ b/packages/rocketchat-oembed/package.js @@ -17,40 +17,39 @@ Package.onUse(function(api) { 'http', 'templating', 'ecmascript', - 'coffeescript', 'underscore', 'konecty:change-case', 'rocketchat:lib' ]); api.addFiles('client/baseWidget.html', 'client'); - api.addFiles('client/baseWidget.coffee', 'client'); + api.addFiles('client/baseWidget.js', 'client'); api.addFiles('client/oembedImageWidget.html', 'client'); - api.addFiles('client/oembedImageWidget.coffee', 'client'); + api.addFiles('client/oembedImageWidget.js', 'client'); api.addFiles('client/oembedAudioWidget.html', 'client'); - api.addFiles('client/oembedAudioWidget.coffee', 'client'); + api.addFiles('client/oembedAudioWidget.js', 'client'); api.addFiles('client/oembedVideoWidget.html', 'client'); - api.addFiles('client/oembedVideoWidget.coffee', 'client'); + api.addFiles('client/oembedVideoWidget.js', 'client'); api.addFiles('client/oembedYoutubeWidget.html', 'client'); - api.addFiles('client/oembedYoutubeWidget.coffee', 'client'); + api.addFiles('client/oembedYoutubeWidget.js', 'client'); api.addFiles('client/oembedUrlWidget.html', 'client'); - api.addFiles('client/oembedUrlWidget.coffee', 'client'); + api.addFiles('client/oembedUrlWidget.js', 'client'); api.addFiles('client/oembedFrameWidget.html', 'client'); - api.addFiles('client/oembedFrameWidget.coffee', 'client'); + api.addFiles('client/oembedFrameWidget.js', 'client'); api.addFiles('client/oembedSandstormGrain.html', 'client'); - api.addFiles('client/oembedSandstormGrain.coffee', 'client'); + api.addFiles('client/oembedSandstormGrain.js', 'client'); - api.addFiles('server/server.coffee', 'server'); - api.addFiles('server/providers.coffee', 'server'); + api.addFiles('server/server.js', 'server'); + api.addFiles('server/providers.js', 'server'); api.addFiles('server/jumpToMessage.js', 'server'); - api.addFiles('server/models/OEmbedCache.coffee', 'server'); + api.addFiles('server/models/OEmbedCache.js', 'server'); api.export('OEmbed', 'server'); }); diff --git a/packages/rocketchat-oembed/server/jumpToMessage.js b/packages/rocketchat-oembed/server/jumpToMessage.js index 0f674e461bf..2cb673cb3a9 100644 --- a/packages/rocketchat-oembed/server/jumpToMessage.js +++ b/packages/rocketchat-oembed/server/jumpToMessage.js @@ -17,7 +17,7 @@ RocketChat.callbacks.add('beforeSaveMessage', (msg) => { msg.attachments.push({ 'text' : jumpToMessage.msg, 'translations': jumpToMessage.translations, - 'author_name' : jumpToMessage.u.username, + 'author_name' : jumpToMessage.alias || jumpToMessage.u.username, 'author_icon' : getAvatarUrlFromUsername(jumpToMessage.u.username), 'message_link' : item.url, 'attachments' : jumpToMessage.attachments || [], diff --git a/packages/rocketchat-oembed/server/models/OEmbedCache.coffee b/packages/rocketchat-oembed/server/models/OEmbedCache.coffee deleted file mode 100644 index cdf8fbfbf27..00000000000 --- a/packages/rocketchat-oembed/server/models/OEmbedCache.coffee +++ /dev/null @@ -1,30 +0,0 @@ -RocketChat.models.OEmbedCache = new class extends RocketChat.models._Base - constructor: -> - super('oembed_cache') - @tryEnsureIndex { 'updatedAt': 1 } - - - # FIND ONE - findOneById: (_id, options) -> - query = - _id: _id - - return @findOne query, options - - - # INSERT - createWithIdAndData: (_id, data) -> - record = - _id: _id - data: data - updatedAt: new Date - - record._id = @insert record - return record - - # REMOVE - removeAfterDate: (date) -> - query = - updatedAt: - $lte: date - @remove query diff --git a/packages/rocketchat-oembed/server/models/OEmbedCache.js b/packages/rocketchat-oembed/server/models/OEmbedCache.js new file mode 100644 index 00000000000..f0b451247f9 --- /dev/null +++ b/packages/rocketchat-oembed/server/models/OEmbedCache.js @@ -0,0 +1,38 @@ + +RocketChat.models.OEmbedCache = new class extends RocketChat.models._Base { + constructor() { + super('oembed_cache'); + this.tryEnsureIndex({ 'updatedAt': 1 }); + } + + //FIND ONE + findOneById(_id, options) { + const query = { + _id + }; + return this.findOne(query, options); + } + + //INSERT + createWithIdAndData(_id, data) { + const record = { + _id, + data, + updatedAt: new Date + }; + record._id = this.insert(record); + return record; + } + + //REMOVE + removeAfterDate(date) { + const query = { + updatedAt: { + $lte: date + } + }; + return this.remove(query); + } +}; + + diff --git a/packages/rocketchat-oembed/server/providers.coffee b/packages/rocketchat-oembed/server/providers.coffee deleted file mode 100644 index 2a4b3002e1b..00000000000 --- a/packages/rocketchat-oembed/server/providers.coffee +++ /dev/null @@ -1,84 +0,0 @@ -URL = Npm.require('url') -QueryString = Npm.require('querystring') - -class Providers - providers: [] - - @getConsumerUrl: (provider, url) -> - urlObj = URL.parse provider.endPoint, true - urlObj.query['url'] = url - delete urlObj.search - return URL.format urlObj - - registerProvider: (provider) -> - this.providers.push(provider) - - getProviders: () -> - return this.providers - - getProviderForUrl: (url) -> - return _.find this.providers, (provider) -> - candidate = _.find provider.urls, (re) -> - return re.test url - return candidate? - -providers = new Providers() -providers.registerProvider - urls: [new RegExp('https?://soundcloud.com/\\S+')] - endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150' -providers.registerProvider - urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')] - endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200' -providers.registerProvider - urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')] - endPoint: 'https://www.youtube.com/oembed?maxheight=200' -providers.registerProvider - urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')] - endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150' -providers.registerProvider - urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')] - endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200' -providers.registerProvider - urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')] - endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200' - -RocketChat.oembed = {} -RocketChat.oembed.providers = providers - -RocketChat.callbacks.add 'oembed:beforeGetUrlContent', (data) -> - if data.parsedUrl? - url = URL.format data.parsedUrl - provider = providers.getProviderForUrl url - if provider? - consumerUrl = Providers.getConsumerUrl provider, url - consumerUrl = URL.parse consumerUrl, true - _.extend data.parsedUrl, consumerUrl - data.urlObj.port = consumerUrl.port - data.urlObj.hostname = consumerUrl.hostname - data.urlObj.pathname = consumerUrl.pathname - data.urlObj.query = consumerUrl.query - delete data.urlObj.search - delete data.urlObj.host - - return data -, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before' - -RocketChat.callbacks.add 'oembed:afterParseContent', (data) -> - if data.parsedUrl?.query? - queryString = data.parsedUrl.query - if _.isString data.parsedUrl.query - queryString = QueryString.parse data.parsedUrl.query - if queryString.url? - url = queryString.url - provider = providers.getProviderForUrl url - if provider? - if data.content?.body? - try - metas = JSON.parse data.content.body; - _.each metas, (value, key) -> - if _.isString value - data.meta[changeCase.camelCase('oembed_' + key)] = value - data.meta['oembedUrl'] = url - - return data -, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-after' diff --git a/packages/rocketchat-oembed/server/providers.js b/packages/rocketchat-oembed/server/providers.js new file mode 100644 index 00000000000..4d5c59ffce9 --- /dev/null +++ b/packages/rocketchat-oembed/server/providers.js @@ -0,0 +1,120 @@ +/*globals changeCase */ + + +const URL = Npm.require('url'); + +const QueryString = Npm.require('querystring'); + +class Providers { + constructor() { + this.providers = []; + } + + static getConsumerUrl(provider, url) { + const urlObj = URL.parse(provider.endPoint, true); + urlObj.query['url'] = url; + delete urlObj.search; + return URL.format(urlObj); + } + + registerProvider(provider) { + return this.providers.push(provider); + } + + getProviders() { + return this.providers; + } + + getProviderForUrl(url) { + return _.find(this.providers, function(provider) { + const candidate = _.find(provider.urls, function(re) { + return re.test(url); + }); + return candidate != null; + }); + } +} + +const providers = new Providers(); + +providers.registerProvider({ + urls: [new RegExp('https?://soundcloud.com/\\S+')], + endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150' +}); + +providers.registerProvider({ + urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')], + endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200' +}); + +providers.registerProvider({ + urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')], + endPoint: 'https://www.youtube.com/oembed?maxheight=200' +}); + +providers.registerProvider({ + urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')], + endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150' +}); + +providers.registerProvider({ + urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')], + endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200' +}); + +providers.registerProvider({ + urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')], + endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200' +}); + +RocketChat.oembed = {}; + +RocketChat.oembed.providers = providers; + +RocketChat.callbacks.add('oembed:beforeGetUrlContent', function(data) { + if (data.parsedUrl != null) { + const url = URL.format(data.parsedUrl); + const provider = providers.getProviderForUrl(url); + if (provider != null) { + let consumerUrl = Providers.getConsumerUrl(provider, url); + consumerUrl = URL.parse(consumerUrl, true); + _.extend(data.parsedUrl, consumerUrl); + data.urlObj.port = consumerUrl.port; + data.urlObj.hostname = consumerUrl.hostname; + data.urlObj.pathname = consumerUrl.pathname; + data.urlObj.query = consumerUrl.query; + delete data.urlObj.search; + delete data.urlObj.host; + } + } + return data; +}, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before'); + +RocketChat.callbacks.add('oembed:afterParseContent', function(data) { + if (data.parsedUrl && data.parsedUrl.query) { + let queryString = data.parsedUrl.query; + if (_.isString(data.parsedUrl.query)) { + queryString = QueryString.parse(data.parsedUrl.query); + } + if (queryString.url != null) { + const url = queryString.url; + const provider = providers.getProviderForUrl(url); + if (provider != null) { + if (data.content && data.content.body) { + try { + const metas = JSON.parse(data.content.body); + _.each(metas, function(value, key) { + if (_.isString(value)) { + return data.meta[changeCase.camelCase(`oembed_${ key }`)] = value; + } + }); + data.meta['oembedUrl'] = url; + } catch (error) { + console.log(error); + } + } + } + } + } + return data; +}, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-after'); diff --git a/packages/rocketchat-oembed/server/server.coffee b/packages/rocketchat-oembed/server/server.coffee deleted file mode 100644 index 63ab940c3d5..00000000000 --- a/packages/rocketchat-oembed/server/server.coffee +++ /dev/null @@ -1,256 +0,0 @@ -URL = Npm.require('url') -querystring = Npm.require('querystring') -request = HTTPInternals.NpmModules.request.module -iconv = Npm.require('iconv-lite') -ipRangeCheck = Npm.require('ip-range-check') -he = Npm.require('he') -jschardet = Npm.require('jschardet') - -OEmbed = {} - -# Detect encoding -# Priority: -# Detected == HTTP Header > Detected == HTML meta > HTTP Header > HTML meta > Detected > Default (utf-8) -# See also: https://www.w3.org/International/questions/qa-html-encoding-declarations.en#quickanswer -getCharset = (contentType, body) -> - contentType = contentType || '' - binary = body.toString('binary') - - detected = jschardet.detect(binary) - if detected.confidence > 0.8 - detectedCharset = detected.encoding.toLowerCase() - - m1 = contentType.match(/charset=([\w\-]+)/i) - if m1 - httpHeaderCharset = m1[1].toLowerCase() - - m2 = binary.match(/]*charset=["']?([\w\-]+)/i) - if m2 - htmlMetaCharset = m2[1].toLowerCase() - - if detectedCharset - if detectedCharset == httpHeaderCharset - result = httpHeaderCharset - else if detectedCharset == htmlMetaCharset - result = htmlMetaCharset - - unless result - result = httpHeaderCharset || htmlMetaCharset || detectedCharset - - return result || 'utf-8' - -toUtf8 = (contentType, body) -> - return iconv.decode(body, getCharset(contentType, body)) - -getUrlContent = (urlObj, redirectCount = 5, callback) -> - if _.isString(urlObj) - urlObj = URL.parse urlObj - - parsedUrl = _.pick urlObj, ['host', 'hash', 'pathname', 'protocol', 'port', 'query', 'search', 'hostname'] - - ignoredHosts = RocketChat.settings.get('API_EmbedIgnoredHosts').replace(/\s/g, '').split(',') or [] - if parsedUrl.hostname in ignoredHosts or ipRangeCheck(parsedUrl.hostname, ignoredHosts) - return callback() - - safePorts = RocketChat.settings.get('API_EmbedSafePorts').replace(/\s/g, '').split(',') or [] - if parsedUrl.port and safePorts.length > 0 and parsedUrl.port not in safePorts - return callback() - - data = RocketChat.callbacks.run 'oembed:beforeGetUrlContent', - urlObj: urlObj - parsedUrl: parsedUrl - - if data.attachments? - return callback null, data - - url = URL.format data.urlObj - opts = - url: url - strictSSL: !RocketChat.settings.get 'Allow_Invalid_SelfSigned_Certs' - gzip: true - maxRedirects: redirectCount - headers: - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36' - - headers = null - statusCode = null - error = null - chunks = [] - chunksTotalLength = 0 - - stream = request opts - stream.on 'response', (response) -> - statusCode = response.statusCode - headers = response.headers - if response.statusCode isnt 200 - return stream.abort() - - stream.on 'data', (chunk) -> - chunks.push chunk - chunksTotalLength += chunk.length - if chunksTotalLength > 250000 - stream.abort() - - stream.on 'end', Meteor.bindEnvironment -> - if error? - return callback null, { - error: error - parsedUrl: parsedUrl - } - - buffer = Buffer.concat(chunks) - - callback null, { - headers: headers - body: toUtf8(headers['content-type'], buffer) - parsedUrl: parsedUrl - statusCode: statusCode - } - - stream.on 'error', (err) -> - error = err - -OEmbed.getUrlMeta = (url, withFragment) -> - getUrlContentSync = Meteor.wrapAsync getUrlContent - - urlObj = URL.parse url - - if withFragment? - queryStringObj = querystring.parse urlObj.query - queryStringObj._escaped_fragment_ = '' - urlObj.query = querystring.stringify queryStringObj - - path = urlObj.pathname - if urlObj.query? - path += '?' + urlObj.query - - urlObj.path = path - - content = getUrlContentSync urlObj, 5 - if !content - return - - if content.attachments? - return content - - metas = undefined - - if content?.body? - metas = {} - content.body.replace /]*>([^<]*)<\/title>/gmi, (meta, title) -> - metas.pageTitle ?= he.unescape title - - content.body.replace /]*(?:name|property)=[']([^']*)['][^>]*\scontent=[']([^']*)['][^>]*>/gmi, (meta, name, value) -> - metas[changeCase.camelCase(name)] ?= he.unescape value - - content.body.replace /]*(?:name|property)=["]([^"]*)["][^>]*\scontent=["]([^"]*)["][^>]*>/gmi, (meta, name, value) -> - metas[changeCase.camelCase(name)] ?= he.unescape value - - content.body.replace /]*\scontent=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, (meta, value, name) -> - metas[changeCase.camelCase(name)] ?= he.unescape value - - content.body.replace /]*\scontent=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, (meta, value, name) -> - metas[changeCase.camelCase(name)] ?= he.unescape value - - - if metas.fragment is '!' and not withFragment? - return OEmbed.getUrlMeta url, true - - headers = undefined - - if content?.headers? - headers = {} - for header, value of content.headers - headers[changeCase.camelCase(header)] = value - - if content?.statusCode isnt 200 - return data - - data = RocketChat.callbacks.run 'oembed:afterParseContent', - meta: metas - headers: headers - parsedUrl: content.parsedUrl - content: content - - return data - -OEmbed.getUrlMetaWithCache = (url, withFragment) -> - cache = RocketChat.models.OEmbedCache.findOneById url - if cache? - return cache.data - - data = OEmbed.getUrlMeta url, withFragment - - if data? - try - RocketChat.models.OEmbedCache.createWithIdAndData url, data - catch e - console.error 'OEmbed duplicated record', url - - return data - - return - -getRelevantHeaders = (headersObj) -> - headers = {} - for key, value of headersObj - if key.toLowerCase() in ['contenttype', 'contentlength'] and value?.trim() isnt '' - headers[key] = value - - if Object.keys(headers).length > 0 - return headers - return - -getRelevantMetaTags = (metaObj) -> - tags = {} - for key, value of metaObj - if /^(og|fb|twitter|oembed|msapplication).+|description|title|pageTitle$/.test(key.toLowerCase()) and value?.trim() isnt '' - tags[key] = value - - if Object.keys(tags).length > 0 - return tags - return - -OEmbed.rocketUrlParser = (message) -> - if Array.isArray message.urls - attachments = [] - changed = false - message.urls.forEach (item) -> - if item.ignoreParse is true then return - if item.url.startsWith "grain://" - changed = true - item.meta = - sandstorm: - grain: item.sandstormViewInfo - return - - if not /^https?:\/\//i.test item.url then return - - data = OEmbed.getUrlMetaWithCache item.url - - if data? - if data.attachments - attachments = _.union attachments, data.attachments - else - if data.meta? - item.meta = getRelevantMetaTags data.meta - - if data.headers? - item.headers = getRelevantHeaders data.headers - - item.parsedUrl = data.parsedUrl - changed = true - - if attachments.length - RocketChat.models.Messages.setMessageAttachments message._id, attachments - - if changed is true - RocketChat.models.Messages.setUrlsById message._id, message.urls - - return message - -RocketChat.settings.get 'API_Embed', (key, value) -> - if value - RocketChat.callbacks.add 'afterSaveMessage', OEmbed.rocketUrlParser, RocketChat.callbacks.priority.LOW, 'API_Embed' - else - RocketChat.callbacks.remove 'afterSaveMessage', 'API_Embed' diff --git a/packages/rocketchat-oembed/server/server.js b/packages/rocketchat-oembed/server/server.js new file mode 100644 index 00000000000..36085f1597f --- /dev/null +++ b/packages/rocketchat-oembed/server/server.js @@ -0,0 +1,300 @@ +/*globals HTTPInternals, changeCase */ +const URL = Npm.require('url'); + +const querystring = Npm.require('querystring'); + +const request = HTTPInternals.NpmModules.request.module; + +const iconv = Npm.require('iconv-lite'); + +const ipRangeCheck = Npm.require('ip-range-check'); + +const he = Npm.require('he'); + +const jschardet = Npm.require('jschardet'); + +const OEmbed = {}; + +// Detect encoding +// Priority: +// Detected == HTTP Header > Detected == HTML meta > HTTP Header > HTML meta > Detected > Default (utf-8) +// See also: https://www.w3.org/International/questions/qa-html-encoding-declarations.en#quickanswer +const getCharset = function(contentType, body) { + let detectedCharset; + let httpHeaderCharset; + let htmlMetaCharset; + let result; + + contentType = contentType || ''; + + const binary = body.toString('binary'); + const detected = jschardet.detect(binary); + if (detected.confidence > 0.8) { + detectedCharset = detected.encoding.toLowerCase(); + } + const m1 = contentType.match(/charset=([\w\-]+)/i); + if (m1) { + httpHeaderCharset = m1[1].toLowerCase(); + } + const m2 = binary.match(/]*charset=["']?([\w\-]+)/i); + if (m2) { + htmlMetaCharset = m2[1].toLowerCase(); + } + if (detectedCharset) { + if (detectedCharset === httpHeaderCharset) { + result = httpHeaderCharset; + } else if (detectedCharset === htmlMetaCharset) { + result = htmlMetaCharset; + } + } + if (!result) { + result = httpHeaderCharset || htmlMetaCharset || detectedCharset; + } + return result || 'utf-8'; +}; + +const toUtf8 = function(contentType, body) { + return iconv.decode(body, getCharset(contentType, body)); +}; + +const getUrlContent = function(urlObj, redirectCount = 5, callback) { + + if (_.isString(urlObj)) { + urlObj = URL.parse(urlObj); + } + + const parsedUrl = _.pick(urlObj, ['host', 'hash', 'pathname', 'protocol', 'port', 'query', 'search', 'hostname']); + const ignoredHosts = RocketChat.settings.get('API_EmbedIgnoredHosts').replace(/\s/g, '').split(',') || []; + if (ignoredHosts.includes(parsedUrl.hostname) || ipRangeCheck(parsedUrl.hostname, ignoredHosts)) { + return callback(); + } + + const safePorts = RocketChat.settings.get('API_EmbedSafePorts').replace(/\s/g, '').split(',') || []; + if (parsedUrl.port && safePorts.length > 0 && (!safePorts.includes(parsedUrl.port))) { + return callback(); + } + + const data = RocketChat.callbacks.run('oembed:beforeGetUrlContent', { + urlObj, + parsedUrl + }); + if (data.attachments != null) { + return callback(null, data); + } + const url = URL.format(data.urlObj); + const opts = { + url, + strictSSL: !RocketChat.settings.get('Allow_Invalid_SelfSigned_Certs'), + gzip: true, + maxRedirects: redirectCount, + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36' + } + }; + let headers = null; + let statusCode = null; + let error = null; + const chunks = []; + let chunksTotalLength = 0; + const stream = request(opts); + stream.on('response', function(response) { + statusCode = response.statusCode; + headers = response.headers; + if (response.statusCode !== 200) { + return stream.abort(); + } + }); + stream.on('data', function(chunk) { + chunks.push(chunk); + chunksTotalLength += chunk.length; + if (chunksTotalLength > 250000) { + return stream.abort(); + } + }); + stream.on('end', Meteor.bindEnvironment(function() { + if (error != null) { + return callback(null, { + error, + parsedUrl + }); + } + const buffer = Buffer.concat(chunks); + return callback(null, { + headers, + body: toUtf8(headers['content-type'], buffer), + parsedUrl, + statusCode + }); + })); + return stream.on('error', function(err) { + return error = err; + }); +}; + +OEmbed.getUrlMeta = function(url, withFragment) { + const getUrlContentSync = Meteor.wrapAsync(getUrlContent); + const urlObj = URL.parse(url); + if (withFragment != null) { + const queryStringObj = querystring.parse(urlObj.query); + queryStringObj._escaped_fragment_ = ''; + urlObj.query = querystring.stringify(queryStringObj); + let path = urlObj.pathname; + if (urlObj.query != null) { + path += `?${ urlObj.query }`; + } + urlObj.path = path; + } + const content = getUrlContentSync(urlObj, 5); + if (!content) { + return; + } + if (content.attachments != null) { + return content; + } + let metas = undefined; + if (content && content.body) { + metas = {}; + content.body.replace(/]*>([^<]*)<\/title>/gmi, function(meta, title) { + return metas.pageTitle != null ? metas.pageTitle : metas.pageTitle = he.unescape(title); + }); + content.body.replace(/]*(?:name|property)=[']([^']*)['][^>]*\scontent=[']([^']*)['][^>]*>/gmi, function(meta, name, value) { + let name1; + return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value); + }); + content.body.replace(/]*(?:name|property)=["]([^"]*)["][^>]*\scontent=["]([^"]*)["][^>]*>/gmi, function(meta, name, value) { + let name1; + return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value); + }); + content.body.replace(/]*\scontent=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, function(meta, value, name) { + let name1; + return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value); + }); + content.body.replace(/]*\scontent=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, function(meta, value, name) { + let name1; + return metas[name1 = changeCase.camelCase(name)] != null ? metas[name1] : metas[name1] = he.unescape(value); + }); + if (metas.fragment === '!' && (withFragment == null)) { + return OEmbed.getUrlMeta(url, true); + } + } + let headers = undefined; + let data = undefined; + + + if (content && content.headers) { + headers = {}; + const headerObj = content.headers; + Object.keys(headerObj).forEach((header) => { + headers[changeCase.camelCase(header)] = headerObj[header]; + }); + } + if (content && content.statusCode !== 200) { + return data; + } + data = RocketChat.callbacks.run('oembed:afterParseContent', { + meta: metas, + headers, + parsedUrl: content.parsedUrl, + content + }); + return data; +}; + +OEmbed.getUrlMetaWithCache = function(url, withFragment) { + const cache = RocketChat.models.OEmbedCache.findOneById(url); + if (cache != null) { + return cache.data; + } + const data = OEmbed.getUrlMeta(url, withFragment); + if (data != null) { + try { + RocketChat.models.OEmbedCache.createWithIdAndData(url, data); + } catch (_error) { + console.error('OEmbed duplicated record', url); + } + return data; + } +}; + +const getRelevantHeaders = function(headersObj) { + const headers = {}; + Object.keys(headersObj).forEach((key) => { + const value = headersObj[key]; + const lowerCaseKey = key.toLowerCase(); + if ((lowerCaseKey === 'contenttype' || lowerCaseKey === 'contentlength') && (value && value.trim() !== '')) { + headers[key] = value; + } + }); + + if (Object.keys(headers).length > 0) { + return headers; + } +}; + +const getRelevantMetaTags = function(metaObj) { + const tags = {}; + Object.keys(metaObj).forEach((key) => { + const value = metaObj[key]; + if (/^(og|fb|twitter|oembed|msapplication).+|description|title|pageTitle$/.test(key.toLowerCase()) && (value && value.trim() !== '')) { + tags[key] = value; + } + }); + + if (Object.keys(tags).length > 0) { + return tags; + } +}; + +OEmbed.rocketUrlParser = function(message) { + if (Array.isArray(message.urls)) { + let attachments = []; + let changed = false; + message.urls.forEach(function(item) { + if (item.ignoreParse === true) { + return; + } + if (item.url.startsWith('grain://')) { + changed = true; + item.meta = { + sandstorm: { + grain: item.sandstormViewInfo + } + }; + return; + } + if (!/^https?:\/\//i.test(item.url)) { + return; + } + const data = OEmbed.getUrlMetaWithCache(item.url); + if (data != null) { + if (data.attachments) { + return attachments = _.union(attachments, data.attachments); + } else { + if (data.meta != null) { + item.meta = getRelevantMetaTags(data.meta); + } + if (data.headers != null) { + item.headers = getRelevantHeaders(data.headers); + } + item.parsedUrl = data.parsedUrl; + return changed = true; + } + } + }); + if (attachments.length) { + RocketChat.models.Messages.setMessageAttachments(message._id, attachments); + } + if (changed === true) { + RocketChat.models.Messages.setUrlsById(message._id, message.urls); + } + } + return message; +}; + +RocketChat.settings.get('API_Embed', function(key, value) { + if (value) { + return RocketChat.callbacks.add('afterSaveMessage', OEmbed.rocketUrlParser, RocketChat.callbacks.priority.LOW, 'API_Embed'); + } else { + return RocketChat.callbacks.remove('afterSaveMessage', 'API_Embed'); + } +}); diff --git a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html index 8e7038988ad..8b051ec36ae 100644 --- a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html +++ b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.html @@ -7,95 +7,111 @@
    • - -
      - {{#if editing 'audioNotification'}} - - - - {{else}} - {{audioValue}} - {{/if}} + +
      + +
    • -
    • - -
      - {{#if editing 'desktopNotifications'}} - - - -
      - {{#if desktopNotificationDuration}} - + {{#if $neq disableNotifications true}} +
    • + +
      + {{#if editing 'audioNotification'}} + + + {{else}} - + {{audioValue}} {{/if}} +
      +
    • +
    • + +
      + {{#if editing 'desktopNotifications'}} + + + +
      + {{#if desktopNotificationDuration}} + + {{else}} + + {{/if}} - - - {{else}} - {{subValue 'desktopNotifications'}} - {{/if}} -
      -
    • - {{#unless editing 'desktopNotifications'}} -
    • - -
      - {{#if desktopNotificationDuration}}{{desktopNotificationDuration}} {{_"seconds"}}{{else}}{{_ "Use_User_Preferences_or_Global_Settings"}}{{/if}} -
      -
    • - {{/unless}} -
    • - -
      - {{#if editing 'mobilePushNotifications'}} - - - - - - {{else}} - {{subValue 'mobilePushNotifications'}} - {{/if}} -
      -
    • -
    • - -
      - {{#if editing 'emailNotifications'}} - - {{#if showEmailMentions}} - + + + {{else}} + {{subValue 'desktopNotifications'}} {{/if}} - - - - - {{else}} - {{subValue 'emailNotifications'}} - {{/if}} -
      -
    • - {{#unless emailVerified}} + + + {{#unless editing 'desktopNotifications'}} +
    • + +
      + {{#if desktopNotificationDuration}}{{desktopNotificationDuration}} {{_"seconds"}}{{else}}{{_ "Use_User_Preferences_or_Global_Settings"}}{{/if}} +
      +
    • + {{/unless}} +
    • + +
      + {{#if editing 'mobilePushNotifications'}} + + + + + + {{else}} + {{subValue 'mobilePushNotifications'}} + {{/if}} +
      +
    • +
    • + +
      + {{#if editing 'emailNotifications'}} + + {{#if showEmailMentions}} + + {{/if}} + + + + + {{else}} + {{subValue 'emailNotifications'}} + {{/if}} +
      +
    • + {{#unless emailVerified}} +
    • +
      + {{_ "You_wont_receive_email_notifications_because_you_have_not_verified_your_email"}} +
      +
    • + {{/unless}} + {{/if}}
    • -
      - {{_ "You_wont_receive_email_notifications_because_you_have_not_verified_your_email"}} + +
      + +
    • - {{/unless}}
    • - +
      {{#if editing 'unreadAlert'}} diff --git a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.js b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.js index e83f4bc12be..fe793bae3c7 100644 --- a/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.js +++ b/packages/rocketchat-push-notifications/client/views/pushNotificationsFlexTab.js @@ -15,6 +15,26 @@ Template.pushNotificationsFlexTab.helpers({ }); return sub ? sub.audioNotification || '' : ''; }, + disableNotifications() { + const sub = ChatSubscription.findOne({ + rid: Session.get('openedRoom') + }, { + fields: { + disableNotifications: 1 + } + }); + return sub ? sub.disableNotifications || false : false; + }, + hideUnreadStatus() { + const sub = ChatSubscription.findOne({ + rid: Session.get('openedRoom') + }, { + fields: { + hideUnreadStatus: 1 + } + }); + return sub ? sub.hideUnreadStatus || false : false; + }, desktopNotifications() { const sub = ChatSubscription.findOne({ rid: Session.get('openedRoom') @@ -160,6 +180,8 @@ Template.pushNotificationsFlexTab.onCreated(function() { this.validateSetting = (field) => { switch (field) { case 'audioNotification': + case 'hideUnreadStatus': + case 'disableNotifications': return true; default: const value = this.$(`input[name=${ field }]:checked`).val(); @@ -178,6 +200,10 @@ Template.pushNotificationsFlexTab.onCreated(function() { case 'audioNotification': value = this.$(`select[name=${ field }]`).val(); break; + case 'hideUnreadStatus': + case 'disableNotifications': + value = this.$(`input[name=${ field }]:checked`).val() ? '1' : '0'; + break; default: value = this.$(`input[name=${ field }]:checked`).val(); break; @@ -255,5 +281,11 @@ Template.pushNotificationsFlexTab.events({ $audio[0].play(); } } + }, + + 'change input[type=checkbox]'(e, instance) { + e.preventDefault(); + instance.editing.set($(e.currentTarget).attr('name')); + instance.saveSetting(); } }); diff --git a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js index 528a55e5d05..f559db2ff31 100644 --- a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js +++ b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js @@ -8,11 +8,11 @@ Meteor.methods({ check(field, String); check(value, String); - if (['audioNotification', 'desktopNotifications', 'mobilePushNotifications', 'emailNotifications', 'unreadAlert'].indexOf(field) === -1) { + if (['audioNotification', 'desktopNotifications', 'mobilePushNotifications', 'emailNotifications', 'unreadAlert', 'disableNotifications', 'hideUnreadStatus'].indexOf(field) === -1) { throw new Meteor.Error('error-invalid-settings', 'Invalid settings field', { method: 'saveNotificationSettings' }); } - if (field !== 'audioNotification' && ['all', 'mentions', 'nothing', 'default'].indexOf(value) === -1) { + if (field !== 'audioNotification' && field !== 'hideUnreadStatus' && field !== 'disableNotifications' && ['all', 'mentions', 'nothing', 'default'].indexOf(value) === -1) { throw new Meteor.Error('error-invalid-settings', 'Invalid settings value', { method: 'saveNotificationSettings' }); } @@ -37,6 +37,12 @@ Meteor.methods({ case 'unreadAlert': RocketChat.models.Subscriptions.updateUnreadAlertById(subscription._id, value); break; + case 'disableNotifications': + RocketChat.models.Subscriptions.updateDisableNotificationsById(subscription._id, value === '1' ? true : false); + break; + case 'hideUnreadStatus': + RocketChat.models.Subscriptions.updateHideUnreadStatusById(subscription._id, value === '1' ? true : false); + break; } return true; diff --git a/packages/rocketchat-push-notifications/server/models/Subscriptions.js b/packages/rocketchat-push-notifications/server/models/Subscriptions.js index d495f607f7f..5a78bc87554 100644 --- a/packages/rocketchat-push-notifications/server/models/Subscriptions.js +++ b/packages/rocketchat-push-notifications/server/models/Subscriptions.js @@ -82,6 +82,34 @@ RocketChat.models.Subscriptions.updateUnreadAlertById = function(_id, unreadAler return this.update(query, update); }; +RocketChat.models.Subscriptions.updateDisableNotificationsById = function(_id, disableNotifications) { + const query = { + _id + }; + + const update = { + $set: { + disableNotifications + } + }; + + return this.update(query, update); +}; + +RocketChat.models.Subscriptions.updateHideUnreadStatusById = function(_id, hideUnreadStatus) { + const query = { + _id + }; + + const update = { + $set: { + hideUnreadStatus + } + }; + + return this.update(query, update); +}; + RocketChat.models.Subscriptions.findAlwaysNotifyDesktopUsersByRoomId = function(roomId) { const query = { rid: roomId, @@ -126,7 +154,8 @@ RocketChat.models.Subscriptions.findNotificationPreferencesByRoom = function(roo {audioNotification: {$exists: true}}, {desktopNotifications: {$exists: true}}, {desktopNotificationDuration: {$exists: true}}, - {mobilePushNotifications: {$exists: true}} + {mobilePushNotifications: {$exists: true}}, + {disableNotifications: {$exists: true}} ] }; diff --git a/packages/rocketchat-slashcommands-archiveroom/server.js b/packages/rocketchat-slashcommands-archiveroom/server.js index 966b6b17a7a..3d382cc25cb 100644 --- a/packages/rocketchat-slashcommands-archiveroom/server.js +++ b/packages/rocketchat-slashcommands-archiveroom/server.js @@ -14,6 +14,11 @@ function Archive(command, params, item) { room = RocketChat.models.Rooms.findOneByName(channel); } + // You can not archive direct messages. + if (room.t === 'd') { + return; + } + const user = Meteor.users.findOne(Meteor.userId()); if (room.archived) { diff --git a/packages/rocketchat-slashcommands-unarchiveroom/server.js b/packages/rocketchat-slashcommands-unarchiveroom/server.js index 06bbd63075c..a9884b87075 100644 --- a/packages/rocketchat-slashcommands-unarchiveroom/server.js +++ b/packages/rocketchat-slashcommands-unarchiveroom/server.js @@ -13,6 +13,12 @@ function Unarchive(command, params, item) { channel = channel.replace('#', ''); room = RocketChat.models.Rooms.findOneByName(channel); } + + // You can not archive direct messages. + if (room.t === 'd') { + return; + } + const user = Meteor.users.findOne(Meteor.userId()); if (!room.archived) { @@ -27,7 +33,9 @@ function Unarchive(command, params, item) { }); return; } + Meteor.call('unarchiveRoom', room._id); + RocketChat.models.Messages.createRoomUnarchivedByRoomIdAndUser(room._id, Meteor.user()); RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { _id: Random.id(), @@ -38,6 +46,7 @@ function Unarchive(command, params, item) { sprintf: [channel] }, user.language) }); + return Unarchive; } diff --git a/packages/rocketchat-statistics/server/functions/get.js b/packages/rocketchat-statistics/server/functions/get.js index 2da4d0baa1d..2e9547406f9 100644 --- a/packages/rocketchat-statistics/server/functions/get.js +++ b/packages/rocketchat-statistics/server/functions/get.js @@ -59,6 +59,11 @@ RocketChat.statistics.get = function _getStatistics() { uptime: process.uptime() }; + statistics.deploy = { + method: process.env.DEPLOY_METHOD || 'tar', + platform: process.env.DEPLOY_PLATFORM || 'selfinstall' + }; + statistics.migration = RocketChat.Migrations._getControl(); statistics.instanceCount = InstanceStatus.getCollection().find({ _updatedAt: { $gt: new Date(Date.now() - process.uptime() * 1000 - 2000) }}).count(); diff --git a/packages/rocketchat-theme/client/imports/base.less b/packages/rocketchat-theme/client/imports/base.less index 2504ab7bdf3..8340766472a 100644 --- a/packages/rocketchat-theme/client/imports/base.less +++ b/packages/rocketchat-theme/client/imports/base.less @@ -11,7 +11,7 @@ *, *::before, *::after { - .box-sizing(border-box); + box-sizing: border-box; } *:not(input):not(textarea), @@ -277,7 +277,7 @@ blockquote { overflow-y: scroll; margin-top: 60px; -webkit-overflow-scrolling: touch; - .calc(height, ~'100% - 60px'); + height: calc(~'100% - 60px'); fieldset { margin-bottom: 1em; @@ -620,7 +620,7 @@ input.input-forward { input.input-forward.show { visibility: visible; - .calc(width, ~'100% - 48px'); + width: calc(~'100% - 48px'); } input.search { @@ -857,15 +857,15 @@ label.required::after { &:nth-child(1), &:nth-child(3) { opacity: 1; - .transform-origin(50%, 50%, 0); + transform-origin: 50%, 50%, 0; } &:nth-child(1) { - .transform(translate(-25%, 3px) rotate(-45deg) scale(0.5, 1)); + transform: translate(-25%, 3px) rotate(-45deg) scale(0.5, 1); } &:nth-child(3) { - .transform(translate(-25%, -3px) rotate(45deg) scale(0.5, 1)); + transform: translate(-25%, -3px) rotate(45deg) scale(0.5, 1); } } } @@ -876,7 +876,7 @@ label.required::after { width: 25px; height: 25px; z-index: 100; - .calc(top, ~"50% - 13px"); + top: calc(~"50% - 13px"); &::before, &::after { @@ -885,57 +885,57 @@ label.required::after { width: 2px; height: 10px; position: absolute; - .calc(top, ~"50% - 5px"); - .calc(left, ~"50% - 5px"); + top: calc(~"50% - 5px"); + left: calc(~"50% - 5px"); } &::before { - .transform(rotate(135deg) translateX(-4px)); + transform: rotate(135deg) translateX(-4px); transition: transform 0.185s ease-out, background 0.15s ease-out; } &::after { - .transform(rotate(-135deg) translateX(-4px)); + transform: rotate(-135deg) translateX(-4px); transition: transform 0.185s ease-out, background 0.15s ease-out; } &.left { &::before { - .transform(rotate(45deg) translateY(-4px)); + transform: rotate(45deg) translateY(-4px); } &::after { - .transform(rotate(-45deg) translateY(4px)); + transform: rotate(-45deg) translateY(4px); } } &.top { &::before { - .transform(rotate(-135deg) translateX(2px) translateY(-2px)); + transform: rotate(-135deg) translateX(2px) translateY(-2px); } &::after { - .transform(rotate(135deg) translateX(-2px) translateY(-2px)); + transform: rotate(135deg) translateX(-2px) translateY(-2px); } } &.bottom { &::before { - .transform(rotate(-45deg) translateX(-2px) translateY(-2px)); + transform: rotate(-45deg) translateX(-2px) translateY(-2px); } &::after { - .transform(rotate(45deg) translateX(2px) translateY(-2px)); + transform: rotate(45deg) translateX(2px) translateY(-2px); } } &.close { &::before { - .transform(rotate(-135deg) translateX(0) translateY(0)); + transform: rotate(-135deg) translateX(0) translateY(0); } &::after { - .transform(rotate(135deg) translateX(0) translateY(0)); + transform: rotate(135deg) translateX(0) translateY(0); } } } @@ -1044,7 +1044,7 @@ label.required::after { height: 100%; .flex-center; flex-flow: row nowrap; - .calc(width, ~"100% - 60px"); + width: calc(~"100% - 60px"); } h4 { @@ -1072,12 +1072,12 @@ label.required::after { overflow-y: auto; -webkit-overflow-scrolling: touch; direction: rtl; - .calc(height, ~'100% - ' @header-min-height + @footer-min-height); + height: calc(~'100% - ' @header-min-height + @footer-min-height); transition: transform 0.3s cubic-bezier(0.5, 0, 0.1, 1); z-index: 99; &.animated-hidden { - .transform(translateY(-100%) translateY(-50px)); + transform: translateY(-100%) translateY(-50px); } > .wrapper { @@ -1098,7 +1098,7 @@ label.required::after { z-index: 5; position: absolute; left: 18px; - .calc(top, ~"50% - 8px"); + top: calc(~"50% - 8px"); } } @@ -1159,12 +1159,12 @@ label.required::after { transition: transform 0.15s cubic-bezier(0.5, 0, 0.1, 1); &.animated-hidden { - .transform(translateX(-100%)); + transform: translateX(-100%); header, footer, .content { - .transform(translateX(-100%)); + transform: translateX(-100%); } } @@ -1236,7 +1236,7 @@ label.required::after { direction: rtl; position: absolute; top: @header-min-height; - .calc(height, ~"100% - " @header-min-height + @footer-min-height); + height: calc(~"100% - " @header-min-height + @footer-min-height); width: 100%; overflow-x: hidden; overflow-y: auto; @@ -1302,7 +1302,7 @@ label.required::after { &.toggle { font-size: 0; - > span { + > label { display: inline-block; width: calc(~"100% - 40px"); font-size: 14px; @@ -1513,14 +1513,14 @@ label.required::after { right: -18px; top: 2px; opacity: 0; - .transform(translateX(-10px)); + transform: translateX(-10px); transition: opacity 0.15s ease 0.35s, transform 0.12s ease-out 0.35s; } &:hover { .opt { opacity: 1; - .transform(translateX(0)); + transform: translateX(0); } } @@ -1585,7 +1585,7 @@ label.required::after { &.fixed { opacity: 1; - .transform(translateX(0)); + transform: translateX(0); } } @@ -1761,7 +1761,7 @@ label.required::after { } input[type='text'] { - .calc(width, ~'100% - 100px'); + width: calc(~'100% - 100px'); vertical-align: top; margin-top: -4px; margin-left: -3px; @@ -2197,14 +2197,14 @@ label.required::after { 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)); + transform: translateY(-10px); opacity: 0; visibility: hidden; &.show { opacity: 1; visibility: visible; - .transform(translateY(0px)); + transform: translateY(0); } > div { @@ -2685,7 +2685,7 @@ label.required::after { margin: 60px 20px 0 0; overflow: hidden; width: 100%; - .calc(height, ~'100% - 120px'); + height: calc(~'100% - 130px'); .message-cog-container { .message-action { @@ -2723,10 +2723,10 @@ label.required::after { left: 50%; z-index: 16; transition: transform 0.3s ease-out; - .transform(translateY(0)); + transform: translateY(0); &.not { - .transform(translateY(150%)); + transform: translateY(150%); } } @@ -2744,10 +2744,10 @@ label.required::after { border-top-left-radius: 4px; border-top-right-radius: 4px; transition: transform 0.3s ease-out; - .transform(translateY(0)); + transform: translateY(0); &.not { - .transform(translateY(150%)); + transform: translateY(150%); } button { @@ -2809,11 +2809,10 @@ label.required::after { display: block; position: absolute; top: -30px; - left: 0; + left: calc(~"50% - 70px"); font-size: 12px; font-weight: 600; text-align: center; - .calc(left, ~"50% - 70px"); z-index: 10; padding: 0 10px; min-width: 140px; @@ -3055,6 +3054,7 @@ label.required::after { max-height: 200px; max-width: 100%; opacity: 0; + cursor: pointer; } } @@ -3263,11 +3263,11 @@ body:not(.is-cordova) { width: 30px; border-width: 0 0 1px; cursor: pointer; - .transform(translateX(-27px)); + transform: translateX(-27px); transition: transform 0.25s ease-out 0.475s, background 0.075s ease-out 0.5s; i { - .transform-origin(50%, 50%, 0); + transform-origin: 50%, 50%, 0; transition: transform 0.3s ease-out; height: 12.5px; vertical-align: top; @@ -3321,7 +3321,7 @@ body:not(.is-cordova) { } > .animated-hidden { - .transform(translateX(100%)); + transform: translateX(100%); opacity: 0; } @@ -3778,7 +3778,7 @@ body:not(.is-cordova) { &.selected { .avatar { &::after { - .transform(scaleX(1)) + transform: scaleX(1); } } } @@ -3841,7 +3841,7 @@ body:not(.is-cordova) { text-overflow: ellipsis; position: relative; padding-left: 10px; - .calc(width, ~"100% - 45px"); + width: calc(~"100% - 45px"); } } @@ -3909,24 +3909,24 @@ body:not(.is-cordova) { main { position: absolute; overflow-y: scroll; - .calc(height, ~'100% - 112px'); + height: calc(~'100% - 112px'); } } } &.opened { - .animation(fadeIn .1s ease-out forwards); + animation: fadeIn 0.1s ease-out forwards; .modal { - .animation(modalEnter .35s cubic-bezier(.5, 0, .1, 1) forwards .1s); + animation: modalEnter 0.35s cubic-bezier(0.5, 0, 0.1, 1) forwards 0.1s; } } &.closed { - .animation(fadeOut .2s ease-out forwards); + animation: fadeOut 0.2s ease-out forwards; .modal { - .animation(modalExit .25s cubic-bezier(.5, 0, .1, 1) forwards); + animation: modalExit 0.25s cubic-bezier(0.5, 0, 0.1, 1) forwards; } } @@ -4525,7 +4525,7 @@ body:not(.is-cordova) { button.more { width: 60px; - .transform(translateX(-57px)); + transform: translateX(-57px); } } @@ -4777,10 +4777,10 @@ body:not(.is-cordova) { bottom: 8px; left: 50%; transition: transform 0.3s ease-out; - .transform(translateY(0)); + transform: translateY(0); &.not { - .transform(translateY(150%)); + transform: translateY(150%); } } @@ -4997,7 +4997,7 @@ a + br.only-after-a { border-width: 0; } - .stream-info { + .users-typing { display: none; } diff --git a/packages/rocketchat-theme/client/imports/keyframes.less b/packages/rocketchat-theme/client/imports/keyframes.less index 8aa3df96f1d..04261b85969 100644 --- a/packages/rocketchat-theme/client/imports/keyframes.less +++ b/packages/rocketchat-theme/client/imports/keyframes.less @@ -117,13 +117,13 @@ 1% { opacity: 0; visibility: visible; - .transform(translateY(-150px)); + transform: translateY(-150px); } 100% { opacity: 1; visibility: visible; - .transform(translateY(0)); + transform: translateY(0); } } @@ -155,7 +155,7 @@ 99% { opacity: 0; visibility: visible; - .transform(translateY(150px)); + transform: translateY(150px); } 100% { diff --git a/packages/rocketchat-theme/client/imports/lesshat.less b/packages/rocketchat-theme/client/imports/lesshat.less deleted file mode 100644 index 0979dd5275e..00000000000 --- a/packages/rocketchat-theme/client/imports/lesshat.less +++ /dev/null @@ -1,85 +0,0 @@ -// 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/client/imports/rtl.less b/packages/rocketchat-theme/client/imports/rtl.less index ca933e6b1e9..21b90888e39 100644 --- a/packages/rocketchat-theme/client/imports/rtl.less +++ b/packages/rocketchat-theme/client/imports/rtl.less @@ -179,12 +179,12 @@ } &.animated-hidden { - .transform(translateX(100%)); + transform: translateX(100%); header, footer, .content { - .transform(translateX(100%)); + transform: translateX(100%); } } } @@ -370,7 +370,7 @@ .more { .right(0); - .transform(translateX(27px)); + transform: translateX(27px); } .search-form { @@ -393,7 +393,7 @@ .flex-tab { .control { .more { - .transform(translateX(0)); + transform: translateX(0); } } } @@ -494,7 +494,7 @@ right: @rooms-box-width; .flex-tab { - .transform(translateX(calc(~'100% + 40px'))); + transform: translateX(calc(~'100% + 40px')); } } } @@ -523,11 +523,11 @@ &.menu-opened { i { &:nth-child(1) { - .transform(translate(25%, 3px) rotate(45deg) scale(0.5, 1)); + transform: translate(25%, 3px) rotate(45deg) scale(0.5, 1); } &:nth-child(3) { - .transform(translate(25%, -3px) rotate(-45deg) scale(0.5, 1)); + transform: translate(25%, -3px) rotate(-45deg) scale(0.5, 1); } } } @@ -536,54 +536,54 @@ .arrow { &::before, &::after { - .calc(right, ~"50% - 5px"); + right: calc(~"50% - 5px"); } &::before { - .transform(rotate(135deg) translateX(4px)); + transform: rotate(135deg) translateX(4px); } &::after { - .transform(rotate(-135deg) translateX(4px)); + transform: rotate(-135deg) translateX(4px); } &.left { &::before { - .transform(rotate(-45deg) translateY(-4px)); + transform: rotate(-45deg) translateY(-4px); } &::after { - .transform(rotate(45deg) translateY(4px)); + transform: rotate(45deg) translateY(4px); } } &.top { &::before { - .transform(rotate(45deg) translateX(-2px) translateY(2px)); + transform: rotate(45deg) translateX(-2px) translateY(2px); } &::after { - .transform(rotate(-45deg) translateX(2px) translateY(2px)); + transform: rotate(-45deg) translateX(2px) translateY(2px); } } &.bottom { &::before { - .transform(rotate(-45deg) translateX(-2px) translateY(-2px)); + transform: rotate(-45deg) translateX(-2px) translateY(-2px); } &::after { - .transform(rotate(45deg) translateX(2px) translateY(-2px)); + transform: rotate(45deg) translateX(2px) translateY(-2px); } } &.close { &::before { - .transform(rotate(-45deg)); + transform: rotate(-45deg); } &::after { - .transform(rotate(45deg)); + transform: rotate(45deg); } } } diff --git a/packages/rocketchat-theme/client/main.less b/packages/rocketchat-theme/client/main.less index 75a37e24dc4..6cbbb09e1ba 100644 --- a/packages/rocketchat-theme/client/main.less +++ b/packages/rocketchat-theme/client/main.less @@ -1,6 +1,5 @@ @import "imports/variables.less"; @import "imports/reset.less"; -@import "imports/lesshat.less"; @import "imports/keyframes.less"; @import "imports/rtl.less"; @import "imports/forms.less"; diff --git a/packages/rocketchat-theme/package.js b/packages/rocketchat-theme/package.js index 7376aeff5c9..e0d32181f49 100644 --- a/packages/rocketchat-theme/package.js +++ b/packages/rocketchat-theme/package.js @@ -38,7 +38,6 @@ Package.onUse(function(api) { api.addFiles('client/main.less', 'client'); // Run-time stylesheets - api.addAssets('server/lesshat.less', 'server'); api.addAssets('server/colors.less', 'server'); }); diff --git a/packages/rocketchat-theme/server/colors.less b/packages/rocketchat-theme/server/colors.less index 3ee92c6f095..0c191b7c3f1 100755 --- a/packages/rocketchat-theme/server/colors.less +++ b/packages/rocketchat-theme/server/colors.less @@ -27,6 +27,20 @@ @transparent-lighter: rgba(255, 255, 255, 0.3); @transparent-lightest: rgba(255, 255, 255, 0.6); +/** ---------------------------------------------------------------------------- + * Mixins + */ + +.buttonColors(@color, @bg) { + color: @color; + background-color: @bg; + + &:hover { + color: mix(@color, contrast(@bg), 60%); + background-color: mix(@bg, contrast(@color), 60%); + } +} + /** ---------------------------------------------------------------------------- * Classes for variables */ @@ -243,7 +257,42 @@ * Forms */ -.input-shade(@primary-font-color, @content-background-color); +input, +select, +textarea { + color: @primary-font-color; + background-color: transparent; + border-color: mix(contrast(@content-background-color), @content-background-color, 10%); + border-style: solid; + + &::placeholder { + color: mix(@primary-font-color, @content-background-color, 75%); + } + + &[disabled] { + background-color: mix(contrast(@content-background-color), @content-background-color, 10%); + } +} + +.disabled label, +[disabled] label { + color: mix(@primary-font-color, @content-background-color, 75%); +} + +.-autocomplete-container { + background-color: mix(contrast(@content-background-color), @content-background-color, 10%); +} + +.-autocomplete-item.selected { + background-color: mix(contrast(@content-background-color), @content-background-color, 20%); +} + +input[type="button"], +input[type="submit"] { + color: @primary-font-color; + background: mix(contrast(@content-background-color), @content-background-color, 10%); + border-color: mix(contrast(@content-background-color), @content-background-color, 10%); +} .toolbar-search__input { &:focus { @@ -260,7 +309,42 @@ } .flex-nav { - .input-shade(@primary-background-contrast, @transparent-lighter); + input, + select, + textarea { + color: @primary-background-contrast; + background-color: transparent; + border-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); + border-style: solid; + + &::placeholder { + color: mix(@primary-background-contrast, @transparent-lighter, 75%); + } + + &[disabled] { + background-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); + } + } + + .disabled label, + [disabled] label { + color: mix(@primary-background-contrast, @transparent-lighter, 75%); + } + + .-autocomplete-container { + background-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); + } + + .-autocomplete-item.selected { + background-color: mix(contrast(@transparent-lighter), @transparent-lighter, 20%); + } + + input[type="button"], + input[type="submit"] { + color: @primary-background-contrast; + background: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); + border-color: mix(contrast(@transparent-lighter), @transparent-lighter, 10%); + } input { &:focus { @@ -391,7 +475,7 @@ a:hover { .full-page, .page-loading { - .gradient(shade(@primary-background-color), @primary-background-color); + background: linear-gradient(to top, shade(@primary-background-color), @primary-background-color); a { color: @tertiary-font-color; @@ -403,8 +487,6 @@ a:hover { } #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; @@ -426,7 +508,11 @@ a:hover { .messages-container { .edit-room-title { - .linkColors(@secondary-font-color, @primary-font-color); + color: @secondary-font-color; + + &:hover { + color: @primary-font-color; + } } .footer { @@ -515,7 +601,11 @@ a:hover { } a { - .linkColors(@link-font-color, darken(@link-font-color, 10%)); + color: @link-font-color; + + &:hover { + color: darken(@link-font-color, 10%); + } } .mention-link { @@ -539,7 +629,11 @@ a:hover { .side-nav { a, .info { - .linkColors(@tertiary-font-color, @tertiary-font-color); + color: @tertiary-font-color; + + &:hover { + color: @tertiary-font-color; + } } .arrow::before, @@ -648,7 +742,10 @@ a:hover { } &.attention { - .blink(@selection-color); + animation-duration: 1000ms; + animation-name: blink; + animation-iteration-count: infinite; + animation-direction: alternate; } } @@ -783,7 +880,11 @@ i.status-offline { } .alert-link { - .linkColors(@link-font-color, darken(@link-font-color, 10%)); + color: @link-font-color; + + &:hover { + color: darken(@link-font-color, 10%); + } } label.required::after { @@ -800,3 +901,13 @@ label.required::after { background-color: @primary-font-color; } } + +@keyframes blink { + from { + color: @selection-color; + } + + to { + opacity: inherit; + } +} diff --git a/packages/rocketchat-theme/server/lesshat.less b/packages/rocketchat-theme/server/lesshat.less deleted file mode 100644 index b56ab5c59d4..00000000000 --- a/packages/rocketchat-theme/server/lesshat.less +++ /dev/null @@ -1,101 +0,0 @@ -.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%); - } - } - - .disabled 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.js b/packages/rocketchat-theme/server/server.js index f6d81dc5d37..940e4a1fddd 100644 --- a/packages/rocketchat-theme/server/server.js +++ b/packages/rocketchat-theme/server/server.js @@ -55,7 +55,7 @@ RocketChat.theme = new class { constructor() { this.variables = {}; this.packageCallbacks = []; - this.files = ['server/lesshat.less', 'server/colors.less']; + this.files = ['server/colors.less']; this.customCSS = ''; RocketChat.settings.add('css', ''); RocketChat.settings.addGroup('Layout'); diff --git a/packages/rocketchat-ui-account/client/accountPreferences.html b/packages/rocketchat-ui-account/client/accountPreferences.html index c0c5f892016..8d3bd2116db 100644 --- a/packages/rocketchat-ui-account/client/accountPreferences.html +++ b/packages/rocketchat-ui-account/client/accountPreferences.html @@ -53,7 +53,7 @@
      - +
      diff --git a/packages/rocketchat-ui-admin/client/users/adminUsers.html b/packages/rocketchat-ui-admin/client/users/adminUsers.html index bf7827daadf..46e17936f74 100644 --- a/packages/rocketchat-ui-admin/client/users/adminUsers.html +++ b/packages/rocketchat-ui-admin/client/users/adminUsers.html @@ -29,6 +29,8 @@ {{_ "Name"}} {{_ "Username"}} {{_ "Email"}} + {{_ "Roles"}} + {{_ "Status"}} @@ -42,10 +44,13 @@ {{name}} {{username}} {{emailAddress}} + {{roles}} + {{status}} {{/each}} + {{#if hasMore}} {{/if}} @@ -58,3 +63,4 @@ {{/with}}
      + diff --git a/packages/rocketchat-ui-flextab/client/flexTabBar.js b/packages/rocketchat-ui-flextab/client/flexTabBar.js index b65246f1748..a6627f9b1c3 100644 --- a/packages/rocketchat-ui-flextab/client/flexTabBar.js +++ b/packages/rocketchat-ui-flextab/client/flexTabBar.js @@ -14,6 +14,10 @@ Template.flexTabBar.helpers({ }, visible() { + if (!Meteor.userId() && !this.anonymous) { + return 'hidden'; + } + if (this.groups.indexOf(Template.instance().tabBar.currentGroup()) === -1) { return 'hidden'; } diff --git a/packages/rocketchat-ui-flextab/client/tabs/membersList.js b/packages/rocketchat-ui-flextab/client/tabs/membersList.js index c9e35fcd0a9..f6a92be6266 100644 --- a/packages/rocketchat-ui-flextab/client/tabs/membersList.js +++ b/packages/rocketchat-ui-flextab/client/tabs/membersList.js @@ -53,7 +53,11 @@ Template.membersList.helpers({ }; }); - users = _.sortBy(users, u => u.user.username); + if (RocketChat.settings.get('UI_Use_Real_Name')) { + users = _.sortBy(users, u => u.user.name); + } else { + users = _.sortBy(users, u => u.user.username); + } // show online users first. // sortBy is stable, so we can do this users = _.sortBy(users, u => u.status == null); diff --git a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html index 7b5d52788e1..4b1dfaffa3c 100644 --- a/packages/rocketchat-ui-flextab/client/tabs/userInfo.html +++ b/packages/rocketchat-ui-flextab/client/tabs/userInfo.html @@ -41,67 +41,69 @@
      {{/with}}