Rearrange directories
This commit is contained in:
9
src/browser/css/jp_fonts.css
Normal file
9
src/browser/css/jp_fonts.css
Normal file
@@ -0,0 +1,9 @@
|
||||
@font-face {
|
||||
font-family: sans-serif;
|
||||
src: local('Meiryo UI');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: monospace;
|
||||
src: local('MS Gothic');
|
||||
}
|
19
src/browser/index.html
Normal file
19
src/browser/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>electron-mattermost</title>
|
||||
<script src="node_modules/react/dist/react.min.js"></script>
|
||||
<script src="node_modules/react-dom/dist/react-dom.min.js"></script>
|
||||
<script src="node_modules/react-bootstrap/dist/react-bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="build/index.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
117
src/browser/index.js
Normal file
117
src/browser/index.js
Normal file
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
const remote = electron.remote;
|
||||
const osLocale = require('os-locale');
|
||||
const fs = require('fs');
|
||||
|
||||
var url = require('url');
|
||||
|
||||
var contextMenu = require('./menus/context');
|
||||
const settings = require('./common/settings');
|
||||
|
||||
var webView = document.getElementById('mainWebview');
|
||||
|
||||
try {
|
||||
var configFile = remote.getGlobal('config-file');
|
||||
var config = settings.readFileSync(configFile);
|
||||
if (config.version != settings.version) {
|
||||
config = settings.upgrade(config);
|
||||
settings.writeFileSync(configFile, config);
|
||||
}
|
||||
if (config.teams[0]) {
|
||||
webView.setAttribute('src', config.teams[0].url);
|
||||
}
|
||||
else {
|
||||
throw 'URL is not configured';
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
window.location.href = './settings.html';
|
||||
}
|
||||
|
||||
var menu = contextMenu.createDefault();
|
||||
window.addEventListener('contextmenu', function(e) {
|
||||
menu.popup(remote.getCurrentWindow());
|
||||
}, false);
|
||||
|
||||
webView.addEventListener('page-title-set', function(e) {
|
||||
document.title = e.title;
|
||||
});
|
||||
|
||||
// Open external link in default browser.
|
||||
webView.addEventListener('new-window', function(e) {
|
||||
var currentURL = url.parse(webView.getURL());
|
||||
var destURL = url.parse(e.url);
|
||||
// Open in browserWindow. for exmaple, attached files.
|
||||
if (currentURL.host === destURL.host) {
|
||||
window.open(e.url, 'Mattermost');
|
||||
}
|
||||
else {
|
||||
require('shell').openExternal(e.url);
|
||||
}
|
||||
});
|
||||
|
||||
webView.addEventListener("dom-ready", function() {
|
||||
// webView.openDevTools();
|
||||
|
||||
// Use 'Meiryo UI' and 'MS Gothic' to prevent CJK fonts on Windows(JP).
|
||||
if (process.platform === 'win32') {
|
||||
var applyCssFile = function(cssFile) {
|
||||
fs.readFile(cssFile, 'utf8', function(err, data) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
webView.insertCSS(data);
|
||||
});
|
||||
};
|
||||
|
||||
osLocale(function(err, locale) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
if (locale === 'ja_JP') {
|
||||
applyCssFile(__dirname + '/css/jp_fonts.css');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Count unread channels.
|
||||
var timer = setInterval(function() {
|
||||
webView.send('retrieveUnreadCount');
|
||||
}, 1000);
|
||||
|
||||
var showUnreadBadge = function(unreadCount) {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
var window = remote.getCurrentWindow();
|
||||
if (unreadCount > 0) {
|
||||
window.setOverlayIcon(__dirname + '/resources/badge.png', 'You have unread channels.');
|
||||
}
|
||||
else {
|
||||
window.setOverlayIcon(null, '');
|
||||
}
|
||||
break;
|
||||
case 'darwin':
|
||||
if (unreadCount > 0) {
|
||||
remote.app.dock.setBadge(unreadCount.toString());
|
||||
}
|
||||
else {
|
||||
remote.app.dock.setBadge('');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
webView.addEventListener('ipc-message', function(event) {
|
||||
switch (event.channel) {
|
||||
case 'retrieveUnreadCount':
|
||||
var unreadCount = event.args[0];
|
||||
showUnreadBadge(unreadCount);
|
||||
break;
|
||||
}
|
||||
});
|
25
src/browser/index.jsx
Normal file
25
src/browser/index.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
var MainPage = React.createClass({
|
||||
render: function() {
|
||||
var style = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
};
|
||||
// 'disablewebsecurity' is necessary to display external images.
|
||||
// However, it allows also CSS/JavaScript.
|
||||
// So webview should use 'allowDisplayingInsecureContent' as same as BrowserWindow.
|
||||
return (
|
||||
<webview style={ style } id="mainWebview" autosize="on" preload="webview/mattermost.js"></webview>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<MainPage />,
|
||||
document.getElementById('content')
|
||||
);
|
30
src/browser/menus/context.js
Normal file
30
src/browser/menus/context.js
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
const remote = require('electron').remote;
|
||||
const Menu = remote.Menu;
|
||||
const MenuItem = remote.MenuItem;
|
||||
|
||||
var createDefault = function() {
|
||||
var menu = new Menu();
|
||||
menu.append(new MenuItem({
|
||||
label: 'Cut',
|
||||
role: 'cut'
|
||||
}));
|
||||
menu.append(new MenuItem({
|
||||
label: 'Copy',
|
||||
role: 'copy'
|
||||
}));
|
||||
menu.append(new MenuItem({
|
||||
label: 'Paste',
|
||||
role: 'paste'
|
||||
}));
|
||||
menu.append(new MenuItem({
|
||||
label: 'Select All',
|
||||
role: 'selectall'
|
||||
}));
|
||||
return menu;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createDefault: createDefault
|
||||
};
|
18
src/browser/settings.html
Normal file
18
src/browser/settings.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Settings</title>
|
||||
<script src="node_modules/react/dist/react.min.js"></script>
|
||||
<script src="node_modules/react-dom/dist/react-dom.min.js"></script>
|
||||
<script src="node_modules/react-bootstrap/dist/react-bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="build/settings.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
178
src/browser/settings.jsx
Normal file
178
src/browser/settings.jsx
Normal file
@@ -0,0 +1,178 @@
|
||||
'use strict';
|
||||
|
||||
const remote = require('electron').remote;
|
||||
const settings = require('./common/settings');
|
||||
|
||||
const Grid = ReactBootstrap.Grid;
|
||||
const Row = ReactBootstrap.Row;
|
||||
const Col = ReactBootstrap.Col;
|
||||
const Button = ReactBootstrap.Button;
|
||||
const ListGroup = ReactBootstrap.ListGroup;
|
||||
const ListGroupItem = ReactBootstrap.ListGroupItem;
|
||||
const Glyphicon = ReactBootstrap.Glyphicon;
|
||||
|
||||
var SettingsPage = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
teams: []
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
var config = settings.readFileSync(this.props.configFile);
|
||||
this.setState({
|
||||
teams: config.teams
|
||||
})
|
||||
},
|
||||
handleTeamsChange: function(teams) {
|
||||
this.setState({
|
||||
teams: teams
|
||||
});
|
||||
},
|
||||
handleSave: function() {
|
||||
var config = {
|
||||
teams: this.state.teams,
|
||||
version: 1
|
||||
};
|
||||
settings.writeFileSync(this.props.configFile, config);
|
||||
window.location = './index.html';
|
||||
},
|
||||
handleCancel: function() {
|
||||
window.location = './index.html';
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Grid className="settingsPage">
|
||||
<Row>
|
||||
<Col md={ 12 }>
|
||||
<h2>Teams</h2>
|
||||
<TeamList teams={ this.state.teams } onTeamsChange={ this.handleTeamsChange } />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col md={ 12 }>
|
||||
<Button onClick={ this.handleCancel }>Cancel</Button>
|
||||
{ ' ' }
|
||||
<Button bsStyle="primary" onClick={ this.handleSave }>Save</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TeamList = React.createClass({
|
||||
handleTeamRemove: function(index) {
|
||||
console.log(index);
|
||||
var teams = this.props.teams;
|
||||
teams.splice(index, 1);
|
||||
this.props.onTeamsChange(teams);
|
||||
},
|
||||
handleTeamAdd: function(team) {
|
||||
var teams = this.props.teams;
|
||||
teams.push(team);
|
||||
this.props.onTeamsChange(teams);
|
||||
},
|
||||
render: function() {
|
||||
var thisObj = this;
|
||||
var teamNodes = this.props.teams.map(function(team, i) {
|
||||
var handleTeamRemove = function() {
|
||||
thisObj.handleTeamRemove(i);
|
||||
};
|
||||
return (
|
||||
<TeamListItem index={ i } name={ team.name } url={ team.url } onTeamRemove={ handleTeamRemove } />
|
||||
);
|
||||
});
|
||||
return (
|
||||
<ListGroup class="teamList">
|
||||
{ teamNodes }
|
||||
<TeamListItemNew onTeamAdd={ this.handleTeamAdd } />
|
||||
</ListGroup>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TeamListItem = React.createClass({
|
||||
handleTeamRemove: function() {
|
||||
this.props.onTeamRemove();
|
||||
},
|
||||
render: function() {
|
||||
var style = {
|
||||
left: {
|
||||
"display": 'inline-block'
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="teamListItem list-group-item">
|
||||
<div style={ style.left }>
|
||||
<h4 className="list-group-item-heading">{ this.props.name }</h4>
|
||||
<p className="list-group-item-text">
|
||||
{ this.props.url }
|
||||
</p>
|
||||
</div>
|
||||
<div className="pull-right">
|
||||
<Button bsSize="xsmall" onClick={ this.handleTeamRemove }>
|
||||
<Glyphicon glyph="remove" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TeamListItemNew = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
name: '',
|
||||
url: ''
|
||||
};
|
||||
},
|
||||
handleSubmit: function(e) {
|
||||
console.log('submit');
|
||||
e.preventDefault();
|
||||
this.props.onTeamAdd({
|
||||
name: this.state.name,
|
||||
url: this.state.url
|
||||
});
|
||||
this.setState(this.getInitialState());
|
||||
},
|
||||
handleNameChange: function(e) {
|
||||
console.log('name');
|
||||
this.setState({
|
||||
name: e.target.value
|
||||
});
|
||||
},
|
||||
handleURLChange: function(e) {
|
||||
console.log('url');
|
||||
this.setState({
|
||||
url: e.target.value
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<ListGroupItem>
|
||||
<form className="form-inline" onSubmit={ this.handleSubmit }>
|
||||
<div className="form-group">
|
||||
<label for="inputTeamName">Name</label>
|
||||
{ ' ' }
|
||||
<input type="text" className="form-control" id="inputTeamName" placeholder="Example team" value={ this.state.name } onChange={ this.handleNameChange } />
|
||||
</div>
|
||||
{ ' ' }
|
||||
<div className="form-group">
|
||||
<label for="inputTeamURL">URL</label>
|
||||
{ ' ' }
|
||||
<input type="url" className="form-control" id="inputTeamURL" placeholder="http://example.com/team" value={ this.state.url } onChange={ this.handleURLChange } />
|
||||
</div>
|
||||
{ ' ' }
|
||||
<Button type="submit">Add</Button>
|
||||
</form>
|
||||
</ListGroupItem>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var configFile = remote.getGlobal('config-file');
|
||||
|
||||
ReactDOM.render(
|
||||
<SettingsPage configFile={ configFile } />,
|
||||
document.getElementById('content')
|
||||
);
|
60
src/browser/webview/mattermost.js
Normal file
60
src/browser/webview/mattermost.js
Normal file
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
const ipc = electron.ipcRenderer;
|
||||
const NativeNotification = Notification;
|
||||
|
||||
ipc.on('retrieveUnreadCount', function() {
|
||||
var unreadCount = document.getElementsByClassName('unread-title').length;
|
||||
ipc.sendToHost('retrieveUnreadCount', unreadCount);
|
||||
});
|
||||
|
||||
// On Windows 8.1 and Windows 8, a shortcut with a Application User Model ID must be installed to the Start screen.
|
||||
// In current version, use tray balloon for notification
|
||||
function isLowerThanOrEqualWindows8_1() {
|
||||
if (process.platform != 'win32') {
|
||||
return false;
|
||||
}
|
||||
var osVersion = require('../common/osVersion');
|
||||
return (osVersion.major <= 6 && osVersion.minor <= 3);
|
||||
};
|
||||
|
||||
// Show balloon when notified.
|
||||
function overrideNotificationWithBalloon() {
|
||||
Notification = function(title, options) {
|
||||
ipc.send('notified', {
|
||||
title: title,
|
||||
options: options
|
||||
});
|
||||
};
|
||||
Notification.requestPermission = function(callback) {
|
||||
callback('granted');
|
||||
};
|
||||
Notification.prototype.close = function() {};
|
||||
};
|
||||
|
||||
// Show window even if it is hidden/minimized when notification is clicked.
|
||||
function overrideNotification() {
|
||||
Notification = function(title, options) {
|
||||
this.notification = new NativeNotification(title, options);
|
||||
};
|
||||
Notification.requestPermission = function(callback) {
|
||||
callback('granted');
|
||||
};
|
||||
Notification.prototype.close = function() {
|
||||
this.notification.close();
|
||||
};
|
||||
Notification.prototype.__defineSetter__('onclick', function(callback) {
|
||||
this.notification.onclick = function() {
|
||||
electron.remote.getCurrentWindow().show();
|
||||
callback();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' && isLowerThanOrEqualWindows8_1()) {
|
||||
overrideNotificationWithBalloon();
|
||||
}
|
||||
else {
|
||||
overrideNotification();
|
||||
}
|
Reference in New Issue
Block a user