Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Updated styling of the various views.

Bumped version to 0.3.0.
  • Loading branch information...
commit 68c14e9b79771bc3fa9116a0515c0932d6239256 2 parents 6bae1a8 + 2986b8d
@fizker authored
View
7 index.js
@@ -11,7 +11,10 @@ var storage
function storageOpened(err, st) {
- if(err) throw err;
+ if(err) {
+ console.log('%s\nCould not create a connection to the database.', err.toString());
+ return;
+ }
console.log('Storage is running..');
storage = st;
@@ -22,8 +25,8 @@ function storageOpened(err, st) {
};
function serverStarted(err, srv) {
if(err) {
+ console.log('%s\nCould not create the web-server.', err.toString());
storage.close(function() {
- throw err;
});
}
View
2  package.json
@@ -2,7 +2,7 @@
"author": "Benjamin Horsleben <benjamin@fizkerinc.dk>",
"name": "links",
"description": "A simple links-repository.",
- "version": "0.2.0",
+ "version": "0.3.0",
"repository": {
"type": "git",
"url": "git://github.com/fizker/links.git"
View
2  readme.md
@@ -25,7 +25,7 @@ Only major point-releases are listed. Struck-through numbers are finished.
- <s>0.1</s>: Basic functionality in place.
- <s>0.2</s>: Permanent storage (Mongo).
-- 0.3: First shot at client-side UI.
+- <s>0.3</s>: First shot at client-side UI.
- 0.4: Bookmarklets.
- 0.5: Client-side UX (backbone, ajax, etc).
- 0.6: One-page app (html routing, fallback to current model).
View
53 src/routes/links.js
@@ -16,13 +16,16 @@ function setupRoutes(options) {
http.post('/links', validateLink, postLink);
http.get('/links/new', newLink);
+
http.get('/links/:url', getLink);
http.put('/links/:url', validateLink, putLink);
+ http.post('/links/:url', validateLink, postUpdateLink);
http.del('/links/:url', deleteLink);
+
+ http.get('/links/:url/edit', editLink);
};
function validateLink(request, response, next) {
- var url = request.params && request.params.url
- , link = request.body
+ var link = request.body
, error
if(!link || link.url == null) {
@@ -32,18 +35,28 @@ function validateLink(request, response, next) {
return;
}
link.encodedUrl = encodeURIComponent(link.url);
- if(url !== undefined) {
- if(url !== link.url) {
- error = 'URL does not match';
- response.render('errors/400', { status: 400, error: error });
- next(new Error(error));
- return;
- }
- }
next();
};
function newLink(request, response) {
- response.render('link.edit.mustache');
+ response.render('link.edit.mustache', {});
+};
+function editLink(request, response) {
+ var url = request.params.url
+
+ db.get(url, linkLoaded)
+
+ function linkLoaded(err, link) {
+ if(err) {
+ response.render('errors/500', { status: 500, error: err });
+ return;
+ }
+ if(!link) {
+ response.render('errors/404', { status: 404 });
+ return;
+ }
+
+ response.render('link.edit.mustache', link);
+ };
};
function allLinks(request, response) {
db.get(function(err, data) {
@@ -83,6 +96,24 @@ function postLink(request, response) {
response.render('link.post.mustache', link);
});
};
+function postUpdateLink(request, response) {
+ var link = request.body
+ , url = request.params.url
+ , otherIsComplete
+
+ db.del(url, checkIsComplete);
+ db.add(link, checkIsComplete);
+
+ function checkIsComplete() {
+ if(otherIsComplete) {
+ allDone();
+ }
+ otherIsComplete = true;
+ };
+ function allDone() {
+ response.render('link.post.mustache', link);
+ };
+};
function putLink(request, response) {
var url = request.params.url
, link = request.body
View
10 src/server/configure.js
@@ -8,9 +8,13 @@ var express = require('express')
, fs = require('fs')
, viewsDir = path.join(__dirname, '../../views')
- , layout = hogan
- .compile(fs
- .readFileSync(path.join(viewsDir, 'layout.mustache'), 'utf8'))
+ // We would want a dev-switch here, and use precompiled when not in dev mode.
+ , layout = { render: function(options) {
+ return hogan.compile(fs
+ .readFileSync(path.join(viewsDir, 'layout.mustache'), 'utf8'))
+ .render(options);
+ }
+ }
function config(options) {
var http = options.http
View
13 src/server/index.js
@@ -19,7 +19,14 @@ function start(options, callback) {
require('./../routes')(options);
- http.listen(port);
-
- callback(null, server);
+ http.listen(port, function(err) {
+ // TODO: If the port is in use, this is not actually called.
+ // We would want to look at that at some point.
+ if(err) {
+ return callback(err, {
+ port: options.port || port
+ });
+ }
+ callback(null, server);
+ });
};
View
6 src/storage/index.js
@@ -30,7 +30,11 @@ function open(options, callback) {
storage.links = links(db);
db.open(function(err) {
- if(err) { return callback(err); }
+ if(err) { return callback(err, {
+ host: options.host || host,
+ port: options.port || port,
+ dbName: options.dbName || dbName
+ }); }
callback(null, storage);
});
View
22 static/css/base.css
@@ -0,0 +1,22 @@
+a:-webkit-any-link {
+ color: #40e;
+ text-decoration: none;
+}
+a:hover {
+ color: green;
+}
+
+body {
+ background: #999;
+ /* #036400 green */
+ /* #640303 red */
+ /* #120a64 blue */
+ background: -webkit-linear-gradient(top, #640303 0, #120a64 100%) fixed;
+
+ font-family: Cochin, helvetica, arial;
+ font-size: 14pt;
+ line-height: 13pt;
+
+ padding: 0;
+ margin: 0;
+}
View
29 static/css/layout.css
@@ -0,0 +1,29 @@
+.lnk-menu {
+ text-align: center;
+ margin: 0 0 10px;
+ padding: 0 0 10px;
+ border-bottom: dashed 1px black;
+}
+.lnk-menu a {
+ display: inline-block;
+}
+.lnk-menu a:not(:last-child) {
+ content: '|';
+ padding: 0 6px;
+ margin: 0 6px;
+ border-right: 1px solid black;
+}
+.lnk-menu a:hover {
+}
+
+.lnk-root {
+ width: 800px;
+ margin: 5vh auto;
+ box-shadow: black 1px 2px 5px;
+ max-width: 90vw;
+ min-height: 90vh;
+ background: rgba(255,255,255,.90);
+ border-radius: 10px;
+ padding: 20px;
+ box-sizing: border-box;
+}
View
30 static/css/md-form.css
@@ -0,0 +1,30 @@
+.md-form {}
+
+.md-form-row {
+ display: -webkit-box;
+ max-width: 450px;
+ margin: 2px auto;
+}
+
+.md-form-lbl {
+ display: inline-block;
+ text-align: right;
+ vertical-align: top;
+}
+
+.md-form-inp-text,
+.md-form-text
+{
+ display: block;
+ -webkit-box-flex: 1;
+}
+
+.md-form-text {
+ height: 60px;
+}
+
+.md-form-btns {
+ max-width: 450px;
+ text-align: right;
+ margin: 10px auto 0;
+}
View
8 static/css/md-lnk-form.css
@@ -0,0 +1,8 @@
+.md-lnk-form-lbl {
+ width: 100px;
+}
+
+label[for=link-title] {
+}
+label[for=link-url] {
+}
View
20 static/css/md-lnk.css
@@ -0,0 +1,20 @@
+.md-lnk-int {}
+.md-lnk-ext {
+ font-size: .8em;
+}
+
+.md-lnk-lst {
+ padding: 0;
+}
+.md-lnk-lst-itm {
+ list-style: none;
+}
+
+.md-lnk-edit-btn {
+ font-size: 12pt;
+ display: sub;
+}
+
+.md-lnk-text {
+ white-space: pre;
+}
View
7 static/css/styles.css
@@ -0,0 +1,7 @@
+@import url(base.css);
+@import url(layout.css);
+
+@import url(md-form.css);
+
+@import url(md-lnk.css);
+@import url(md-lnk-form.css);
View
65 test/routes.links.js
@@ -62,6 +62,42 @@ describe('routes.links.js', function() {
storage: { links: storage }
});
});
+ describe('When getting "/links/:url/edit', function() {
+ describe('With a non-existing url', function() {
+ beforeEach(function() {
+ storage.get.yields(null, null);
+ request = {
+ params: {
+ url: 'abc'
+ }
+ };
+ caller(http.routes.get['/links/:url/edit'], request, response);
+ });
+ it('should return status 404', function() {
+ var data = response.render.lastCall.args[1];
+ expect(data.status).to.eql(404);
+ });
+ });
+ describe('With an existing url', function() {
+ beforeEach(function() {
+ storage.get.withArgs('abc').yields(null, { url: 'abc' });
+ request = {
+ params: {
+ url: 'abc'
+ }
+ };
+ caller(http.routes.get['/links/:url/edit'], request, response);
+ });
+ it('should return the link', function() {
+ var link = response.render.lastCall.args[1];
+ expect(link).to.eql({ url: 'abc' });
+ });
+ it('should present the link.edit view', function() {
+ var view = response.render.lastCall.args[0];
+ expect(view).to.match(/link\.edit/);
+ });
+ });
+ });
describe('When getting "/links/abc"', function() {
beforeEach(function() {
storage.get.withArgs('abc').yields(null, { url: 'abc' });
@@ -93,6 +129,35 @@ describe('routes.links.js', function() {
]);
});
});
+ describe('When posting to "/links/:url"', function() {
+ beforeEach(function() {
+ storage.del.withArgs('abc').yields({ url: 'abc' });
+ storage.add.yields({ url: 'abc' });
+ request = {
+ params: {
+ url: 'abc'
+ },
+ body: {
+ url: 'def'
+ }
+ };
+ caller(http.routes.post['/links/:url'], request, response);
+ });
+ it('should not fail for different urls', function() {
+ var status = response.render.lastCall.args[1].status;
+ expect(status).not.to.satisfy(function(num) {
+ return num < 200 || num > 299;
+ });
+ });
+ it('should remove the old link', function() {
+ expect(storage.del)
+ .to.have.been.calledWith('abc');
+ });
+ it('should add the new link', function() {
+ var url = storage.add.lastCall.args[0].url
+ expect(url).to.eql('def');
+ });
+ });
describe('When putting to "/links/:url"', function() {
describe('with invalid data', function() {
beforeEach(function() {
View
23 views/layout.mustache
@@ -1,13 +1,12 @@
<!doctype html>
-<html>
- <head>
- <title>Links</title>
- </head>
- <body>
- <nav>
- <a href="/links">All links</a>
- <a href="/links/new">New link</a>
- </nav>
- {{{body}}}
- </body>
-</html>
+<title>Links</title>
+<meta name="viewport" content="initial-scale=1, maximum-scale=1">
+<link href=/css/styles.css rel=stylesheet>
+<div class="lnk-root">
+<nav class="lnk-menu">
+ <a href=/links>All links</a
+ ><a href=/links/new>New link</a
+ >
+</nav>
+{{{body}}}
+</div>
View
28 views/link.edit.mustache
@@ -1,17 +1,23 @@
-<form action="/links" method="post">
- <div>
- <label for="link-title">Title:</label>
- <input id="link-title" name="title" value="{{title}}">
+{{#link}}
+<form action=/links{{#url}}/{{encodedUrl}}{{/url}} method=post class="md-form">
+ <div class="md-form-row">
+ <label class="md-form-lbl md-lnk-form-lbl" for=link-title>Title:</label>
+ <input class="md-form-inp-text" id=link-title name=title value="{{title}}">
</div>
- <div>
- <label for="link-url">URL:</label>
- <input id="link-url" name="url" value="{{url}}">
+ <div class="md-form-row">
+ <label class="md-form-lbl md-lnk-form-lbl" for=link-url>URL:</label>
+ <input class="md-form-inp-text" id=link-url name=url value="{{url}}">
</div>
- <div>
- <button type="reset">Reset</button>
- <button type="submit">
+ <div class="md-form-row">
+ <label class="md-form-lbl md-lnk-form-lbl" for=link-text>Description:</label>
+ <textarea class="md-form-text" id=link-text name=text>{{text}}</textarea>
+ </div>
+ <div class="md-form-btns">
+ <button type=reset>Reset</button>
+ <button type=submit>
{{#url}}Update link{{/url}}
{{^url}}Create link{{/url}}
</button>
</div>
-</form>
+</form>
+{{/link}}
View
6 views/link.mustache
@@ -1,4 +1,8 @@
{{#link}}
-<h1>{{title}}</h1>
+<h1>
+ {{title}}
+ <a class="md-lnk-edit-btn" href=/links/{{encodedUrl}}/edit>Edit</a>
+</h1>
<a href="{{url}}">{{url}}</a>
+<p class="md-lnk-text">{{text}}</p>
{{/link}}
View
2  views/link.post.mustache
@@ -1,5 +1,5 @@
{{#link}}
<p>The link has been created</p>
<a href="{{url}}">Click here to follow the link</a>
-<a href="/links/{{encodedUrl}}">Click here to see it in the system</a>
+<a href=/links/{{encodedUrl}}>Click here to see it in the system</a>
{{/link}}
View
8 views/links.mustache
@@ -1,9 +1,11 @@
<h1>Links</h1>
-<ul>
+<ul class="md-lnk-lst">
{{^links}}
-<li>No links</li>
+<li>No links
{{/links}}
{{#links}}
-<li><a href="/links/{{encodedUrl}}">{{title}}</a></li>
+<li class="md-lnk-lst-itm">
+ <a class="md-lnk-int" href=/links/{{encodedUrl}}>{{title}}</a>
+ &lt;<a class="md-lnk-ext" href="{{url}}">{{url}}</a>&gt;
{{/links}}
</ul>
Please sign in to comment.
Something went wrong with that request. Please try again.