commit
f7eec0b282
@ -0,0 +1,96 @@ |
||||
const EventEmitter = require('events').EventEmitter |
||||
const hardCodedNotices = require('../../development/notices.json') |
||||
|
||||
module.exports = class NoticeController extends EventEmitter { |
||||
|
||||
constructor (opts) { |
||||
super() |
||||
this.configManager = opts.configManager |
||||
this.noticePoller = null |
||||
} |
||||
|
||||
getState() { |
||||
var lastUnreadNotice = this.getLatestUnreadNotice() |
||||
|
||||
return { |
||||
lastUnreadNotice: lastUnreadNotice, |
||||
noActiveNotices: !lastUnreadNotice, |
||||
} |
||||
} |
||||
|
||||
getNoticesList() { |
||||
var data = this.configManager.getData() |
||||
if ('noticesList' in data) { |
||||
return data.noticesList |
||||
} else { |
||||
return [] |
||||
} |
||||
} |
||||
|
||||
setNoticesList(list) { |
||||
var data = this.configManager.getData() |
||||
data.noticesList = list |
||||
this.configManager.setData(data) |
||||
return Promise.resolve(true) |
||||
} |
||||
|
||||
markNoticeRead(notice, cb) { |
||||
cb = cb || function(err){ if (err) throw err } |
||||
try { |
||||
var notices = this.getNoticesList() |
||||
var id = notice.id |
||||
notices[id].read = true |
||||
this.setNoticesList(notices) |
||||
let latestNotice = this.getLatestUnreadNotice() |
||||
cb(null, latestNotice) |
||||
} catch (err) { |
||||
cb(err) |
||||
} |
||||
} |
||||
|
||||
updateNoticesList() { |
||||
return this._retrieveNoticeData().then((newNotices) => { |
||||
var oldNotices = this.getNoticesList() |
||||
var combinedNotices = this._mergeNotices(oldNotices, newNotices) |
||||
return Promise.resolve(this.setNoticesList(combinedNotices)) |
||||
}) |
||||
} |
||||
|
||||
getLatestUnreadNotice() { |
||||
var notices = this.getNoticesList() |
||||
var filteredNotices = notices.filter((notice) => { |
||||
return notice.read === false |
||||
}) |
||||
return filteredNotices[filteredNotices.length - 1] |
||||
} |
||||
|
||||
startPolling () { |
||||
if (this.noticePoller) { |
||||
clearInterval(this.noticePoller) |
||||
} |
||||
this.noticePoller = setInterval(() => { |
||||
this.noticeController.updateNoticesList() |
||||
}, 300000) |
||||
} |
||||
|
||||
_mergeNotices(oldNotices, newNotices) { |
||||
var noticeMap = this._mapNoticeIds(oldNotices) |
||||
newNotices.forEach((notice) => { |
||||
if (noticeMap.indexOf(notice.id) === -1) { |
||||
oldNotices.push(notice) |
||||
} |
||||
}) |
||||
return oldNotices |
||||
} |
||||
|
||||
_mapNoticeIds(notices) { |
||||
return notices.map((notice) => notice.id) |
||||
} |
||||
|
||||
_retrieveNoticeData() { |
||||
// Placeholder for the API.
|
||||
return Promise.resolve(hardCodedNotices) |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,36 @@ |
||||
var fsp = require('fs-promise') |
||||
var path = require('path') |
||||
var prompt = require('prompt') |
||||
var open = require('open') |
||||
var extend = require('extend') |
||||
var notices = require('./notices.json') |
||||
|
||||
var id = 0 |
||||
var date = new Date().toDateString() |
||||
|
||||
var notice = { |
||||
read: false, |
||||
date: date, |
||||
} |
||||
|
||||
fsp.readdir('notices') |
||||
.then((files) => { |
||||
files.forEach(file => { id ++ }) |
||||
Promise.resolve() |
||||
}).then(() => { |
||||
fsp.writeFile(`notices/notice_${id}.md`,'Message goes here. Please write out your notice and save before proceeding at the command line.') |
||||
.then(() => { |
||||
open(`notices/notice_${id}.md`) |
||||
prompt.start() |
||||
prompt.get(['title'], (err, result) => { |
||||
notice.title = result.title |
||||
fsp.readFile(`notices/notice_${id}.md`) |
||||
.then((body) => { |
||||
notice.body = body.toString() |
||||
notice.id = id |
||||
notices.push(notice) |
||||
return fsp.writeFile(`development/notices.json`, JSON.stringify(notices)) |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1 @@ |
||||
[{"read":false,"date":"Fri Dec 16 2016","title":"Ending Morden Support","body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n","id":0}] |
File diff suppressed because one or more lines are too long
@ -0,0 +1,62 @@ |
||||
{ |
||||
"metamask": { |
||||
"isInitialized": true, |
||||
"isUnlocked": true, |
||||
"identities": { |
||||
"0x24a1d059462456aa332d6da9117aa7f91a46f2ac": { |
||||
"address": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac", |
||||
"name": "Account 1" |
||||
} |
||||
}, |
||||
"unconfTxs": {}, |
||||
"currentFiat": "USD", |
||||
"conversionRate": 8.3533002, |
||||
"conversionDate": 1481671082, |
||||
"noActiveNotices": false, |
||||
"lastUnreadNotice": { |
||||
"read": false, |
||||
"date": "Tue Dec 13 2016", |
||||
"title": "MultiVault Support", |
||||
"body": "# Multi\n# Line\n## Support\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi tincidunt dapibus justo a auctor. Sed luctus metus non mi laoreet, sit amet placerat nibh ultricies. Cras fringilla, urna sit amet sodales porttitor, lacus risus lacinia lorem, non euismod magna felis id ex. Nam iaculis, ante nec imperdiet suscipit, nisi quam fringilla nisl, sed fringilla turpis lectus et nibh. Pellentesque sed neque pretium nulla elementum lacinia eu eget felis. Nulla facilisi. Pellentesque id mi tempor, tempus sapien id, ultricies nibh. Integer faucibus elit non orci dapibus porttitor. Pellentesque rutrum hendrerit sapien ut lacinia. Nunc elementum eget arcu eu volutpat. Integer ullamcorper aliquam metus, eu malesuada tellus vestibulum a.\n", |
||||
"id": 0 |
||||
}, |
||||
"network": "3", |
||||
"accounts": { |
||||
"0x24a1d059462456aa332d6da9117aa7f91a46f2ac": { |
||||
"code": "0x", |
||||
"nonce": "0x0", |
||||
"balance": "0x0", |
||||
"address": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac" |
||||
} |
||||
}, |
||||
"transactions": [], |
||||
"provider": { |
||||
"type": "testnet" |
||||
}, |
||||
"selectedAccount": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac", |
||||
"seedWords": null, |
||||
"isDisclaimerConfirmed": true, |
||||
"unconfMsgs": {}, |
||||
"messages": [], |
||||
"shapeShiftTxList": [], |
||||
"keyringTypes": [ |
||||
"Simple Key Pair", |
||||
"HD Key Tree" |
||||
] |
||||
}, |
||||
"appState": { |
||||
"menuOpen": false, |
||||
"currentView": { |
||||
"name": "accountDetail", |
||||
"detailView": null, |
||||
"context": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac" |
||||
}, |
||||
"accountDetail": { |
||||
"subview": "transactions" |
||||
}, |
||||
"transForward": true, |
||||
"isLoading": false, |
||||
"warning": null |
||||
}, |
||||
"identities": {} |
||||
} |
@ -0,0 +1,40 @@ |
||||
{ |
||||
"metamask": { |
||||
"isInitialized": false, |
||||
"isUnlocked": false, |
||||
"rpcTarget": "https://rawtestrpc.metamask.io/", |
||||
"identities": {}, |
||||
"unconfTxs": {}, |
||||
"currentFiat": "USD", |
||||
"conversionRate": 8.18703468, |
||||
"conversionDate": 1481755832, |
||||
"network": "3", |
||||
"accounts": {}, |
||||
"transactions": [], |
||||
"provider": { |
||||
"type": "testnet" |
||||
}, |
||||
"isDisclaimerConfirmed": false, |
||||
"unconfMsgs": {}, |
||||
"messages": [], |
||||
"shapeShiftTxList": [], |
||||
"keyringTypes": [ |
||||
"Simple Key Pair", |
||||
"HD Key Tree" |
||||
] |
||||
}, |
||||
"appState": { |
||||
"menuOpen": false, |
||||
"currentView": { |
||||
"name": "accounts", |
||||
"detailView": null |
||||
}, |
||||
"accountDetail": { |
||||
"subview": "transactions" |
||||
}, |
||||
"transForward": true, |
||||
"isLoading": false, |
||||
"warning": null |
||||
}, |
||||
"identities": {} |
||||
} |
@ -0,0 +1,12 @@ |
||||
Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network. |
||||
|
||||
Users will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/). |
||||
|
||||
Please use the new Ropsten Network as your new default test network. |
||||
|
||||
You can fund your Ropsten account using the buy button on your account page. |
||||
|
||||
Best wishes! |
||||
|
||||
The MetaMask Team |
||||
|
@ -0,0 +1,115 @@ |
||||
const assert = require('assert') |
||||
const extend = require('xtend') |
||||
const rp = require('request-promise') |
||||
const nock = require('nock') |
||||
const configManagerGen = require('../lib/mock-config-manager') |
||||
const NoticeController = require('../../app/scripts/notice-controller') |
||||
const STORAGE_KEY = 'metamask-persistance-key' |
||||
// Hacking localStorage support into JSDom
|
||||
window.localStorage = {} |
||||
|
||||
describe('notice-controller', function() { |
||||
var noticeController |
||||
|
||||
beforeEach(function() { |
||||
let configManager = configManagerGen() |
||||
noticeController = new NoticeController({ |
||||
configManager: configManager, |
||||
}) |
||||
}) |
||||
|
||||
describe('notices', function() { |
||||
describe('#getNoticesList', function() { |
||||
it('should return an empty array when new', function() { |
||||
var testList = [{ |
||||
id:0, |
||||
read:false, |
||||
title:"Futuristic Notice" |
||||
}] |
||||
var result = noticeController.getNoticesList() |
||||
assert.equal(result.length, 0) |
||||
}) |
||||
}) |
||||
|
||||
describe('#setNoticesList', function() { |
||||
it('should set data appropriately', function () { |
||||
var testList = [{ |
||||
id:0, |
||||
read:false, |
||||
title:"Futuristic Notice" |
||||
}] |
||||
noticeController.setNoticesList(testList) |
||||
var testListId = noticeController.getNoticesList()[0].id |
||||
assert.equal(testListId, 0) |
||||
}) |
||||
}) |
||||
|
||||
describe('#updateNoticeslist', function() { |
||||
it('should integrate the latest changes from the source', function() { |
||||
var testList = [{ |
||||
id:55, |
||||
read:false, |
||||
title:"Futuristic Notice" |
||||
}] |
||||
noticeController.setNoticesList(testList) |
||||
noticeController.updateNoticesList().then(() => { |
||||
var newList = noticeController.getNoticesList() |
||||
assert.ok(newList[0].id === 55) |
||||
assert.ok(newList[1]) |
||||
}) |
||||
}) |
||||
it('should not overwrite any existing fields', function () { |
||||
var testList = [{ |
||||
id:0, |
||||
read:false, |
||||
title:"Futuristic Notice" |
||||
}] |
||||
noticeController.setNoticesList(testList) |
||||
noticeController.updateNoticesList().then(() => { |
||||
var newList = noticeController.getNoticesList() |
||||
assert.equal(newList[0].id, 0) |
||||
assert.equal(newList[0].title, "Futuristic Notice") |
||||
assert.equal(newList.length, 1) |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
describe('#markNoticeRead', function () { |
||||
it('should mark a notice as read', function () { |
||||
var testList = [{ |
||||
id:0, |
||||
read:false, |
||||
title:"Futuristic Notice" |
||||
}] |
||||
noticeController.setNoticesList(testList) |
||||
noticeController.markNoticeRead(testList[0]) |
||||
var newList = noticeController.getNoticesList() |
||||
assert.ok(newList[0].read) |
||||
}) |
||||
}) |
||||
|
||||
describe('#getLatestUnreadNotice', function () { |
||||
it('should retrieve the latest unread notice', function () { |
||||
var testList = [ |
||||
{id:0,read:true,title:"Past Notice"}, |
||||
{id:1,read:false,title:"Current Notice"}, |
||||
{id:2,read:false,title:"Future Notice"}, |
||||
] |
||||
noticeController.setNoticesList(testList) |
||||
var latestUnread = noticeController.getLatestUnreadNotice() |
||||
assert.equal(latestUnread.id, 2) |
||||
}) |
||||
it('should return undefined if no unread notices exist.', function () { |
||||
var testList = [ |
||||
{id:0,read:true,title:"Past Notice"}, |
||||
{id:1,read:true,title:"Current Notice"}, |
||||
{id:2,read:true,title:"Future Notice"}, |
||||
] |
||||
noticeController.setNoticesList(testList) |
||||
var latestUnread = noticeController.getLatestUnreadNotice() |
||||
assert.ok(!latestUnread) |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
}) |
@ -0,0 +1,118 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const ReactMarkdown = require('react-markdown') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('./actions') |
||||
const linker = require('extension-link-enabler') |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
|
||||
module.exports = connect(mapStateToProps)(Notice) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
lastUnreadNotice: state.metamask.lastUnreadNotice, |
||||
} |
||||
} |
||||
|
||||
inherits(Notice, Component) |
||||
function Notice () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
Notice.prototype.render = function () { |
||||
const props = this.props |
||||
const title = props.lastUnreadNotice.title |
||||
const date = props.lastUnreadNotice.date |
||||
|
||||
return ( |
||||
h('.flex-column.flex-center.flex-grow', [ |
||||
h('h3.flex-center.text-transform-uppercacse.terms-header', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
textAlign: 'center', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
title, |
||||
]), |
||||
|
||||
h('h5.flex-center.text-transform-uppercacse.terms-header', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
marginBottom: 24, |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
textAlign: 'center', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
date, |
||||
]), |
||||
|
||||
h('style', ` |
||||
|
||||
.markdown { |
||||
overflow-x: hidden; |
||||
} |
||||
.markdown h1, .markdown h2, .markdown h3 { |
||||
margin: 10px 0; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.markdown strong { |
||||
font-weight: bold; |
||||
} |
||||
.markdown em { |
||||
font-style: italic; |
||||
} |
||||
|
||||
.markdown p { |
||||
margin: 10px 0; |
||||
} |
||||
|
||||
.markdown a { |
||||
color: #df6b0e; |
||||
} |
||||
|
||||
`),
|
||||
|
||||
h('div.markdown', { |
||||
style: { |
||||
background: 'rgb(235, 235, 235)', |
||||
height: '310px', |
||||
padding: '6px', |
||||
width: '90%', |
||||
overflowY: 'scroll', |
||||
scroll: 'auto', |
||||
}, |
||||
}, [ |
||||
h(ReactMarkdown, { |
||||
source: props.lastUnreadNotice.body, |
||||
skipHtml: true, |
||||
}), |
||||
]), |
||||
|
||||
h('button', { |
||||
onClick: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)), |
||||
style: { |
||||
marginTop: '18px', |
||||
}, |
||||
}, 'Continue'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
Notice.prototype.componentDidMount = function () { |
||||
var node = findDOMNode(this) |
||||
linker.setupListener(node) |
||||
} |
||||
|
||||
Notice.prototype.componentWillUnmount = function () { |
||||
var node = findDOMNode(this) |
||||
linker.teardownListener(node) |
||||
} |
Loading…
Reference in new issue