Skip to content

Instantly share code, notes, and snippets.

@segphault
Created July 6, 2016 23:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save segphault/1e583dd7e02c3780ca3a8d6f28dae461 to your computer and use it in GitHub Desktop.
Save segphault/1e583dd7e02c3780ca3a8d6f28dae461 to your computer and use it in GitHub Desktop.
Horizon bookmark manager example using React and RxJS
<html>
<head>
<script type="text/javascript" src="//npmcdn.com/@reactivex/rxjs@5.0.0-beta.9/dist/global/Rx.umd.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react-dom.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
<script type="text/javascript" src="/horizon/horizon.js"></script>
<script type="text/babel" src="views.js"></script>
<script type="text/babel" src="app.js"></script>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600,700">
<link rel="stylesheet" href="//code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link rel="stylesheet" href="main.css" type="text/css" />
</head>
<body>
</body>
</html>
const horizon = Horizon();
const db = horizon("bookmarks");
let state = new Rx.BehaviorSubject({
bookmarks: [],
shared: [],
userID: null,
editing: null
});
let updateState = changes => state.next({...state.value, ...changes});
let query = args => db.findAll(args).order("time", "descending").limit(50).watch();
let getUser = horizon.currentUser().fetch().map(u => ({userID: u.id}));
let shared = query({shared: true}).map(shared => ({shared}));
let bookmarks = state.pluck("userID")
.filter(u => u)
.distinctUntilChanged()
.switchMap(u => query({user: u}))
.map(bookmarks => ({bookmarks}))
Rx.Observable.merge(bookmarks, shared, getUser).subscribe(updateState);
let actions = {
logout() {
Horizon.clearAuthTokens();
updateState({bookmarks: [], userID: null});
},
login() {
horizon.authEndpoint("github").subscribe(url => location.pathname = url);
},
edit: b => updateState({editing: b}),
add: (u, b) => db.store({user: u, bookmark: b, time: new Date(), shared: false}),
update: (editing, b) => db.replace({...editing, bookmark: b}),
share: b => db.replace({...b, shared: !b.shared}),
remove: b => db.remove(b),
}
state.subscribe(state =>
ReactDOM.render(<App {...state} actions={actions} />, document.body));
let Timestamp = ({time}) =>
<span className="timestamp-value">{moment(time).fromNow()}</span>;
let BookmarkActions = ({shared, onShare, onEdit, onDelete}) =>
<div className="bookmark-actions">
<a href="#" onClick={onEdit}><i className="icon ion-compose"></i>Edit</a>
<a href="#" onClick={onDelete}><i className="icon ion-trash-b"></i>Delete</a>
<a href="#" onClick={onShare} className={"bookmark-" + (shared ? "public" : "private")}>
<i className={"icon ion-" + (shared ? "checkmark" : "share")}></i>
Share{shared && "d"}
</a>
</div>;
let Bookmark = ({bookmark, time, shared, user, children}) =>
<div className="bookmark-item">
<i className="icon ion-bookmark"></i>
<div className="bookmark-content">
<div className="timestamp">Bookmark saved <Timestamp time={time} /></div>
<a className="bookmark-link" href={bookmark.url}>{bookmark.title}</a>
{children}
</div>
</div>;
let LoginMenu = ({user, onEdit, onLogin, onLogout}) =>
<div className="login">
{user ?
<div>
<a href="#" onClick={onEdit}><i className="icon ion-plus"></i>&nbsp;Add Bookmark</a>
<a href="#" onClick={onLogout}><i className="icon ion-android-exit"></i>&nbsp;Logout</a>
</div> :
<a href="#" onClick={onLogin}>
Login with Github to Save Bookmarks&nbsp;<i className="icon ion-arrow-right-c"></i>
</a>}
</div>;
let BookmarkEditor = ({editing, onSave, onCancel}) => {
if (!editing) return null;
let textboxURL = null;
let textboxTitle = null;
let title = editing.bookmark ? editing.bookmark.title : "";
let url = editing.bookmark ? editing.bookmark.url : "";
let handleSave = e =>
onSave({url: textboxURL.value, title: textboxTitle.value});
return <div className="add-bookmark">
<h2>Add New Bookmark</h2>
<input type="text" placeholder="URL" ref={e => textboxURL = e} defaultValue={url} />
<input type="text" placeholder="Title" ref={e => textboxTitle = e} defaultValue={title} />
<button onClick={handleSave}>
<i className="icon ion-android-add"></i>{editing.bookmark ? "Save" : "Add"}
</button>
<button onClick={onCancel}>
<i className="icon ion-android-close"></i>Cancel
</button>
</div>;
}
let App = ({userID, bookmarks, shared, editing, actions}) => {
let {edit, share, remove, login, logout} = actions;
let handleSave = bookmark => {
if (editing.id) actions.update(editing, bookmark)
else actions.add(userID, bookmark);
edit(false);
}
return <div id="app">
<LoginMenu user={userID} onEdit={e => edit(true)} onLogin={login} onLogout={logout} />
<h1>Bookmark Manager</h1>
<main className="bookmarks">
<section className="private-bookmarks">
<h2>Your Bookmarks</h2>
<BookmarkEditor editing={editing} onSave={handleSave} onCancel={e => edit(false)} />
{bookmarks.map(b =>
<Bookmark {...b}>
<BookmarkActions onEdit={e => edit(b)} shared={b.shared}
onShare={e => share(b)}
onDelete={e => remove(b)} />
</Bookmark>)}
</section>
<section className="public-bookmarks">
<h2>Public Bookmarks</h2>
{shared.map(Bookmark)}
</section>
</main>
</div>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment