├── .gitignore └── app ├── js ├── controllers.show.js ├── application.js ├── framework.js └── controllers.index.js └── index.haml /.gitignore: -------------------------------------------------------------------------------- 1 | /public 2 | -------------------------------------------------------------------------------- /app/js/controllers.show.js: -------------------------------------------------------------------------------- 1 | controllers.show = function(key) { 2 | var _this = this; 3 | 4 | var story = _.find(store, function(story) { 5 | return story.key == key; 6 | }); 7 | 8 | function loadStory(url) { 9 | var iframe = $(""); 10 | $("body").append(iframe); 11 | iframe.load(function() { 12 | $("#story").html(iframe[0].contentDocument.body.innerHTML); 13 | iframe.remove(); 14 | setupChaptersList(); 15 | }); 16 | iframe.attr("src", url); 17 | } 18 | 19 | /*function setupChaptersList() { 20 | 21 | $("#chapters-list"). 22 | 23 | 24 | }*/ 25 | 26 | function setVisited(key) { 27 | if(!localStorage["visited." + key]) localStorage["visited." + key] = "0"; 28 | localStorage["visited." + key] = parseInt(localStorage["visited." + key]) + 1; 29 | } 30 | 31 | this.init = function() { 32 | console.log("starting show"); 33 | $("#view-show").show().addClass("current-view"); 34 | loadStory(story.url); 35 | setVisited(story.key); 36 | } 37 | 38 | this.render = function() { 39 | } 40 | 41 | this.destroy = function() { 42 | console.log("destroying show"); 43 | $("#story").empty(); 44 | $("#view-show").hide().removeClass("current-view"); 45 | } 46 | } 47 | 48 | controllers.show.setup = function() { 49 | }; 50 | -------------------------------------------------------------------------------- /app/js/application.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('.ui.sidebar').sidebar('toggle'); 3 | }); 4 | 5 | var store = null; 6 | if(!localStorage["visited"]) localStorage["visited"] = {}; 7 | 8 | $(function() { 9 | $(document).on("dragstart", "a, img", false); 10 | 11 | $(window).bind('hashchange', function() { 12 | $(window).scrollTop((utils.page() - 1) * Math.max(0, $(window).height() - 51 - 60)); 13 | }).trigger('hashchange'); 14 | 15 | $("#page-back").click(function(e) { 16 | e.stopPropagation(); 17 | utils.page(utils.page() - 1); 18 | }); 19 | $("#page-back-10").click(function(e) { 20 | e.stopPropagation(); 21 | utils.page(utils.page() - 10); 22 | }); 23 | $("#page-next").click(function(e) { 24 | e.stopPropagation(); 25 | utils.page(utils.page() + 1); 26 | }); 27 | $("#page-next-10").click(function(e) { 28 | e.stopPropagation(); 29 | utils.page(utils.page() + 10); 30 | }); 31 | $("#page-home").click(function(e) { 32 | e.stopPropagation(); 33 | location.hash = lastControllerLocation; 34 | }); 35 | 36 | $(document).on("click", "body", function(e) { 37 | e.stopPropagation(); 38 | $("#nav-container").toggle(); 39 | }); 40 | 41 | $.getJSON("data.json").done(function(data) { 42 | if(data.length == 0) alert("No data.json, or data invalid."); 43 | 44 | store = data; 45 | 46 | window.router = new router(); 47 | router.init(); 48 | if(location.hash == "#" || location.hash == "") location.hash = "#index!1"; 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /app/js/framework.js: -------------------------------------------------------------------------------- 1 | var utils = {}; 2 | 3 | utils.location = function(changes) { 4 | if(arguments.length == 1) { 5 | var output = _.extend(utils.location(), changes); 6 | location.hash = "#" + [output.controller].concat(output.params).join("/") + "!" + output.hash; 7 | } 8 | else { 9 | var route_hash = location.hash.substr(1).split("!"); 10 | var route = route_hash[0]; 11 | var controller = route.split("/")[0]; 12 | var params = route.split("/").slice(1); 13 | var hash = route_hash.slice(1).join("!"); 14 | return { 15 | route: route, //Don't set this 16 | controller: controller, 17 | params: params, 18 | hash: hash 19 | }; 20 | } 21 | } 22 | 23 | utils.page = function(index, max) { 24 | if(arguments.length == 2) { 25 | if(isNaN(index) || index < 1) index = 1; 26 | if(index > max) index = max; 27 | utils.location({ hash: index }); 28 | } 29 | else if(arguments.length == 1) { 30 | utils.location({ hash: index }); 31 | } 32 | else { 33 | var index = parseInt(utils.location().hash); 34 | if(isNaN(index) || index < 1) index = 1; 35 | return index; 36 | } 37 | } 38 | 39 | utils.scrollDistanceFromBottom = function() { 40 | return utils.pageHeight() - (window.pageYOffset + self.innerHeight); 41 | } 42 | 43 | utils.pageHeight = function() { 44 | return $(".current-view").height(); 45 | } 46 | 47 | utils.nearBottomOfPage = function() { 48 | return utils.scrollDistanceFromBottom() < 250; 49 | } 50 | 51 | utils.pages = function(array, perPage) { 52 | return Math.ceil(array.length / (perPage + 0.0)); 53 | } 54 | 55 | utils.paginate = function(array, perPage) { 56 | var page = utils.page(); 57 | return array.slice((page - 1) * perPage, page * perPage); 58 | } 59 | 60 | var router = function() { 61 | var _this = this; 62 | 63 | var currentController = null; 64 | var currentRoute = null; 65 | 66 | this.init = function() { 67 | _.each(controllers, function(controller) { 68 | controller.setup(); 69 | }); 70 | 71 | $(window).bind("hashchange", function() { 72 | var route = utils.location().route; 73 | if(route == "") return; 74 | 75 | if(route != currentRoute) { 76 | console.log("changing route from ", currentRoute, " to ", route); 77 | currentRoute = route; 78 | 79 | if(currentController) { 80 | $(".current-view .navbar-content").append($("#navbar-content-target").contents().detach()); 81 | currentController.destroy(); 82 | delete currentController; 83 | } 84 | 85 | var controllerFunction = controllers[utils.location().controller]; 86 | currentController = new (controllerFunction.bind.apply(controllerFunction, [null].concat(utils.location().params))); 87 | currentController.init(); 88 | $("#navbar-content-target").empty().append($(".current-view .navbar-content").contents().detach()); 89 | } 90 | 91 | currentController.render(); 92 | }).trigger("hashchange"); 93 | } 94 | }; 95 | 96 | var controllers = {}; 97 | -------------------------------------------------------------------------------- /app/js/controllers.index.js: -------------------------------------------------------------------------------- 1 | var lastControllerLocation = "#index!1"; 2 | 3 | controllers.index = function(search, sort, sortDirection) { 4 | var _this = this; 5 | 6 | function sortFor(type) { 7 | if(!type) type = "publishedOn"; 8 | 9 | if(type == "publishedOn") return function(story) { 10 | return story.publishedOn; 11 | }; 12 | if(type == "wordCount") return function(story) { 13 | return story.wordCount; 14 | }; 15 | if(type == "title") return function(story) { 16 | return story.title.toLowerCase(); 17 | }; 18 | } 19 | 20 | var storys = store; 21 | if(search && search != "") { 22 | var words = search.split(/\s+/); 23 | _.each(words, function(word) { 24 | regex = RegExp(word, "i"); 25 | storys = _.filter(storys, function(story) { 26 | return story.title.match(regex); 27 | }); 28 | }); 29 | } 30 | if(!sort) sort = "publishedOn"; 31 | storys = _.sortBy(storys, sortFor(sort)); 32 | 33 | if(!sortDirection) sortDirection = "desc"; 34 | if(sortDirection == "desc") storys = storys.reverse(); 35 | 36 | function getVisits(key) { 37 | return parseInt(localStorage["visited." + key]); 38 | } 39 | 40 | function addStories(storys) { 41 | if($("#stories > li").length) return; 42 | 43 | _.each(storys, function(story) { 44 | story.visits = getVisits(story.key); 45 | story.pages = Math.ceil(story.wordCount / 300); 46 | $("#stories").append(controllers.index.storyTemplate({ story: story })); 47 | }); 48 | } 49 | 50 | this.init = function() { 51 | console.log("starting index"); 52 | 53 | $("#search").bind("keydown", function(event) { 54 | event.stopPropagation(); 55 | if(event.keyCode == 13) { 56 | event.preventDefault(); 57 | utils.location({ params: [$("#search").val(), sort, sortDirection], hash: "1" }); 58 | } 59 | }); 60 | 61 | $("#clear-search").bind("click", function() { 62 | $("#search").val(""); 63 | event.preventDefault(); 64 | location.href = "#index!1"; 65 | }); 66 | 67 | $(".sort button").bind("click", function(event) { 68 | event.preventDefault(); 69 | utils.location({ params: [search, $(this).data("sort"), sortDirection], hash: "1" }); 70 | }); 71 | 72 | $(".sort button").removeClass("active"); 73 | $(".sort button[data-sort=" + sort + "]").addClass("active"); 74 | 75 | $(".sort-direction button").bind("click", function(event) { 76 | event.preventDefault(); 77 | console.log("clicked sorter", $(this).data("sort-direction")); 78 | utils.location({ params: [search, sort, $(this).data("sort-direction")], hash: "1" }); 79 | }); 80 | 81 | $(".sort-direction button").removeClass("active"); 82 | $(".sort-direction button[data-sort-direction=" + sortDirection + "]").addClass("active"); 83 | 84 | $("#view-index").show().addClass("current-view"); 85 | addStories(storys); 86 | } 87 | 88 | this.render = function() { 89 | lastControllerLocation = location.hash; 90 | } 91 | 92 | this.destroy = function() { 93 | console.log("destroying index"); 94 | $("#search").unbind("keydown"); 95 | $("#clear-search").unbind("click"); 96 | $(".sort button").unbind("click"); 97 | $(".sort-direction button").unbind("click"); 98 | $("#stories").empty(); 99 | $("#view-index").hide().removeClass("current-view"); 100 | } 101 | } 102 | 103 | controllers.index.setup = function() { 104 | controllers.index.storyTemplate = Handlebars.compile($("#stories-template").remove().html()); 105 | } 106 | -------------------------------------------------------------------------------- /app/index.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html 3 | %head 4 | %meta{ charset: "utf-8" } 5 | %title Storys 6 | %link{ rel: "stylesheet", type: "text/css", href: "/css/semantic.min.css" } 7 | %link{ rel: "stylesheet", type: "text/css", href: "/css/application.css" } 8 | %script{ src: "/js/jquery.min.js" } 9 | %script{ src: "/js/underscore-min.js" } 10 | %script{ src: "/js/handlebars.min.js" } 11 | %script{ src: "/js/semantic.min.js" } 12 | %script{ src: "/js/framework.js" } 13 | %script{ src: "/js/controllers.index.js" } 14 | %script{ src: "/js/controllers.show.js" } 15 | %script{ src: "/js/application.js" } 16 | %body 17 |