<!DOCTYPE html>
<html>
<head>
<link href="style.css" rel="stylesheet">
<script src="//rawgit.com/magnumjs/mag.js/master/dist/mag.0.21.2.min.js"></script>
</head>
<body>
<!--
https://gist.github.com/jlongster/3f32b2c7dce588f24c92#file-d-twitter-js-L230
http://jlongster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome#p59
<h1>Hello Mag.JS!</h1>
<a target="_tab" href="https://github.com/magnumjs/mag.js">GitHub</a>
<hr/> -->
<div id="app">
<div id="toolbar"><em>Logged in as <username/></em>
<button>settings</button>
<button>undo</button>
</div>
<div class="content">
<div id="feed">
<div class="feed-count"><count></count> message(s)</div>
<input name="newMessage">
<div class="items">
<div class="message">
<h4></h4>
<div></div>
<a href="#" class="star"><img src="http://jlongster.com/s/bloop/app3/star.png"></a>
</div>
</div>
</div>
<div class="single-message">
<h1>Message</h1>
<p></p>
<a href="#">Back</a>
</div>
</div>
<div class="settings">
<div class="dismiss"></div>
<div id="settings" style="top: 90px; left: 229.688px;">
<form>Username:
<input name="username">
<div class="submit">
<button type="submit">save</button>
</div>
</form>
</div>
</div>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<script src="//rawgit.com/magnumjs/mag.js/master/src/mag.addons.0.2.js"></script>
<script src="settings.js"></script>
<script src="feed.js"></script>
<script src="toolbar.js"></script>
<script src="undo.js"></script>
<script src="app.js"></script>
</body>
</html>
var appState = {
username: 'James',
feeder: {
items: [{
id: Math.random() * 10000 | 0,
author: 'James',
text: 'This is your initial message. Love it, embrace it!',
starred: false
}]
},
selectedMessage: null,
settingsOpen: false
};
var prevStates = [JSON.stringify(appState)];
function getMessage(id) {
return _.find(appState.feeder.items, {
id: id
});
}
mag.module('app', {
controller: function(props) {
this.handleMessage = function(newMessage) {
if (newMessage) {
props.feeder.items.unshift({
id: Math.random() * 10000 | 0,
author: props.username,
text: newMessage,
starred: false
});
}
}.bind(this)
this.toggleStarred = function(id) {
var msg = getMessage(parseInt(id));
msg.starred = !msg.starred;
}
this.openItem = function(id) {
var msg = getMessage(parseInt(id));
if (msg) {
this.selectedMessage = msg;
} else {
this.selectedMessage = null;
}
}.bind(this)
this.didupdate = function() {
var nstate = JSON.stringify(mag.utils.merge(props, mag.utils.copy(this)));
prevStates.push(nstate);
}
this.updateSettings = function(username) {
props.username = username;
}
this.toggleSettings = function() {
props.settingsOpen = !props.settingsOpen;
}
},
view: function(state, props) {
Toolbar({
username: props.username,
onSettings: state.toggleSettings,
undo: undo
});
state['single-message'] = mag.show(!!state.selectedMessage)
var section;
if (state.selectedMessage) {
state.feed = {
_class: 'hide'
}
// author, text, starred, id
state['single-message'].p = state.selectedMessage.text
state['single-message'].a = {
_onClick: state.openItem.bind(null, null)
}
} else {
state.feed = {
_class: ''
}
Feed({
items: props.feeder.items,
onMessageSubmit: state.handleMessage,
onOpenItem: state.openItem,
onStar: state.toggleStarred
})
}
state.settings = mag.show(props.settingsOpen)
state.dismiss = {
_onclick: state.toggleSettings
}
props.settingsOpen && Settings({
username: props.username,
onSave: state.updateSettings,
onClose: state.toggleSettings
})
}
}, appState)
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
body {
background-color: #466a5c;
color: white;
padding-top: 1em;
}
a,
a:visited {
color: #e98782;
}
.app {
margin: 1.5em;
}
#toolbar {
overflow: hidden;
}
#toolbar button {
float: right;
font-size: 1em;
background-color: rgba(225, 225, 225, .3);
border: 0;
color: white;
padding: .5em;
margin-left: 1em;
}
#toolbar button:hover {
background-color: #e98782;
cursor: pointer;
}
.feed-count {
position: fixed;
bottom: 0;
left: calc(50% - 3em);
margin-bottom: 1em;
}
#feed input {
width: 100%;
margin: 1em 0;
padding: .25em;
font-size: 14px;
}
.message {
position: relative;
background-color: rgba(225, 225, 225, .3);
margin: 1em 0;
padding: .5em;
}
.message:hover {
background-color: #e98782;
cursor: pointer;
}
.message:first-of-type {
margin: 0;
}
.message h4 {
margin: 0;
}
.message a.star {
position: absolute;
top: 0;
right: 0;
margin: 1em;
}
.message a.star:hover img {
position: relative;
top: -2px;
left: 2px;
width: 26px;
}
.message a.star img {
width: 22px;
}
.dismiss {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#settings {
position: absolute;
background-color: white;
color: #222222;
border: 3px solid #e98782;
border-radius: 3px;
padding: 1em;
}
#settings:after,
#settings:before {
bottom: 100%;
left: 85%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
#settings:after {
border-bottom-color: white;
border-width: 10px;
margin-left: -10px;
}
#settings:before {
border-bottom-color: #e98782;
border-width: 14px;
margin-left: -14px;
}
#settings div.submit {
text-align: right;
margin-top: .5em;
}
.undo {
position: absolute;
bottom: 0;
right: 0;
z-index: 500;
margin: 1em;
font-size: 1.5em;
border-radius: 5px;
background-color: #f0f0f0;
}
.hide {
display:none;
}
var Feed = mag.create('feed', {
controller: function(props) {
// handle input pass value up the chain
this.newMessage = '';
},
view: function(state, props) {
state.count = props.items.length
state.input = {
_onKeyUp: function(e) {
if (e.keyCode === 13) {
props.onMessageSubmit(state.newMessage)
state.newMessage = '';
}
}
}
state.message = props.items.map(function(msg) {
return {
star: {
_onClick: function(e) {
e.stopPropagation();
props.onStar(msg.id)
},
img: {
_src: (msg.starred ? 'http://jlongster.com/s/bloop/app3/star.png' : 'http://jlongster.com/s/bloop/app3/star-outline.png')
}
},
_onClick: props.onOpenItem.bind(null, msg.id),
h4: msg.author,
div: msg.text
}
})
}
})
var Toolbar = mag.create('toolbar', {
view: function(state, props) {
state.username = props.username
state.$button = {
_onclick: function(event, index) {
if (index == 1) props.onSettings()
else props.undo()
}
}
}
})
var Settings = mag.create('settings', {
controller: function(props) {
return {
save: function(e) {
e.preventDefault()
props.onSave(this.username.value);
props.onClose();
},
username: props.username
};
},
view: function(state, props) {
state.form = {
username: {
_value: props.username,
_config: function(node, isNew) {
if(!isNew) node.focus()
}
},
_onsubmit: state.save
}
}
})
function undo() {
while (1) {
var state = JSON.parse(prevStates.pop());
if (!prevStates.length) {
// This is the initial app state, so unconditionally apply it
// and push it back onto the history so we don't lose it
appState.feeder = state.feeder;
prevStates.push(JSON.stringify(state));
break;
} else if (JSON.stringify(appState) !== JSON.stringify(state)) {
// We found a state where the feed has changed, so apply it
appState.username = state.username;
appState.feeder = state.feeder;
break;
}
}
}