Merge package.json, match version in electron/package.json

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2017-05-05 17:03:28 +01:00
commit a09f890619
53 changed files with 1046 additions and 484 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@
npm-debug.log npm-debug.log
electron/dist electron/dist
electron/pub electron/pub
/config.json

View file

@ -1,3 +1,122 @@
Changes in [0.9.8](https://github.com/vector-im/riot-web/releases/tag/v0.9.8) (2017-04-12)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.3...v0.9.8)
* No changes
Changes in [0.9.8-rc.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.8-rc.3) (2017-04-11)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.2...v0.9.8-rc.3)
* Make the clear cache button work on desktop
[\#3598](https://github.com/vector-im/riot-web/pull/3598)
Changes in [0.9.8-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.8-rc.2) (2017-04-10)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.1...v0.9.8-rc.2)
* Redacted events bg: black lozenge -> torn paper
[\#3596](https://github.com/vector-im/riot-web/pull/3596)
* Add 'app' parameter to rageshake report
[\#3594](https://github.com/vector-im/riot-web/pull/3594)
Changes in [0.9.8-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.8-rc.1) (2017-04-07)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7...v0.9.8-rc.1)
* Add support for indexeddb sync in webworker
[\#3578](https://github.com/vector-im/riot-web/pull/3578)
* Add CSS to make Emote sender cursor : pointer
[\#3574](https://github.com/vector-im/riot-web/pull/3574)
* Remove rageshake server
[\#3565](https://github.com/vector-im/riot-web/pull/3565)
* Adjust CSS for matrix-org/matrix-react-sdk#789
[\#3566](https://github.com/vector-im/riot-web/pull/3566)
* Fix tests to reflect recent changes
[\#3537](https://github.com/vector-im/riot-web/pull/3537)
* Do not assume getTs will return comparable integer
[\#3536](https://github.com/vector-im/riot-web/pull/3536)
* Rename ReactPerf to Perf
[\#3535](https://github.com/vector-im/riot-web/pull/3535)
* Don't show phone number as target for email notifs
[\#3530](https://github.com/vector-im/riot-web/pull/3530)
* Fix people section again
[\#3458](https://github.com/vector-im/riot-web/pull/3458)
* dark theme invert inconsistent across browsers
[\#3479](https://github.com/vector-im/riot-web/pull/3479)
* CSS for adding phone number in UserSettings
[\#3451](https://github.com/vector-im/riot-web/pull/3451)
* Support for phone number registration/signin, mk2
[\#3426](https://github.com/vector-im/riot-web/pull/3426)
* Confirm redactions with a dialog
[\#3470](https://github.com/vector-im/riot-web/pull/3470)
* Better CSS for redactions
[\#3453](https://github.com/vector-im/riot-web/pull/3453)
* Fix the people section
[\#3448](https://github.com/vector-im/riot-web/pull/3448)
* Merge the two RoomTile context menus into one
[\#3395](https://github.com/vector-im/riot-web/pull/3395)
* Refactor screen set after login
[\#3385](https://github.com/vector-im/riot-web/pull/3385)
* CSS for redacted EventTiles
[\#3379](https://github.com/vector-im/riot-web/pull/3379)
* Height:100% for welcome pages on Safari
[\#3340](https://github.com/vector-im/riot-web/pull/3340)
* `view_room` dispatch from `onClick` RoomTile
[\#3376](https://github.com/vector-im/riot-web/pull/3376)
* Hide statusAreaBox_line entirely when inCall
[\#3350](https://github.com/vector-im/riot-web/pull/3350)
* Set padding-bottom: 0px for .mx_Dialog spinner
[\#3351](https://github.com/vector-im/riot-web/pull/3351)
* Support InteractiveAuth based registration
[\#3333](https://github.com/vector-im/riot-web/pull/3333)
* Expose notification option for username/MXID
[\#3334](https://github.com/vector-im/riot-web/pull/3334)
* Float the toggle in the top right of MELS
[\#3190](https://github.com/vector-im/riot-web/pull/3190)
* More aggressive rageshake log culling
[\#3311](https://github.com/vector-im/riot-web/pull/3311)
* Don't overflow directory network options
[\#3282](https://github.com/vector-im/riot-web/pull/3282)
* CSS for ban / kick reason prompt
[\#3250](https://github.com/vector-im/riot-web/pull/3250)
* Allow forgetting rooms you're banned from
[\#3246](https://github.com/vector-im/riot-web/pull/3246)
* Fix icon paths in manifest
[\#3245](https://github.com/vector-im/riot-web/pull/3245)
* Fix broken tests caused by adding IndexedDB support
[\#3242](https://github.com/vector-im/riot-web/pull/3242)
* CSS for un-ban button in RoomSettings
[\#3227](https://github.com/vector-im/riot-web/pull/3227)
* Remove z-index property on avatar initials
[\#3239](https://github.com/vector-im/riot-web/pull/3239)
* Reposition certain icons in the status bar
[\#3233](https://github.com/vector-im/riot-web/pull/3233)
* CSS for kick/ban confirmation dialog
[\#3224](https://github.com/vector-im/riot-web/pull/3224)
* Style for split-out interactive auth
[\#3217](https://github.com/vector-im/riot-web/pull/3217)
* Use the teamToken threaded through from react sdk
[\#3196](https://github.com/vector-im/riot-web/pull/3196)
* rageshake: Add file server with basic auth
[\#3169](https://github.com/vector-im/riot-web/pull/3169)
* Fix bug with home icon not appearing when logged in as team member
[\#3162](https://github.com/vector-im/riot-web/pull/3162)
* Add ISSUE_TEMPLATE
[\#2836](https://github.com/vector-im/riot-web/pull/2836)
* Store bug reports in separate directories
[\#3150](https://github.com/vector-im/riot-web/pull/3150)
* Quick and dirty support for custom welcome pages.
[\#2575](https://github.com/vector-im/riot-web/pull/2575)
* RTS Welcome Pages
[\#3103](https://github.com/vector-im/riot-web/pull/3103)
* rageshake: Abide by Go standards
[\#3149](https://github.com/vector-im/riot-web/pull/3149)
* Bug report server script
[\#3072](https://github.com/vector-im/riot-web/pull/3072)
* Bump olm version
[\#3125](https://github.com/vector-im/riot-web/pull/3125)
Changes in [0.9.7](https://github.com/vector-im/riot-web/releases/tag/v0.9.7) (2017-02-04) Changes in [0.9.7](https://github.com/vector-im/riot-web/releases/tag/v0.9.7) (2017-02-04)
========================================================================================== ==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.3...v0.9.7) [Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.3...v0.9.7)

View file

@ -1,7 +1,7 @@
{ {
"name": "riot-web", "name": "riot-web",
"main": "src/electron-main.js", "main": "src/electron-main.js",
"version": "0.0.0", "version": "0.9.8",
"description": "A feature-rich client for Matrix.org", "description": "A feature-rich client for Matrix.org",
"author": "Vector Creations Ltd.", "author": "Vector Creations Ltd.",
"dependencies": { "dependencies": {

View file

@ -173,6 +173,7 @@ const shouldQuit = electron.app.makeSingleInstance((commandLine, workingDirector
}); });
if (shouldQuit) { if (shouldQuit) {
console.log("Other instance detected: exiting");
electron.app.quit() electron.app.quit()
} }
@ -213,9 +214,12 @@ electron.app.on('ready', () => {
brand: vectorConfig.brand || 'Riot' brand: vectorConfig.brand || 'Riot'
}); });
mainWindow.once('ready-to-show', () => { if (!process.argv.includes('--hidden')) {
mainWindow.show(); mainWindow.once('ready-to-show', () => {
}); mainWindow.show();
});
}
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
mainWindow = null; mainWindow = null;
}); });

View file

@ -2,7 +2,7 @@
"name": "riot-web", "name": "riot-web",
"productName": "Riot", "productName": "Riot",
"main": "electron/src/electron-main.js", "main": "electron/src/electron-main.js",
"version": "0.9.7", "version": "0.9.8",
"description": "A feature-rich client for Matrix.org", "description": "A feature-rich client for Matrix.org",
"author": "Vector Creations Ltd.", "author": "Vector Creations Ltd.",
"repository": { "repository": {
@ -30,18 +30,18 @@
"build:res": "node scripts/copy-res.js", "build:res": "node scripts/copy-res.js",
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js", "build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
"build:compile": "babel --source-maps -d lib src", "build:compile": "babel --source-maps -d lib src",
"build:bundle": "NODE_ENV=production webpack -p --progress", "build:bundle": "cross-env NODE_ENV=production webpack -p --progress",
"build:bundle:dev": "webpack --optimize-occurence-order --progress", "build:bundle:dev": "webpack --optimize-occurence-order --progress",
"build:electron": "npm run clean && npm run build && npm run install:electron && build -wml --ia32 --x64", "build:electron": "npm run clean && npm run build && npm run install:electron && build -wml --ia32 --x64",
"build": "node scripts/babelcheck.js && npm run build:res && npm run build:bundle", "build": "npm run build:res && npm run build:bundle",
"build:dev": "node scripts/babelcheck.js && npm run build:res && npm run build:bundle:dev", "build:dev": "npm run build:res && npm run build:bundle:dev",
"dist": "scripts/package.sh", "dist": "scripts/package.sh",
"install:electron": "install-app-deps", "install:electron": "install-app-deps",
"electron": "npm run install:electron && electron .", "electron": "npm run install:electron && electron .",
"start:res": "node scripts/copy-res.js -w", "start:res": "node scripts/copy-res.js -w",
"start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-file=bundles/_dev_/[name].js -w --progress", "start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-file=bundles/_dev_/[name].js -w --progress",
"start:js:prod": "NODE_ENV=production webpack-dev-server -w --progress", "start:js:prod": "cross-env NODE_ENV=production webpack-dev-server -w --progress",
"start": "node scripts/babelcheck.js && parallelshell \"npm run start:res\" \"npm run start:js\"", "start": "parallelshell \"npm run start:res\" \"npm run start:js\"",
"start:prod": "parallelshell \"npm run start:res\" \"npm run start:js:prod\"", "start:prod": "parallelshell \"npm run start:res\" \"npm run start:js:prod\"",
"lint": "eslint src/", "lint": "eslint src/",
"lintall": "eslint src/ test/", "lintall": "eslint src/ test/",
@ -58,21 +58,21 @@
"draft-js": "^0.8.1", "draft-js": "^0.8.1",
"extract-text-webpack-plugin": "^0.9.1", "extract-text-webpack-plugin": "^0.9.1",
"favico.js": "^0.3.10", "favico.js": "^0.3.10",
"filesize": "^3.1.2", "filesize": "3.5.6",
"flux": "~2.0.3", "flux": "~2.0.3",
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
"gfm.css": "^1.1.1", "gfm.css": "^1.1.1",
"highlight.js": "^9.0.0", "highlight.js": "^9.0.0",
"linkifyjs": "^2.1.3", "linkifyjs": "^2.1.3",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"matrix-react-sdk": "matrix-org/matrix-react-sdk#develop", "matrix-react-sdk": "matrix-org/matrix-react-sdk#develop",
"modernizr": "^3.1.0", "modernizr": "^3.1.0",
"pako": "^1.0.5",
"q": "^1.4.1", "q": "^1.4.1",
"react": "^15.4.0", "react": "^15.4.0",
"react-dnd": "^2.1.4", "react-dnd": "^2.1.4",
"react-dnd-html5-backend": "^2.1.2", "react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.4.0", "react-dom": "^15.4.0",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#39d858c",
"sanitize-html": "^1.11.1", "sanitize-html": "^1.11.1",
"ua-parser-js": "^0.7.10", "ua-parser-js": "^0.7.10",
"url": "^0.11.0" "url": "^0.11.0"
@ -95,6 +95,7 @@
"babel-preset-stage-2": "^6.17.0", "babel-preset-stage-2": "^6.17.0",
"chokidar": "^1.6.1", "chokidar": "^1.6.1",
"cpx": "^1.3.2", "cpx": "^1.3.2",
"cross-env": "^4.0.0",
"css-raw-loader": "^0.1.1", "css-raw-loader": "^0.1.1",
"electron-builder": "^11.2.4", "electron-builder": "^11.2.4",
"electron-builder-squirrel-windows": "^11.2.1", "electron-builder-squirrel-windows": "^11.2.1",
@ -140,7 +141,7 @@
"build": { "build": {
"appId": "im.riot.app", "appId": "im.riot.app",
"category": "Network", "category": "Network",
"electronVersion": "1.4.14", "electronVersion": "1.6.2",
"//asar=false": "https://github.com/electron-userland/electron-builder/issues/675", "//asar=false": "https://github.com/electron-userland/electron-builder/issues/675",
"asar": false, "asar": false,
"dereference": true, "dereference": true,

View file

@ -1,26 +0,0 @@
#!/usr/bin/env node
var exec = require('child_process').exec;
// Makes sure the babel executable in the path is babel 6 (or greater), not
// babel 5, which it is if you upgrade from an older version of react-sdk and
// run 'npm install' since the package has changed to babel-cli, so 'babel'
// remains installed and the executable in node_modules/.bin remains as babel
// 5.
// This script is duplicated from matrix-react-sdk because it can't reliably
// be pulled in from react-sdk while npm install is failing, as it will do
// if the environment is in the erroneous state this script checks for.
exec("babel -V", function (error, stdout, stderr) {
if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
console.log("\033[31m\033[1m"+
'*****************************************\n'+
'* vector-web has moved to babel 6 *\n'+
'* Please "rm -rf node_modules && npm i" *\n'+
'* then restore links as appropriate *\n'+
'*****************************************\n'+
"\033[91m");
process.exit(1);
}
});

View file

@ -10,6 +10,7 @@ const COPY_LIST = [
["res/{media,vector-icons}/**", "webapp"], ["res/{media,vector-icons}/**", "webapp"],
["src/skins/vector/{fonts,img}/**", "webapp"], ["src/skins/vector/{fonts,img}/**", "webapp"],
["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"], ["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"],
["node_modules/emojione/assets/png/*", "webapp/emojione/png/"],
["./config.json", "webapp", {directwatch: 1}], ["./config.json", "webapp", {directwatch: 1}],
]; ];

View file

@ -1,146 +0,0 @@
// Run a web server capable of dumping bug reports sent by Riot.
// Requires Go 1.5+
// Usage: BUGS_USER=user BUGS_PASS=password go run rageshake.go PORT
// Example: BUGS_USER=alice BUGS_PASS=secret go run rageshake.go 8080
package main
import (
"bytes"
"compress/gzip"
"crypto/subtle"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
)
var maxPayloadSize = 1024 * 1024 * 55 // 55 MB
type LogEntry struct {
ID string `json:"id"`
Lines string `json:"lines"`
}
type Payload struct {
Text string `json:"text"`
Version string `json:"version"`
UserAgent string `json:"user_agent"`
Logs []LogEntry `json:"logs"`
}
func respond(code int, w http.ResponseWriter) {
w.WriteHeader(code)
w.Write([]byte("{}"))
}
func gzipAndSave(data []byte, dirname, fpath string) error {
_ = os.MkdirAll(filepath.Join("bugs", dirname), os.ModePerm)
fpath = filepath.Join("bugs", dirname, fpath)
if _, err := os.Stat(fpath); err == nil {
return fmt.Errorf("file already exists") // the user can just retry
}
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write(data); err != nil {
return err
}
if err := gz.Flush(); err != nil {
return err
}
if err := gz.Close(); err != nil {
return err
}
if err := ioutil.WriteFile(fpath, b.Bytes(), 0644); err != nil {
return err
}
return nil
}
func basicAuth(handler http.Handler, username, password, realm string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth() // pull creds from the request
// check user and pass securely
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n"))
return
}
handler.ServeHTTP(w, r)
})
}
func main() {
http.HandleFunc("/api/submit", func(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" && req.Method != "OPTIONS" {
respond(405, w)
return
}
// Set CORS
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
if req.Method == "OPTIONS" {
respond(200, w)
return
}
if length, err := strconv.Atoi(req.Header.Get("Content-Length")); err != nil || length > maxPayloadSize {
respond(413, w)
return
}
var p Payload
if err := json.NewDecoder(req.Body).Decode(&p); err != nil {
respond(400, w)
return
}
// Dump bug report to disk as form:
// "bugreport-20170115-112233.log.gz" => user text, version, user agent, # logs
// "bugreport-20170115-112233-0.log.gz" => most recent log
// "bugreport-20170115-112233-1.log.gz" => ...
// "bugreport-20170115-112233-N.log.gz" => oldest log
t := time.Now().UTC()
prefix := t.Format("2006-01-02/150405")
summary := fmt.Sprintf(
"%s\n\nNumber of logs: %d\nVersion: %s\nUser-Agent: %s\n", p.Text, len(p.Logs), p.Version, p.UserAgent,
)
if err := gzipAndSave([]byte(summary), prefix, "details.log.gz"); err != nil {
respond(500, w)
return
}
for i, log := range p.Logs {
if err := gzipAndSave([]byte(log.Lines), prefix, fmt.Sprintf("logs-%d.log.gz", i)); err != nil {
respond(500, w)
return // TODO: Rollback?
}
}
respond(200, w)
})
// Make sure bugs directory exists
_ = os.Mkdir("bugs", os.ModePerm)
// serve files under "bugs"
fs := http.FileServer(http.Dir("bugs"))
fs = http.StripPrefix("/api/listing/", fs)
// set auth if env vars exist
usr := os.Getenv("BUGS_USER")
pass := os.Getenv("BUGS_PASS")
if usr == "" || pass == "" {
fmt.Println("BUGS_USER and BUGS_PASS env vars not found. No authentication is running for /api/listing")
} else {
fs = basicAuth(fs, usr, pass, "Riot bug reports")
}
http.Handle("/api/listing/", fs)
port := os.Args[1]
log.Fatal(http.ListenAndServe(":"+port, nil))
}

View file

@ -40,6 +40,8 @@ import structures$RoomDirectory from './components/structures/RoomDirectory';
structures$RoomDirectory && (module.exports.components['structures.RoomDirectory'] = structures$RoomDirectory); structures$RoomDirectory && (module.exports.components['structures.RoomDirectory'] = structures$RoomDirectory);
import structures$RoomSubList from './components/structures/RoomSubList'; import structures$RoomSubList from './components/structures/RoomSubList';
structures$RoomSubList && (module.exports.components['structures.RoomSubList'] = structures$RoomSubList); structures$RoomSubList && (module.exports.components['structures.RoomSubList'] = structures$RoomSubList);
import structures$RoomSubListHeader from './components/structures/RoomSubListHeader';
structures$RoomSubListHeader && (module.exports.components['structures.RoomSubListHeader'] = structures$RoomSubListHeader);
import structures$SearchBox from './components/structures/SearchBox'; import structures$SearchBox from './components/structures/SearchBox';
structures$SearchBox && (module.exports.components['structures.SearchBox'] = structures$SearchBox); structures$SearchBox && (module.exports.components['structures.SearchBox'] = structures$SearchBox);
import structures$ViewSource from './components/structures/ViewSource'; import structures$ViewSource from './components/structures/ViewSource';

View file

@ -19,9 +19,11 @@ limitations under the License.
var React = require('react'); var React = require('react');
var DragDropContext = require('react-dnd').DragDropContext; var DragDropContext = require('react-dnd').DragDropContext;
var HTML5Backend = require('react-dnd-html5-backend'); var HTML5Backend = require('react-dnd-html5-backend');
var KeyCode = require('matrix-react-sdk/lib/KeyCode');
var sdk = require('matrix-react-sdk') var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher'); var dis = require('matrix-react-sdk/lib/dispatcher');
var VectorConferenceHandler = require('../../VectorConferenceHandler'); var VectorConferenceHandler = require('../../VectorConferenceHandler');
var CallHandler = require("matrix-react-sdk/lib/CallHandler"); var CallHandler = require("matrix-react-sdk/lib/CallHandler");
@ -40,6 +42,10 @@ var LeftPanel = React.createClass({
}; };
}, },
componentWillMount: function() {
this.focusedElement = null;
},
componentDidMount: function() { componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
}, },
@ -62,6 +68,91 @@ var LeftPanel = React.createClass({
} }
}, },
_onFocus: function(ev) {
this.focusedElement = ev.target;
},
_onBlur: function(ev) {
this.focusedElement = null;
},
_onKeyDown: function(ev) {
if (!this.focusedElement) return;
let handled = false;
switch (ev.keyCode) {
case KeyCode.UP:
this._onMoveFocus(true);
handled = true;
break;
case KeyCode.DOWN:
this._onMoveFocus(false);
handled = true;
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
},
_onMoveFocus: function(up) {
var element = this.focusedElement;
// unclear why this isn't needed
// var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending;
// this.focusDirection = up;
var descending = false; // are we currently descending or ascending through the DOM tree?
var classes;
do {
var child = up ? element.lastElementChild : element.firstElementChild;
var sibling = up ? element.previousElementSibling : element.nextElementSibling;
if (descending) {
if (child) {
element = child;
}
else if (sibling) {
element = sibling;
}
else {
descending = false;
element = element.parentElement;
}
}
else {
if (sibling) {
element = sibling;
descending = true;
}
else {
element = element.parentElement;
}
}
if (element) {
classes = element.classList;
if (classes.contains("mx_LeftPanel")) { // we hit the top
element = up ? element.lastElementChild : element.firstElementChild;
descending = true;
}
}
} while(element && !(
classes.contains("mx_RoomTile") ||
classes.contains("mx_SearchBox_search") ||
classes.contains("mx_RoomSubList_ellipsis")));
if (element) {
element.focus();
this.focusedElement = element;
this.focusedDescending = descending;
}
},
_recheckCallElement: function(selectedRoomId) { _recheckCallElement: function(selectedRoomId) {
// if we aren't viewing a room with an ongoing call, but there is an // if we aren't viewing a room with an ongoing call, but there is an
// active call, show the call element - we need to do this to make // active call, show the call element - we need to do this to make
@ -120,7 +211,8 @@ var LeftPanel = React.createClass({
} }
return ( return (
<aside className={classes} style={{ opacity: this.props.opacity }}> <aside className={classes} style={{ opacity: this.props.opacity }}
onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
<SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } /> <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />
{ collapseButton } { collapseButton }
{ callPreview } { callPreview }

View file

@ -23,7 +23,6 @@ var ContentRepo = require("matrix-js-sdk").ContentRepo;
var Modal = require('matrix-react-sdk/lib/Modal'); var Modal = require('matrix-react-sdk/lib/Modal');
var sdk = require('matrix-react-sdk'); var sdk = require('matrix-react-sdk');
var dis = require('matrix-react-sdk/lib/dispatcher'); var dis = require('matrix-react-sdk/lib/dispatcher');
var GeminiScrollbar = require('react-gemini-scrollbar');
var linkify = require('linkifyjs'); var linkify = require('linkifyjs');
var linkifyString = require('linkifyjs/string'); var linkifyString = require('linkifyjs/string');
@ -162,7 +161,7 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list", title: "Failed to get public room list",
description: "The server may be unavailable or overloaded", description: ((err && err.message) ? err.message : "The server may be unavailable or overloaded"),
}); });
}); });
}, },
@ -210,8 +209,8 @@ module.exports = React.createClass({
this.refreshRoomList(); this.refreshRoomList();
console.error("Failed to " + step + ": " + err); console.error("Failed to " + step + ": " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to " + step,
description: "Failed to " + step, description: ((err && err.message) ? err.message : "The server may be unavailable or overloaded"),
}); });
}); });
} }
@ -460,6 +459,17 @@ module.exports = React.createClass({
return fields; return fields;
}, },
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
handleScrollKey: function(ev) {
if (this.scrollPanel) {
this.scrollPanel.handleScrollKey(ev);
}
},
render: function() { render: function() {
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -538,7 +548,7 @@ module.exports = React.createClass({
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox'); const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return ( return (
<div className="mx_RoomDirectory"> <div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" /> <SimpleRoomHeader title="Directory" icon="img/icons-directory.svg"/>
<div className="mx_RoomDirectory_list"> <div className="mx_RoomDirectory_list">
<div className="mx_RoomDirectory_listheader"> <div className="mx_RoomDirectory_listheader">
<DirectorySearchBox <DirectorySearchBox

View file

@ -27,8 +27,11 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs'); var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils'); var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
var RoomSubListHeader = require('./RoomSubListHeader.js');
import Modal from 'matrix-react-sdk/lib/Modal';
// turn this on for drop & drag console debugging galore // turn this on for drag & drop console debugging galore
var debug = false; var debug = false;
const TRUNCATE_AT = 10; const TRUNCATE_AT = 10;
@ -71,14 +74,12 @@ var RoomSubList = React.createClass({
order: React.PropTypes.string.isRequired, order: React.PropTypes.string.isRequired,
// undefined if no room is selected (eg we are showing settings)
selectedRoom: React.PropTypes.string,
startAsHidden: React.PropTypes.bool, startAsHidden: React.PropTypes.bool,
showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded
collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed? collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
onHeaderClick: React.PropTypes.func, onHeaderClick: React.PropTypes.func,
alwaysShowHeader: React.PropTypes.bool, alwaysShowHeader: React.PropTypes.bool,
selectedRoom: React.PropTypes.string,
incomingCall: React.PropTypes.object, incomingCall: React.PropTypes.object,
onShowMoreRooms: React.PropTypes.func, onShowMoreRooms: React.PropTypes.func,
searchFilter: React.PropTypes.string, searchFilter: React.PropTypes.string,
@ -100,13 +101,31 @@ var RoomSubList = React.createClass({
}, },
componentWillMount: function() { componentWillMount: function() {
constantTimeDispatcher.register("RoomSubList.sort", this.props.tagName, this.onSort);
constantTimeDispatcher.register("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order); this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
this._fixUndefinedOrder(this.props.list);
},
componentWillUnmount: function() {
constantTimeDispatcher.unregister("RoomSubList.sort", this.props.tagName, this.onSort);
constantTimeDispatcher.unregister("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
}, },
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
// order the room list appropriately before we re-render // order the room list appropriately before we re-render
//if (debug) console.log("received new props, list = " + newProps.list); //if (debug) console.log("received new props, list = " + newProps.list);
this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order); this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order);
this._fixUndefinedOrder(newProps.list);
},
onSort: function() {
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
// we deliberately don't waste time trying to fix undefined ordering here
},
onRefresh: function() {
this.forceUpdate();
}, },
applySearchFilter: function(list, filter) { applySearchFilter: function(list, filter) {
@ -119,7 +138,7 @@ var RoomSubList = React.createClass({
// The header is collapsable if it is hidden or not stuck // The header is collapsable if it is hidden or not stuck
// The dataset elements are added in the RoomList _initAndPositionStickyHeaders method // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
isCollapsableOnClick: function() { isCollapsableOnClick: function() {
var stuck = this.refs.header.dataset.stuck; var stuck = this.refs.header.refs.header.dataset.stuck;
if (this.state.hidden || stuck === undefined || stuck === "none") { if (this.state.hidden || stuck === undefined || stuck === "none") {
return true; return true;
} else { } else {
@ -142,14 +161,15 @@ var RoomSubList = React.createClass({
this.props.onHeaderClick(isHidden); this.props.onHeaderClick(isHidden);
} else { } else {
// The header is stuck, so the click is to be interpreted as a scroll to the header // The header is stuck, so the click is to be interpreted as a scroll to the header
this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition); this.props.onHeaderClick(this.state.hidden, this.refs.header.refs.header.dataset.originalPosition);
} }
}, },
onRoomTileClick(roomId) { onRoomTileClick(roomId, ev) {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: roomId, room_id: roomId,
clear_search: (ev && (ev.keyCode == 13 || ev.keyCode == 32)),
}); });
}, },
@ -211,9 +231,6 @@ var RoomSubList = React.createClass({
if (order === "manual") comparator = this.manualComparator; if (order === "manual") comparator = this.manualComparator;
if (order === "recent") comparator = this.recentsComparator; if (order === "recent") comparator = this.recentsComparator;
// Fix undefined orders here, and make sure the backend gets updated as well
this._fixUndefinedOrder(list);
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list); //if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
this.setState({ sortedList: list.sort(comparator) }); this.setState({ sortedList: list.sort(comparator) });
}, },
@ -248,10 +265,9 @@ var RoomSubList = React.createClass({
if (badges) { if (badges) {
result[0] += notificationCount; result[0] += notificationCount;
if (highlight) {
result[1] = true;
}
} }
result[1] |= highlight;
} }
return result; return result;
}, [0, false]); }, [0, false]);
@ -359,7 +375,6 @@ var RoomSubList = React.createClass({
var self = this; var self = this;
var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile"); var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile");
return this.state.sortedList.map(function(room) { return this.state.sortedList.map(function(room) {
var selected = room.roomId == self.props.selectedRoom;
// XXX: is it evil to pass in self as a prop to RoomTile? // XXX: is it evil to pass in self as a prop to RoomTile?
return ( return (
<DNDRoomTile <DNDRoomTile
@ -367,9 +382,7 @@ var RoomSubList = React.createClass({
roomSubList={ self } roomSubList={ self }
key={ room.roomId } key={ room.roomId }
collapsed={ self.props.collapsed || false} collapsed={ self.props.collapsed || false}
selected={ selected } selectedRoom={ self.props.selectedRoom }
unread={ Unread.doesRoomHaveUnreadMessages(room) }
highlight={ room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites' }
isInvite={ self.props.label === 'Invites' } isInvite={ self.props.label === 'Invites' }
refreshSubList={ self._updateSubListCount } refreshSubList={ self._updateSubListCount }
incomingCall={ null } incomingCall={ null }
@ -379,70 +392,6 @@ var RoomSubList = React.createClass({
}); });
}, },
_getHeaderJsx: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var subListNotifications = this.roomNotificationCount();
var subListNotifCount = subListNotifications[0];
var subListNotifHighlight = subListNotifications[1];
var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
var chevronClasses = classNames({
'mx_RoomSubList_chevron': true,
'mx_RoomSubList_chevronRight': this.state.hidden,
'mx_RoomSubList_chevronDown': !this.state.hidden,
});
var badgeClasses = classNames({
'mx_RoomSubList_badge': true,
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
});
var badge;
if (subListNotifCount > 0) {
badge = <div className={badgeClasses}>{ FormattingUtils.formatCount(subListNotifCount) }</div>;
}
// When collapsed, allow a long hover on the header to show user
// the full tag name and room count
var title;
if (this.props.collapsed) {
title = this.props.label;
if (roomCount !== '') {
title += " [" + roomCount + "]";
}
}
var incomingCall;
if (this.props.incomingCall) {
var self = this;
// Check if the incoming call is for this section
var incomingCallRoom = this.props.list.filter(function(room) {
return self.props.incomingCall.roomId === room.roomId;
});
if (incomingCallRoom.length === 1) {
var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
incomingCall = <IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={ this.props.incomingCall }/>;
}
}
var tabindex = this.props.searchFilter === "" ? "0" : "-1";
return (
<div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
<AccessibleButton onClick={ this.onClick } className="mx_RoomSubList_label" tabIndex={tabindex}>
{ this.props.collapsed ? '' : this.props.label }
<div className="mx_RoomSubList_roomCount">{ roomCount }</div>
<div className={chevronClasses}></div>
{ badge }
{ incomingCall }
</AccessibleButton>
</div>
);
},
_createOverflowTile: function(overflowCount, totalCount) { _createOverflowTile: function(overflowCount, totalCount) {
var content = <div className="mx_RoomSubList_chevronDown"></div>; var content = <div className="mx_RoomSubList_chevronDown"></div>;
@ -498,7 +447,7 @@ var RoomSubList = React.createClass({
// gets triggered and another list is passed in. Doing it one at a time means that // gets triggered and another list is passed in. Doing it one at a time means that
// we always correctly calculate the highest order for the list - stops multiple // we always correctly calculate the highest order for the list - stops multiple
// rooms getting the same order. This is only really relevant for the first time this // rooms getting the same order. This is only really relevant for the first time this
// is run with historical room tag data, after that there should only be undefined // is run with historical room tag data, after that there should only be one undefined
// in the list at a time anyway. // in the list at a time anyway.
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) { if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) {
@ -508,8 +457,8 @@ var RoomSubList = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + self.props.tagName + " to room" + err); console.error("Failed to add tag " + self.props.tagName + " to room" + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to add tag " + self.props.tagName + " to room",
description: "Failed to add tag " + self.props.tagName + " to room", description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
break; break;
@ -532,6 +481,16 @@ var RoomSubList = React.createClass({
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>; target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
} }
var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
var isIncomingCallRoom;
if (this.props.incomingCall) {
// Check if the incoming call is for this section
isIncomingCallRoom = this.props.list.find(room=>{
return this.props.incomingCall.roomId === room.roomId;
}) ? true : false;
}
if (this.state.sortedList.length > 0 || this.props.editable) { if (this.state.sortedList.length > 0 || this.props.editable) {
var subList; var subList;
var classes = "mx_RoomSubList"; var classes = "mx_RoomSubList";
@ -550,7 +509,19 @@ var RoomSubList = React.createClass({
return connectDropTarget( return connectDropTarget(
<div> <div>
{ this._getHeaderJsx() } <RoomSubListHeader
ref='header'
label={ this.props.label }
tagName={ this.props.tagName }
roomCount={ roomCount }
collapsed={ this.props.collapsed }
hidden={ this.state.hidden }
incomingCall={ this.props.incomingCall }
isIncomingCallRoom={ isIncomingCallRoom }
roomNotificationCount={ this.roomNotificationCount() }
onClick={ this.onClick }
onHeaderClick={ this.props.onHeaderClick }
/>
{ subList } { subList }
</div> </div>
); );
@ -559,7 +530,20 @@ var RoomSubList = React.createClass({
var Loader = sdk.getComponent("elements.Spinner"); var Loader = sdk.getComponent("elements.Spinner");
return ( return (
<div className="mx_RoomSubList"> <div className="mx_RoomSubList">
{ this.props.alwaysShowHeader ? this._getHeaderJsx() : undefined } { this.props.alwaysShowHeader ?
<RoomSubListHeader
ref='header'
label={ this.props.label }
tagName={ this.props.tagName }
roomCount={ roomCount }
collapsed={ this.props.collapsed }
hidden={ this.state.hidden }
isIncomingCallRoom={ isIncomingCallRoom }
roomNotificationCount={ this.roomNotificationCount() }
onClick={ this.onClick }
onHeaderClick={ this.props.onHeaderClick }
/>
: undefined }
{ (this.props.showSpinner && !this.state.hidden) ? <Loader /> : undefined } { (this.props.showSpinner && !this.state.hidden) ? <Loader /> : undefined }
</div> </div>
); );

View file

@ -0,0 +1,121 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var classNames = require('classnames');
var sdk = require('matrix-react-sdk')
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
module.exports = React.createClass({
displayName: 'RoomSubListHeader',
propTypes: {
label: React.PropTypes.string.isRequired,
tagName: React.PropTypes.string,
roomCount: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]),
collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
incomingCall: React.PropTypes.object,
isIncomingCallRoom: React.PropTypes.bool,
roomNotificationCount: React.PropTypes.array,
hidden: React.PropTypes.bool,
onClick: React.PropTypes.func,
onHeaderClick: React.PropTypes.func,
},
getDefaultProps: function() {
return {
onHeaderClick: function() {}, // NOP
};
},
componentWillMount: function() {
// constantTimeDispatcher.register("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
},
componentWillUnmount: function() {
// constantTimeDispatcher.unregister("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
},
// onRefresh: function() {
// this.forceUpdate();
// },
render: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var subListNotifications = this.props.roomNotificationCount;
var subListNotifCount = subListNotifications[0];
var subListNotifHighlight = subListNotifications[1];
var chevronClasses = classNames({
'mx_RoomSubList_chevron': true,
'mx_RoomSubList_chevronRight': this.props.hidden,
'mx_RoomSubList_chevronDown': !this.props.hidden,
});
var badgeClasses = classNames({
'mx_RoomSubList_badge': true,
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
});
var badge;
if (subListNotifCount > 0) {
badge = <div className={badgeClasses}>{ FormattingUtils.formatCount(subListNotifCount) }</div>;
}
else if (subListNotifHighlight) {
badge = <div className={badgeClasses}>!</div>;
}
// When collapsed, allow a long hover on the header to show user
// the full tag name and room count
var title;
var roomCount = this.props.roomCount;
if (this.props.collapsed) {
title = this.props.label;
if (roomCount !== '') {
title += " [" + roomCount + "]";
}
}
var incomingCall;
if (this.props.isIncomingCallRoom) {
var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
incomingCall = <IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={ this.props.incomingCall }/>;
}
return (
<div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
<AccessibleButton onClick={ this.props.onClick } className="mx_RoomSubList_label" tabIndex="0">
{ this.props.collapsed ? '' : this.props.label }
<div className="mx_RoomSubList_roomCount">{ roomCount }</div>
<div className={chevronClasses}></div>
{ badge }
</AccessibleButton>
{ incomingCall }
</div>
);
},
});

View file

@ -21,6 +21,7 @@ var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher'); var dis = require('matrix-react-sdk/lib/dispatcher');
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc'); var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
var KeyCode = require('matrix-react-sdk/lib/KeyCode');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'SearchBox', displayName: 'SearchBox',
@ -38,25 +39,23 @@ module.exports = React.createClass({
componentDidMount: function() { componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
document.addEventListener('keydown', this._onKeyDown);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
document.removeEventListener('keydown', this._onKeyDown);
}, },
onAction: function(payload) { onAction: function(payload) {
// Disabling this as I find it really really annoying, and was used to the
// previous behaviour - see https://github.com/vector-im/riot-web/issues/3348
/*
switch (payload.action) { switch (payload.action) {
// Clear up the text field when a room is selected. // Clear up the text field when a room is selected.
case 'view_room': case 'view_room':
if (this.refs.search) { if (payload.clear_search && this.refs.search) {
this._clearSearch(); this._clearSearch();
} }
break; break;
} }
*/
}, },
onChange: function() { onChange: function() {
@ -90,6 +89,38 @@ module.exports = React.createClass({
this.onChange(); this.onChange();
}, },
_onKeyDown: function(ev) {
let handled = false;
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
let ctrlCmdOnly;
if (isMac) {
ctrlCmdOnly = ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
} else {
ctrlCmdOnly = ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
}
switch (ev.keyCode) {
case KeyCode.ESCAPE:
this._clearSearch();
dis.dispatch({action: 'focus_composer'});
break;
case KeyCode.KEY_K:
if (ctrlCmdOnly) {
if (this.refs.search) {
this.refs.search.focus();
this.refs.search.select();
}
handled = true;
}
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
},
render: function() { render: function() {
var TintableSvg = sdk.getComponent('elements.TintableSvg'); var TintableSvg = sdk.getComponent('elements.TintableSvg');

View file

@ -71,7 +71,7 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + tagNameOff + " from room", title: "Failed to remove tag " + tagNameOff + " from room",
description: err.toString() description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
} }
@ -88,7 +88,7 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + tagNameOn + " to room", title: "Failed to add tag " + tagNameOn + " to room",
description: err.toString() description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
} }
@ -149,7 +149,7 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Failed to set Direct Message status of room", title: "Failed to set Direct Message status of room",
description: err.toString() description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
}, },
@ -187,8 +187,8 @@ module.exports = React.createClass({
var errCode = err.errcode || "unknown error code"; var errCode = err.errcode || "unknown error code";
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: `Failed to forget room (${errCode})`,
description: `Failed to forget room (${errCode})` description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import rageshake from '../../../vector/rageshake'; import SdkConfig from 'matrix-react-sdk/lib/SdkConfig';
export default class BugReportDialog extends React.Component { export default class BugReportDialog extends React.Component {
constructor(props, context) { constructor(props, context) {
@ -26,11 +26,18 @@ export default class BugReportDialog extends React.Component {
busy: false, busy: false,
err: null, err: null,
text: "", text: "",
progress: null,
}; };
this._unmounted = false;
this._onSubmit = this._onSubmit.bind(this); this._onSubmit = this._onSubmit.bind(this);
this._onCancel = this._onCancel.bind(this); this._onCancel = this._onCancel.bind(this);
this._onTextChange = this._onTextChange.bind(this); this._onTextChange = this._onTextChange.bind(this);
this._onSendLogsChange = this._onSendLogsChange.bind(this); this._onSendLogsChange = this._onSendLogsChange.bind(this);
this._sendProgressCallback = this._sendProgressCallback.bind(this);
}
componentWillUnmount() {
this._unmounted = true;
} }
_onCancel(ev) { _onCancel(ev) {
@ -46,12 +53,27 @@ export default class BugReportDialog extends React.Component {
}); });
return; return;
} }
this.setState({ busy: true, err: null }); this.setState({ busy: true, progress: null, err: null });
rageshake.sendBugReport(userText, sendLogs).then(() => { this._sendProgressCallback("Loading bug report module");
this.setState({ busy: false });
this.props.onFinished(false); require(['../../../vector/submit-rageshake'], (s) => {
}, (err) => { s(SdkConfig.get().bug_report_endpoint_url, {
this.setState({ busy: false, err: `Failed: ${err.message}` }); userText: userText,
sendLogs: sendLogs,
progressCallback: this._sendProgressCallback,
}).then(() => {
if (!this._unmounted) {
this.setState({ busy: false, progress: null });
this.props.onFinished(false);
}
}, (err) => {
if (!this._unmounted) {
this.setState({
busy: false, progress: null,
err: `Failed to send report: ${err.message}`,
});
}
});
}); });
} }
@ -63,6 +85,13 @@ export default class BugReportDialog extends React.Component {
this.setState({ sendLogs: ev.target.checked }); this.setState({ sendLogs: ev.target.checked });
} }
_sendProgressCallback(progress) {
if (this._unmounted) {
return;
}
this.setState({progress: progress});
}
render() { render() {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -73,8 +102,6 @@ export default class BugReportDialog extends React.Component {
</div>; </div>;
} }
const okLabel = this.state.busy ? <Loader /> : 'Send';
let cancelButton = null; let cancelButton = null;
if (!this.state.busy) { if (!this.state.busy) {
cancelButton = <button onClick={this._onCancel}> cancelButton = <button onClick={this._onCancel}>
@ -82,6 +109,16 @@ export default class BugReportDialog extends React.Component {
</button>; </button>;
} }
let progress = null;
if (this.state.busy) {
progress = (
<div className="progress">
<Loader />
{this.state.progress} ...
</div>
);
}
return ( return (
<div className="mx_BugReportDialog"> <div className="mx_BugReportDialog">
<div className="mx_Dialog_title"> <div className="mx_Dialog_title">
@ -104,6 +141,7 @@ export default class BugReportDialog extends React.Component {
<input type="checkbox" checked={this.state.sendLogs} <input type="checkbox" checked={this.state.sendLogs}
onChange={this._onSendLogsChange} id="mx_BugReportDialog_logs"/> onChange={this._onSendLogsChange} id="mx_BugReportDialog_logs"/>
<label htmlFor="mx_BugReportDialog_logs">Send logs</label> <label htmlFor="mx_BugReportDialog_logs">Send logs</label>
{progress}
{error} {error}
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
@ -111,8 +149,9 @@ export default class BugReportDialog extends React.Component {
className="mx_Dialog_primary danger" className="mx_Dialog_primary danger"
onClick={this._onSubmit} onClick={this._onSubmit}
autoFocus={true} autoFocus={true}
disabled={this.state.busy}
> >
{okLabel} Send
</button> </button>
{cancelButton} {cancelButton}

View file

@ -176,7 +176,7 @@ module.exports = React.createClass({
{ this.getName() } { this.getName() }
</div> </div>
{ eventMeta } { eventMeta }
<a className="mx_ImageView_link" href={ this.props.src } target="_blank" rel="noopener"> <a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener">
<div className="mx_ImageView_download"> <div className="mx_ImageView_download">
Download this file<br/> Download this file<br/>
<span className="mx_ImageView_size">{ size_res }</span> <span className="mx_ImageView_size">{ size_res }</span>

View file

@ -18,6 +18,9 @@ limitations under the License.
var React = require('react'); var React = require('react');
const i = [1, 2, 3, 4, 5][Math.floor(Math.random() * 5)];
const DEFAULT_LOGO_URI = "img/logos/riot-logo-" + i + ".svg";
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'VectorLoginHeader', displayName: 'VectorLoginHeader',
statics: { statics: {
@ -30,7 +33,7 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div className="mx_Login_logo"> <div className="mx_Login_logo">
<img src={this.props.icon || "img/logo.png"} alt="Riot"/> <img src={this.props.icon || DEFAULT_LOGO_URI} alt="Riot"/>
</div> </div>
); );
} }

View file

@ -25,7 +25,7 @@ module.exports = React.createClass({
render: function() { render: function() {
var date = new Date(this.props.ts); var date = new Date(this.props.ts);
return ( return (
<span className="mx_MessageTimestamp"> <span className="mx_MessageTimestamp" title={ DateUtils.formatDate(date) }>
{ DateUtils.formatTime(date) } { DateUtils.formatTime(date) }
</span> </span>
); );

View file

@ -90,8 +90,8 @@ var roomTileSource = {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set direct chat tag " + err); console.error("Failed to set direct chat tag " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to set direct chat tag",
description: "Failed to set direct chat tag", description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
return; return;
@ -115,8 +115,8 @@ var roomTileSource = {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to remove tag " + prevTag + " from room: " + err); console.error("Failed to remove tag " + prevTag + " from room: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to remove tag " + prevTag + " from room",
description: "Failed to remove tag " + prevTag + " from room", description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
} }
@ -137,8 +137,8 @@ var roomTileSource = {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + newTag + " to room: " + err); console.error("Failed to add tag " + newTag + " to room: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to add tag " + newTag + " to room",
description: "Failed to add tag " + newTag + " to room", description: ((err && err.message) ? err.message : "Operation failed"),
}); });
}); });
} }

View file

@ -22,20 +22,12 @@ module.exports = React.createClass({
displayName: 'RoomDropTarget', displayName: 'RoomDropTarget',
render: function() { render: function() {
if (this.props.placeholder) { return (
return ( <div className="mx_RoomDropTarget">
<div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder"> <div className="mx_RoomDropTarget_label">
{ this.props.label }
</div> </div>
); </div>
} );
else {
return (
<div className="mx_RoomDropTarget">
<div className="mx_RoomDropTarget_label">
{ this.props.label }
</div>
</div>
);
}
} }
}); });

View file

@ -240,8 +240,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to change settings: " + error); console.error("Failed to change settings: " + error);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to change settings",
description: "Failed to change settings", description: ((error && error.message) ? error.message : "Operation failed"),
onFinished: self._refreshFromServer onFinished: self._refreshFromServer
}); });
}); });
@ -310,8 +310,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Can't update user notification settings: " + error); console.error("Can't update user notification settings: " + error);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Can't update user notification settings",
description: "Can't update user notification settings", description: ((error && error.message) ? error.message : "Operation failed"),
onFinished: self._refreshFromServer onFinished: self._refreshFromServer
}); });
}); });
@ -352,8 +352,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to update keywords: " + error); console.error("Failed to update keywords: " + error);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Error", title: "Failed to update keywords",
description: "Failed to update keywords", description: ((error && error.message) ? error.message : "Operation failed"),
onFinished: self._refreshFromServer onFinished: self._refreshFromServer
}); });
} }

View file

@ -65,7 +65,7 @@ input[type=text].error, input[type=password].error {
border: 1px solid $warning-color; border: 1px solid $warning-color;
} }
input[type=text]:focus, textarea:focus { input[type=text]:focus, input[type=password]:focus, textarea:focus {
border: 1px solid $accent-color; border: 1px solid $accent-color;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
@ -225,6 +225,10 @@ textarea {
vertical-align: middle; vertical-align: middle;
} }
.mx_Dialog button:focus, .mx_Dialog input[type="submit"]:focus {
filter: brightness($focus-brightness);
}
.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary { .mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary {
color: $accent-fg-color; color: $accent-fg-color;
background-color: $accent-color; background-color: $accent-color;

View file

@ -19,6 +19,7 @@
@import "./matrix-react-sdk/views/dialogs/_EncryptedEventDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_EncryptedEventDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_SetDisplayNameDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_SetDisplayNameDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_UnknownDeviceDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_UnknownDeviceDialog.scss";
@import "./matrix-react-sdk/views/elements/_AccessibleButton.scss";
@import "./matrix-react-sdk/views/elements/_AddressSelector.scss"; @import "./matrix-react-sdk/views/elements/_AddressSelector.scss";
@import "./matrix-react-sdk/views/elements/_AddressTile.scss"; @import "./matrix-react-sdk/views/elements/_AddressTile.scss";
@import "./matrix-react-sdk/views/elements/_DirectorySearchBox.scss"; @import "./matrix-react-sdk/views/elements/_DirectorySearchBox.scss";
@ -28,6 +29,7 @@
@import "./matrix-react-sdk/views/elements/_RichText.scss"; @import "./matrix-react-sdk/views/elements/_RichText.scss";
@import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss"; @import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
@import "./matrix-react-sdk/views/login/_ServerConfig.scss"; @import "./matrix-react-sdk/views/login/_ServerConfig.scss";
@import "./matrix-react-sdk/views/messages/_MEmoteBody.scss";
@import "./matrix-react-sdk/views/messages/_MImageBody.scss"; @import "./matrix-react-sdk/views/messages/_MImageBody.scss";
@import "./matrix-react-sdk/views/messages/_MNoticeBody.scss"; @import "./matrix-react-sdk/views/messages/_MNoticeBody.scss";
@import "./matrix-react-sdk/views/messages/_MTextBody.scss"; @import "./matrix-react-sdk/views/messages/_MTextBody.scss";

View file

@ -160,7 +160,8 @@ hr.mx_RoomView_myReadMarker {
border-bottom: solid 1px $accent-color; border-bottom: solid 1px $accent-color;
margin-top: 0px; margin-top: 0px;
position: relative; position: relative;
top: 5px; top: -1px;
z-index: 1;
} }
.mx_RoomView_statusArea { .mx_RoomView_statusArea {
@ -171,7 +172,7 @@ hr.mx_RoomView_myReadMarker {
max-height: 0px; max-height: 0px;
background-color: $primary-bg-color; background-color: $primary-bg-color;
z-index: 1000; z-index: 5;
overflow: hidden; overflow: hidden;
-webkit-transition: all .2s ease-out; -webkit-transition: all .2s ease-out;
@ -259,4 +260,4 @@ hr.mx_RoomView_myReadMarker {
.mx_RoomView_ongoingConfCallNotification a { .mx_RoomView_ongoingConfCallNotification a {
color: $accent-fg-color ! important; color: $accent-fg-color ! important;
} }

View file

@ -176,6 +176,15 @@ limitations under the License.
cursor: pointer; cursor: pointer;
} }
.mx_UserSettings_phoneSection {
display:table;
}
.mx_UserSettings_phoneCountry {
width: 70px;
display: table-cell;
}
input.mx_UserSettings_phoneNumberField { input.mx_UserSettings_phoneNumberField {
margin-left: 3px; margin-left: 3px;
width: 172px; width: 172px;
@ -213,3 +222,9 @@ input.mx_UserSettings_phoneNumberField {
.mx_UserSettings_avatarPicker_edit > input { .mx_UserSettings_avatarPicker_edit > input {
display: none; display: none;
} }
.mx_UserSettings_advanced_spoiler {
cursor: pointer;
color: $accent-color;
word-break: break-all;
}

View file

@ -42,7 +42,8 @@ limitations under the License.
.mx_Login_logo { .mx_Login_logo {
text-align: center; text-align: center;
height: 195px; height: 150px;
margin-bottom: 45px;
} }
.mx_Login_logo img { .mx_Login_logo img {
@ -66,10 +67,6 @@ limitations under the License.
margin-bottom: 14px; margin-bottom: 14px;
} }
.mx_Login_username {
margin-bottom: 0px;
}
.mx_Login_fieldLabel { .mx_Login_fieldLabel {
margin-top: -10px; margin-top: -10px;
margin-left: 8px; margin-left: 8px;
@ -167,16 +164,82 @@ limitations under the License.
margin-bottom: 12px; margin-bottom: 12px;
} }
.mx_Login_phoneSection { .mx_Login_type_container {
display: table; display: flex;
margin-bottom: 14px;
}
.mx_Login_type_label {
flex-grow: 1;
line-height: 35px;
}
.mx_Login_type_dropdown {
width: 125px;
align-self: flex-end;
}
.mx_Login_field_group {
display: flex;
}
.mx_Login_field_prefix {
height: 33px;
padding: 0px 5px;
line-height: 33px;
background-color: #eee;
border: 1px solid #c7c7c7;
border-right: 0px;
border-radius: 3px 0px 0px 3px;
text-align: center;
}
.mx_Login_field_suffix {
height: 33px;
padding: 0px 5px;
line-height: 33px;
background-color: #eee;
border: 1px solid #c7c7c7;
border-left: 0px;
border-radius: 0px 3px 3px 0px;
text-align: center;
flex-grow: 1;
}
.mx_Login_username {
flex-shrink: 1;
min-width: 0px;
border-radius: 3px;
}
.mx_Login_field_has_prefix {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
.mx_Login_field_has_suffix {
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
} }
.mx_Login_phoneCountry { .mx_Login_phoneCountry {
display: table-cell; margin-bottom: 14px;
width: 70px;
} }
.mx_Login_phoneNumberField { .mx_Login_phoneCountry .mx_Dropdown_option {
width: 210px; /*
margin-left: 3px; To match height of mx_Login_field
33px + 2px border from mx_Dropdown_option = 35px
*/
height: 33px;
line-height: 33px;
}
.mx_Login_phoneCountry .mx_Dropdown_option img {
margin: 4px;
vertical-align: top;
} }

View file

@ -0,0 +1,21 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_AccessibleButton:focus {
outline: 0;
filter: brightness($focus-brightness);
}

View file

@ -27,6 +27,15 @@ limitations under the License.
user-select: none; user-select: none;
} }
.mx_Dropdown_input:focus {
border-color: $accent-color;
}
/* Disable dropdown highlight on focus */
.mx_Dropdown_input.mx_AccessibleButton:focus {
filter: none;
}
.mx_Dropdown_arrow { .mx_Dropdown_arrow {
border-color: $primary-fg-color transparent transparent; border-color: $primary-fg-color transparent transparent;
border-style: solid; border-style: solid;
@ -74,7 +83,7 @@ input.mx_Dropdown_option, input.mx_Dropdown_option:focus {
border: 1px solid $accent-color; border: 1px solid $accent-color;
background-color: $primary-bg-color; background-color: $primary-bg-color;
max-height: 200px; max-height: 200px;
overflow-y: scroll; overflow-y: auto;
} }
.mx_Dropdown_menu .mx_Dropdown_option_highlight { .mx_Dropdown_menu .mx_Dropdown_option_highlight {

View file

@ -20,6 +20,7 @@ limitations under the License.
.mx_TextualEvent.mx_MemberEventListSummary_summary { .mx_TextualEvent.mx_MemberEventListSummary_summary {
font-size: 14px; font-size: 14px;
display: inline-flex;
} }
.mx_MemberEventListSummary_avatars { .mx_MemberEventListSummary_avatars {

View file

@ -0,0 +1,19 @@
/*
Copyright 2017 Vector Creations Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MEmoteBody_sender {
cursor: pointer;
}

View file

@ -133,10 +133,18 @@ limitations under the License.
.mx_EventTile_redacted .mx_EventTile_line .mx_UnknownBody { .mx_EventTile_redacted .mx_EventTile_line .mx_UnknownBody {
display: block; display: block;
width: 100%; width: 100%;
max-width: 300px; height: 36px;
height: 24px; background-image: $event-redacted-img;
border-radius: 4px; background-repeat: no-repeat;
background-color: black; background-size: contain;
}
.mx_EventTile.mx_EventTile_redacted .mx_EventTile_line {
/*
Prevent changing colour of the background because
$event-redacted-img matches $primary-bg-color
*/
background-color: initial !important;
} }
.mx_EventTile_highlight, .mx_EventTile_highlight,
@ -210,8 +218,8 @@ limitations under the License.
} }
.mx_EventTile_continuation .mx_EventTile_readAvatars, .mx_EventTile_continuation .mx_EventTile_readAvatars,
.mx_EventTile_info .mx_EventTile_readAvatars .mx_EventTile_info .mx_EventTile_readAvatars,
{ .mx_EventTile_emote .mx_EventTile_readAvatars {
top: 7px; top: 7px;
} }
@ -323,6 +331,14 @@ limitations under the License.
font-family: inherit ! important; font-family: inherit ! important;
} }
/* Make h1 and h2 the same size as h3. */
.mx_EventTile_content .markdown-body h1,
.mx_EventTile_content .markdown-body h2
{
font-size: 1.5em;
}
.mx_EventTile_content .markdown-body a { .mx_EventTile_content .markdown-body a {
color: $accent-color; color: $accent-color;
} }

View file

@ -31,6 +31,7 @@ limitations under the License.
margin-left: -2px; margin-left: -2px;
order: 1; order: 1;
flex: 1; flex: 1;
overflow: hidden;
} }
.mx_RoomHeader_spinner { .mx_RoomHeader_spinner {
@ -95,6 +96,12 @@ limitations under the License.
float: right; float: right;
} }
.mx_RoomHeader_simpleHeader .mx_RoomHeader_icon {
margin-left: 14px;
margin-right: 24px;
vertical-align: -4px;
}
.mx_RoomHeader_name { .mx_RoomHeader_name {
vertical-align: middle; vertical-align: middle;
width: 100%; width: 100%;

View file

@ -156,7 +156,7 @@ limitations under the License.
} }
.mx_RoomTile:focus { .mx_RoomTile:focus {
outline: 0; filter: none ! important;
background-color: $roomtile-focused-bg-color; background-color: $roomtile-focused-bg-color;
} }

View file

@ -17,14 +17,15 @@ limitations under the License.
.mx_TopUnreadMessagesBar { .mx_TopUnreadMessagesBar {
margin: auto; /* centre horizontally */ margin: auto; /* centre horizontally */
max-width: 960px; max-width: 960px;
padding-top: 5px; padding-top: 10px;
padding-bottom: 5px; padding-bottom: 10px;
border-bottom: 1px solid $primary-hairline-color; border-bottom: 1px solid $primary-hairline-color;
} }
.mx_TopUnreadMessagesBar_scrollUp { .mx_TopUnreadMessagesBar_scrollUp {
display: inline; display: inline;
cursor: pointer; cursor: pointer;
text-decoration: underline;
} }
.mx_TopUnreadMessagesBar_scrollUp img { .mx_TopUnreadMessagesBar_scrollUp img {

View file

@ -15,6 +15,8 @@ $accent-color: #76CFA6;
$selection-fg-color: $primary-bg-color; $selection-fg-color: $primary-bg-color;
$focus-brightness: 125%;
// red warning colour // red warning colour
$warning-color: #ff0064; $warning-color: #ff0064;
@ -93,6 +95,9 @@ $event-encrypting-color: #abddbc;
$event-sending-color: #ddd; $event-sending-color: #ddd;
$event-notsent-color: #f44; $event-notsent-color: #f44;
// event redaction
$event-redacted-img: url('../../img/redacted.jpg');
// event timestamp // event timestamp
$event-timestamp-color: #acacac; $event-timestamp-color: #acacac;

View file

@ -15,6 +15,8 @@ $accent-color: #76CFA6;
$selection-fg-color: $primary-fg-color; $selection-fg-color: $primary-fg-color;
$focus-brightness: 200%;
// red warning colour // red warning colour
$warning-color: #ff0064; $warning-color: #ff0064;
@ -93,6 +95,9 @@ $event-encrypting-color: rgba(171, 221, 188, 0.4);
$event-sending-color: #888; $event-sending-color: #888;
$event-notsent-color: #f44; $event-notsent-color: #f44;
// event redaction
$event-redacted-img: url('../../img/redacted-dark.jpg');
// event timestamp // event timestamp
$event-timestamp-color: #acacac; $event-timestamp-color: #acacac;

View file

@ -21,12 +21,17 @@ limitations under the License.
margin-right: auto; margin-right: auto;
margin-bottom: 12px; margin-bottom: 12px;
color: $primary-fg-color; color: $primary-fg-color;
word-break: break-word;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.mx_RoomDirectory .mx_RoomHeader_simpleHeader {
margin-left: 0px;
}
.mx_RoomDirectory_list { .mx_RoomDirectory_list {
flex: 1; flex: 1;

View file

@ -33,11 +33,6 @@ limitations under the License.
margin-left: 10px; margin-left: 10px;
} }
.mx_RoomDropTarget_placeholder {
padding-top: 1px;
padding-bottom: 1px;
}
.mx_RoomDropTarget_label { .mx_RoomDropTarget_label {
position: relative; position: relative;
margin-top: 3px; margin-top: 3px;

View file

@ -1,21 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: sketchtool 3.8.3 (29802) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>E34C64ED-EBD7-49B6-BDD9-CB729162705A</title> <title>icons_directory</title>
<desc>Created with sketchtool.</desc> <desc>Created with Sketch.</desc>
<defs> <defs></defs>
<rect id="path-1" x="6" y="10" width="13" height="8" rx="1"></rect>
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="13" height="8" fill="white">
<use xlink:href="#path-1"></use>
</mask>
</defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Room-list-Copy-3" transform="translate(-58.000000, -726.000000)"> <g id="Left-panel" transform="translate(-83.000000, -726.000000)">
<g id="icons_directory" transform="translate(58.000000, 726.000000)"> <g id="icons_directory">
<path d="M12.5,25 C19.4035594,25 25,19.4035594 25,12.5 C25,5.59644063 19.4035594,0 12.5,0 C5.59644063,0 0,5.59644063 0,12.5 C0,19.4035594 5.59644063,25 12.5,25 Z" id="Oval-1-Copy-7" fill="#76CFA6"></path> <g transform="translate(83.000000, 726.000000)">
<use id="Rectangle-9" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="2" opacity="0.8" xlink:href="#path-1"></use> <path d="M12.5,25 C19.4035594,25 25,19.4035594 25,12.5 C25,5.59644063 19.4035594,0 12.5,0 C5.59644063,0 0,5.59644063 0,12.5 C0,19.4035594 5.59644063,25 12.5,25 Z" id="Oval-1-Copy-7" fill="#76CFA6"></path>
<path d="M6,9 L6,6.99895656 C6,6.44724809 6.45097518,6 6.99077797,6 L11.009222,6 C11.5564136,6 12,6.44386482 12,7 L12,8 L17.9970707,8 C18.5509732,8 19,8.45318604 19,9 L19,9 L6,9 Z" id="Path-Copy" fill="#FFFFFF" opacity="0.6"></path> <g id="Lines" transform="translate(6.000000, 7.000000)" stroke="#FFFFFF" stroke-linecap="round">
<path d="M4,5.5 L9,5.5" id="Line"></path>
<path d="M4,1.5 L13,1.5" id="Line-Copy-4"></path>
<path d="M0,1.5 L2,1.5" id="Line" opacity="0.6"></path>
<path d="M0,5.5 L2,5.5" id="Line" opacity="0.6"></path>
<path d="M4,9.5 L11,9.5" id="Line-Copy-6"></path>
<path d="M0,9.5 L2,9.5" id="Line-Copy-3" opacity="0.6"></path>
</g>
</g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>Slice 1</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="scrollup">
<g id="02-Chat" transform="translate(12.000000, 12.000000) scale(-1, 1) rotate(-180.000000) translate(-12.000000, -12.000000) ">
<g id="02_7-Chat-new-messages">
<g id="icon_newmessages">
<circle id="Oval-1909" fill-opacity="0.5" fill="#454545" fill-rule="nonzero" cx="12" cy="12" r="12"></circle>
<circle id="Oval" stroke="#FFFFFF" cx="12" cy="12" r="7"></circle>
<circle id="Oval" stroke="#FFFFFF" cx="12" cy="12" r="4"></circle>
<circle id="Oval" fill="#FFFFFF" cx="12" cy="12" r="1"></circle>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -37,8 +37,19 @@
<body style="height: 100%;"> <body style="height: 100%;">
<section id="matrixchat" style="height: 100%;"></section> <section id="matrixchat" style="height: 100%;"></section>
<noscript>Sorry, Riot requires JavaScript to be enabled.</noscript> <noscript>Sorry, Riot requires JavaScript to be enabled.</noscript>
<% for (var i=0; i < htmlWebpackPlugin.files.js.length; i++) {%> <% for (var i=0; i < htmlWebpackPlugin.files.js.length; i++) {
<script src="<%= htmlWebpackPlugin.files.js[i] %>"></script> // Not a particularly graceful way of not putting the indexeddb worker script
// into the main page
if (_.endsWith(htmlWebpackPlugin.files.js[i], 'indexeddb-worker.js')) {
%>
<script>
window.vector_indexeddb_worker_script = '<%= htmlWebpackPlugin.files.js[i] %>';
</script>
<%
continue;
}
%>
<script src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
<% } %> <% } %>
<img src="img/warning.svg" width="24" height="23" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/> <img src="img/warning.svg" width="24" height="23" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
<audio id="messageAudio"> <audio id="messageAudio">

View file

@ -68,11 +68,15 @@ import url from 'url';
import {parseQs, parseQsFromFragment} from './url_utils'; import {parseQs, parseQsFromFragment} from './url_utils';
import Platform from './platform'; import Platform from './platform';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
var lastLocationHashSet = null; var lastLocationHashSet = null;
var CallHandler = require("matrix-react-sdk/lib/CallHandler"); var CallHandler = require("matrix-react-sdk/lib/CallHandler");
CallHandler.setConferenceHandler(VectorConferenceHandler); CallHandler.setConferenceHandler(VectorConferenceHandler);
MatrixClientPeg.setIndexedDbWorkerScript(window.vector_indexeddb_worker_script);
function checkBrowserFeatures(featureList) { function checkBrowserFeatures(featureList) {
if (!window.Modernizr) { if (!window.Modernizr) {
console.error("Cannot check features - Modernizr global is missing."); console.error("Cannot check features - Modernizr global is missing.");
@ -255,11 +259,15 @@ async function loadApp() {
let configError; let configError;
try { try {
configJson = await getConfig(); configJson = await getConfig();
rageshake.setBugReportEndpoint(configJson.bug_report_endpoint_url);
} catch (e) { } catch (e) {
configError = e; configError = e;
} }
if (window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser')) {
console.log('User has previously accepted risks in using an unsupported browser');
validBrowser = true;
}
console.log("Vector starting at "+window.location); console.log("Vector starting at "+window.location);
if (configError) { if (configError) {
window.matrixChat = ReactDOM.render(<div className="error"> window.matrixChat = ReactDOM.render(<div className="error">
@ -291,6 +299,7 @@ async function loadApp() {
var CompatibilityPage = sdk.getComponent("structures.CompatibilityPage"); var CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
window.matrixChat = ReactDOM.render( window.matrixChat = ReactDOM.render(
<CompatibilityPage onAccept={function() { <CompatibilityPage onAccept={function() {
if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
validBrowser = true; validBrowser = true;
console.log("User accepts the compatibility risks."); console.log("User accepts the compatibility risks.");
loadApp(); loadApp();

View file

@ -0,0 +1,21 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {IndexedDBStoreWorker} from 'matrix-js-sdk/lib/indexeddb-worker.js';
const remoteWorker = new IndexedDBStoreWorker(postMessage);
onmessage = remoteWorker.onMessage;

View file

@ -24,12 +24,12 @@ import q from 'q';
const electron = require('electron'); const electron = require('electron');
const remote = electron.remote; const remote = electron.remote;
electron.remote.autoUpdater.on('update-downloaded', onUpdateDownloaded); remote.autoUpdater.on('update-downloaded', onUpdateDownloaded);
function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) { function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) {
dis.dispatch({ dis.dispatch({
action: 'new_version', action: 'new_version',
currentVersion: electron.remote.app.getVersion(), currentVersion: remote.app.getVersion(),
newVersion: ver, newVersion: ver,
releaseNotes: releaseNotes, releaseNotes: releaseNotes,
}); });
@ -68,7 +68,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
try { try {
remote.app.setBadgeCount(count); remote.app.setBadgeCount(count);
} catch (e) { } catch (e) {
console.error("Failed to set notification count", e); console.error('Failed to set notification count', e);
} }
} }
@ -81,13 +81,24 @@ export default class ElectronPlatform extends VectorBasePlatform {
} }
displayNotification(title: string, msg: string, avatarUrl: string, room: Object): Notification { displayNotification(title: string, msg: string, avatarUrl: string, room: Object): Notification {
// GNOME notification spec parses HTML tags for styling...
// Electron Docs state all supported linux notification systems follow this markup spec
// https://github.com/electron/electron/blob/master/docs/tutorial/desktop-environment-integration.md#linux
// maybe we should pass basic styling (italics, bold, underline) through from MD
// we only have to strip out < and > as the spec doesn't include anything about things like &amp;
// so we shouldn't assume that all implementations will treat those properly. Very basic tag parsing is done.
if (window.process.platform === 'linux') {
msg = msg.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// Notifications in Electron use the HTML5 notification API // Notifications in Electron use the HTML5 notification API
const notification = new global.Notification( const notification = new global.Notification(
title, title,
{ {
body: msg, body: msg,
icon: avatarUrl, icon: avatarUrl,
tag: "vector", tag: 'vector',
silent: true, // we play our own sounds silent: true, // we play our own sounds
} }
); );
@ -95,13 +106,14 @@ export default class ElectronPlatform extends VectorBasePlatform {
notification.onclick = function() { notification.onclick = function() {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: room.roomId room_id: room.roomId,
}); });
global.focus(); global.focus();
const currentWin = electron.remote.getCurrentWindow(); const win = remote.getCurrentWindow();
currentWin.show();
currentWin.restore(); if (win.isMinimized()) win.restore();
currentWin.focus(); else if (!win.isVisible()) win.show();
else win.focus();
}; };
return notification; return notification;
@ -112,7 +124,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
} }
getAppVersion() { getAppVersion() {
return q(electron.remote.app.getVersion()); return q(remote.app.getVersion());
} }
pollForUpdate() { pollForUpdate() {
@ -129,7 +141,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
} }
getDefaultDeviceDisplayName() { getDefaultDeviceDisplayName() {
return "Riot Desktop on " + platformFriendlyName(); return 'Riot Desktop on ' + platformFriendlyName();
} }
screenCaptureErrorString() { screenCaptureErrorString() {
@ -139,4 +151,8 @@ export default class ElectronPlatform extends VectorBasePlatform {
requestNotificationPermission() : Promise { requestNotificationPermission() : Promise {
return q('granted'); return q('granted');
} }
reload() {
remote.getCurrentWebContents().reload();
}
} }

View file

@ -206,4 +206,10 @@ export default class WebPlatform extends VectorBasePlatform {
} }
return null; return null;
} }
reload() {
// forceReload=false since we don't really need new HTML/JS files
// we just need to restart the JS runtime.
window.location.reload(false);
}
} }

View file

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
import request from "browser-request";
import q from "q"; import q from "q";
// This module contains all the code needed to log the console, persist it to // This module contains all the code needed to log the console, persist it to
@ -205,9 +203,6 @@ class IndexedDBLogStore {
} }
let txn = this.db.transaction(["logs", "logslastmod"], "readwrite"); let txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
let objStore = txn.objectStore("logs"); let objStore = txn.objectStore("logs");
objStore.add(this._generateLogEntry(lines));
let lastModStore = txn.objectStore("logslastmod");
lastModStore.put(this._generateLastModifiedTime());
txn.oncomplete = (event) => { txn.oncomplete = (event) => {
resolve(); resolve();
}; };
@ -219,6 +214,9 @@ class IndexedDBLogStore {
new Error("Failed to write logs: " + event.target.errorCode) new Error("Failed to write logs: " + event.target.errorCode)
); );
} }
objStore.add(this._generateLogEntry(lines));
let lastModStore = txn.objectStore("logslastmod");
lastModStore.put(this._generateLastModifiedTime());
}); });
return this.flushPromise; return this.flushPromise;
} }
@ -396,7 +394,6 @@ function selectQuery(store, keyRange, resultMapper) {
let store = null; let store = null;
let logger = null; let logger = null;
let initPromise = null; let initPromise = null;
let bugReportEndpoint = null;
module.exports = { module.exports = {
/** /**
@ -430,80 +427,29 @@ module.exports = {
await store.consume(); await store.consume();
}, },
setBugReportEndpoint: function(url) {
bugReportEndpoint = url;
},
/** /**
* Send a bug report. * Get a recent snapshot of the logs, ready for attaching to a bug report
* @param {string} userText Any additional user input. *
* @param {boolean} sendLogs True to send logs * @return {Array<{lines: string, id, string}>} list of log data
* @return {Promise} Resolved when the bug report is sent.
*/ */
sendBugReport: async function(userText, sendLogs) { getLogsForReport: async function() {
if (!logger) { if (!logger) {
throw new Error( throw new Error(
"No console logger, did you forget to call init()?" "No console logger, did you forget to call init()?"
); );
} }
if (!bugReportEndpoint) {
throw new Error("No bug report endpoint has been set.");
}
let version = "UNKNOWN";
try {
version = await PlatformPeg.get().getAppVersion();
}
catch (err) {} // PlatformPeg already logs this.
let userAgent = "UNKNOWN";
if (window.navigator && window.navigator.userAgent) {
userAgent = window.navigator.userAgent;
}
// If in incognito mode, store is null, but we still want bug report // If in incognito mode, store is null, but we still want bug report
// sending to work going off the in-memory console logs. // sending to work going off the in-memory console logs.
console.log("Sending bug report."); if (store) {
let logs = []; // flush most recent logs
if (sendLogs) { await store.flush();
if (store) { return await store.consume();
// flush most recent logs
await store.flush();
logs = await store.consume();
}
else {
logs.push({
lines: logger.flush(true),
id: "-",
});
}
} }
else {
await q.Promise((resolve, reject) => { return [{
request({ lines: logger.flush(true),
method: "POST", id: "-",
url: bugReportEndpoint, }];
body: { }
logs: logs, },
text: (
userText || "User did not supply any additional text."
),
version: version,
user_agent: userAgent,
},
json: true,
timeout: 5 * 60 * 1000,
}, (err, res) => {
if (err) {
reject(err);
return;
}
if (res.status < 200 || res.status >= 400) {
reject(new Error(`HTTP ${res.status}`));
return;
}
resolve();
})
});
}
}; };

View file

@ -0,0 +1,124 @@
/*
Copyright 2017 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import pako from 'pako';
import q from "q";
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
import rageshake from './rageshake'
// polyfill textencoder if necessary
import * as TextEncodingUtf8 from 'text-encoding-utf-8';
let TextEncoder = window.TextEncoder;
if (!TextEncoder) {
TextEncoder = TextEncodingUtf8.TextEncoder;
}
/**
* Send a bug report.
*
* @param {string} bugReportEndpoint HTTP url to send the report to
*
* @param {object} opts optional dictionary of options
*
* @param {string} opts.userText Any additional user input.
*
* @param {boolean} opts.sendLogs True to send logs
*
* @param {function(string)} opts.progressCallback Callback to call with progress updates
*
* @return {Promise} Resolved when the bug report is sent.
*/
export default async function sendBugReport(bugReportEndpoint, opts) {
if (!bugReportEndpoint) {
throw new Error("No bug report endpoint has been set.");
}
opts = opts || {};
const progressCallback = opts.progressCallback || (() => {});
progressCallback("Collecting app version information");
let version = "UNKNOWN";
try {
version = await PlatformPeg.get().getAppVersion();
}
catch (err) {} // PlatformPeg already logs this.
let userAgent = "UNKNOWN";
if (window.navigator && window.navigator.userAgent) {
userAgent = window.navigator.userAgent;
}
const client = MatrixClientPeg.get();
console.log("Sending bug report.");
const body = new FormData();
body.append('text', opts.userText || "User did not supply any additional text.");
body.append('app', 'riot-web');
body.append('version', version);
body.append('user_agent', userAgent);
if (client) {
body.append('user_id', client.credentials.userId);
body.append('device_id', client.deviceId);
}
if (opts.sendLogs) {
progressCallback("Collecting logs");
const logs = await rageshake.getLogsForReport();
for (let entry of logs) {
// encode as UTF-8
const buf = new TextEncoder().encode(entry.lines);
// compress
const compressed = pako.gzip(buf);
body.append('compressed-log', new Blob([compressed]), entry.id);
}
}
progressCallback("Uploading report");
await _submitReport(bugReportEndpoint, body, progressCallback);
}
function _submitReport(endpoint, body, progressCallback) {
const deferred = q.defer();
const req = new XMLHttpRequest();
req.open("POST", endpoint);
req.timeout = 5 * 60 * 1000;
req.onreadystatechange = function() {
if (req.readyState === XMLHttpRequest.LOADING) {
progressCallback("Waiting for response from server");
} else if (req.readyState === XMLHttpRequest.DONE) {
on_done();
}
};
req.send(body);
return deferred.promise;
function on_done() {
if (req.status < 200 || req.status >= 400) {
deferred.reject(new Error(`HTTP ${req.status}`));
return;
}
deferred.resolve();
}
}

View file

@ -72,14 +72,13 @@ describe('joining a room', function () {
var ROOM_ALIAS = '#alias:localhost'; var ROOM_ALIAS = '#alias:localhost';
var ROOM_ID = '!id:localhost'; var ROOM_ID = '!id:localhost';
httpBackend.when('PUT', '/presence/'+encodeURIComponent(USER_ID)+'/status')
.respond(200, {});
httpBackend.when('GET', '/pushrules').respond(200, {}); httpBackend.when('GET', '/pushrules').respond(200, {});
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' }); httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
httpBackend.when('GET', '/sync').respond(200, {}); httpBackend.when('GET', '/sync').respond(200, {});
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
httpBackend.when('GET', '/thirdparty/protocols').respond(200, {}); // note that we deliberately do *not* set an expectation for a
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID }); // presence update - setting one makes the first httpBackend.flush
// return before the first /sync arrives.
// start with a logged-in client // start with a logged-in client
localStorage.setItem("mx_hs_url", HS_URL ); localStorage.setItem("mx_hs_url", HS_URL );
@ -94,9 +93,18 @@ describe('joining a room', function () {
matrixChat._setPage(PageTypes.RoomDirectory); matrixChat._setPage(PageTypes.RoomDirectory);
var roomView; var roomView;
// wait for /sync to happen // wait for /sync to happen
return q.delay(1).then(() => { return q.delay(1).then(() => {
return httpBackend.flush(); return httpBackend.flush();
}).then(() => {
// wait for the directory requests
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
httpBackend.when('GET', '/thirdparty/protocols').respond(200, {});
return q.all([
httpBackend.flush('/publicRooms'),
httpBackend.flush('/thirdparty/protocols'),
]);
}).then(() => { }).then(() => {
var roomDir = ReactTestUtils.findRenderedComponentWithType( var roomDir = ReactTestUtils.findRenderedComponentWithType(
matrixChat, RoomDirectory); matrixChat, RoomDirectory);
@ -110,6 +118,7 @@ describe('joining a room', function () {
// that should create a roomview which will start a peek; wait // that should create a roomview which will start a peek; wait
// for the peek. // for the peek.
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
httpBackend.when('GET', '/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync") httpBackend.when('GET', '/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync")
.respond(401, {errcode: 'M_GUEST_ACCESS_FORBIDDEN'}); .respond(401, {errcode: 'M_GUEST_ACCESS_FORBIDDEN'});
return httpBackend.flush(); return httpBackend.flush();

View file

@ -1,11 +1,12 @@
var path = require('path'); const path = require('path');
var webpack = require('webpack'); const webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin"); const ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { module.exports = {
entry: { entry: {
"bundle": "./src/vector/index.js", "bundle": "./src/vector/index.js",
"indexeddb-worker": "./src/vector/indexedbd-worker.js",
// We ship olm.js as a separate lump of javascript. This makes it get // We ship olm.js as a separate lump of javascript. This makes it get
// loaded via a separate <script/> tag in index.html (which loads it // loaded via a separate <script/> tag in index.html (which loads it
@ -18,11 +19,11 @@ module.exports = {
// CSS themes // CSS themes
"theme-light": "./src/skins/vector/css/themes/light.scss", "theme-light": "./src/skins/vector/css/themes/light.scss",
"theme-dark": "./src/skins/vector/css/themes/dark.scss" "theme-dark": "./src/skins/vector/css/themes/dark.scss",
}, },
module: { module: {
preLoaders: [ preLoaders: [
{ test: /\.js$/, loader: "source-map-loader" } { test: /\.js$/, loader: "source-map-loader" },
], ],
loaders: [ loaders: [
{ test: /\.json$/, loader: "json" }, { test: /\.json$/, loader: "json" },
@ -37,9 +38,7 @@ module.exports = {
// would also drag in the imgs and fonts that our CSS refers to // would also drag in the imgs and fonts that our CSS refers to
// as webpack inputs.) // as webpack inputs.)
// 3. ExtractTextPlugin turns that string into a separate asset. // 3. ExtractTextPlugin turns that string into a separate asset.
loader: ExtractTextPlugin.extract( loader: ExtractTextPlugin.extract("css-raw-loader!postcss-loader?config=postcss.config.js"),
"css-raw-loader!postcss-loader?config=postcss.config.js"
),
}, },
{ {
// this works similarly to the scss case, without postcss. // this works similarly to the scss case, without postcss.
@ -48,15 +47,18 @@ module.exports = {
}, },
], ],
noParse: [ noParse: [
// for cross platform compatibility use [\\\/] as the path separator
// this ensures that the regex trips on both Windows and *nix
// don't parse the languages within highlight.js. They cause stack // don't parse the languages within highlight.js. They cause stack
// overflows (https://github.com/webpack/webpack/issues/1721), and // overflows (https://github.com/webpack/webpack/issues/1721), and
// there is no need for webpack to parse them - they can just be // there is no need for webpack to parse them - they can just be
// included as-is. // included as-is.
/highlight\.js\/lib\/languages/, /highlight\.js[\\\/]lib[\\\/]languages/,
// olm takes ages for webpack to process, and it's already heavily // olm takes ages for webpack to process, and it's already heavily
// optimised, so there is little to gain by us uglifying it. // optimised, so there is little to gain by us uglifying it.
/olm\/(javascript\/)?olm\.js$/, /olm[\\\/](javascript[\\\/])?olm\.js$/,
], ],
}, },
output: { output: {
@ -82,7 +84,7 @@ module.exports = {
// various levels of '.' and '..' // various levels of '.' and '..'
// Also, sometimes the resource path is absolute. // Also, sometimes the resource path is absolute.
return path.relative(process.cwd(), info.resourcePath).replace(/^[\/\.]*/, ''); return path.relative(process.cwd(), info.resourcePath).replace(/^[\/\.]*/, '');
} },
}, },
resolve: { resolve: {
alias: { alias: {
@ -105,16 +107,13 @@ module.exports = {
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV) NODE_ENV: JSON.stringify(process.env.NODE_ENV),
} },
}), }),
new ExtractTextPlugin( new ExtractTextPlugin("bundles/[hash]/[name].css", {
"bundles/[hash]/[name].css", allChunks: true,
{ }),
allChunks: true
}
),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: './src/vector/index.html', template: './src/vector/index.html',