Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat into rocketchat-ui-message
commit
6ac7193519
@ -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"] |
@ -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 |
||||
}; |
@ -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}} |
||||
|
@ -0,0 +1,11 @@ |
||||
{{#if noteGroups}} |
||||
{{#each noteGroups}} |
||||
|
||||
### {{title}} |
||||
|
||||
{{#each notes}} |
||||
* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} |
||||
{{/each}} |
||||
{{/each}} |
||||
|
||||
{{/if}} |
@ -0,0 +1,26 @@ |
||||
<a name="{{version}}"></a> |
||||
{{#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}} |
@ -0,0 +1,22 @@ |
||||
{{> header}} |
||||
|
||||
{{#each commitGroups}} |
||||
|
||||
{{#if collapse}} |
||||
<details> |
||||
<summary>{{title}}</summary> |
||||
{{else}} |
||||
### {{title}} |
||||
{{/if}} |
||||
|
||||
{{#each commits}} |
||||
{{> commit root=@root}} |
||||
{{/each}} |
||||
{{#if collapse}} |
||||
</details> |
||||
{{/if}} |
||||
|
||||
{{/each}} |
||||
{{> footer}} |
||||
|
||||
|
@ -1 +1 @@ |
||||
METEOR@1.4.3.1 |
||||
METEOR@1.4.4.2 |
||||
|
@ -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'); |
||||
}); |
||||
}); |
@ -0,0 +1,61 @@ |
||||
 |
||||
|
||||
# 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 |
@ -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] |
@ -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 |
||||
|
@ -1,8 +0,0 @@ |
||||
var ism = db.isMaster(); |
||||
printjson(ism); |
||||
if (ism.ismaster) { |
||||
} else
|
||||
{ |
||||
var msg = rs.initiate(); |
||||
printjson(msg); |
||||
} |
@ -0,0 +1,13 @@ |
||||
var ism = db.isMaster();
|
||||
if (!ism.ismaster) { |
||||
rs.initiate( |
||||
{
|
||||
_id: 'rs0',
|
||||
members: [
|
||||
{
|
||||
_id: 0,
|
||||
host: 'localhost:27017'
|
||||
}
|
||||
] |
||||
});
|
||||
} |
@ -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 |
@ -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 |
@ -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." |
@ -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 |
@ -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 |
||||
|
@ -1,2 +0,0 @@ |
||||
**/lesshat.less |
||||
**/_lesshat.import.less |
@ -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 |
||||
} |
||||
}); |
||||
}); |
||||
}); |
@ -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); |
||||
}); |
@ -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; |
||||
|
@ -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 |
||||
}; |
||||
} |
||||
}); |
||||
}); |
||||
|
@ -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+ |
@ -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); |
||||
} |
||||
}); |
||||
}); |
||||
} |
@ -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; |
||||
} |
@ -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'); |
||||
}); |
||||
|
@ -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 }); |
||||
}); |
||||
}); |
@ -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; |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 = "<div class=\"katex-error katex-#{display_mode}-error\">" |
||||
rendered += "#{s.escapeHTML e.message}" |
||||
rendered += "</div>" |
||||
|
||||
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 |
@ -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 = `<div class="katex-error katex-${ display_mode }-error">`; |
||||
rendered += `${ s.escapeHTML(e.message) }`; |
||||
rendered += '</div>'; |
||||
} |
||||
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); |
||||
}); |
||||
} |
@ -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'} |
@ -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
|
@ -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; |
@ -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 |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue