diff --git a/assets/images/gear.png b/assets/images/gear.png new file mode 100644 index 0000000..7445d23 Binary files /dev/null and b/assets/images/gear.png differ diff --git a/assets/jquery/bootstrap-modal.js b/assets/jquery/bootstrap-modal.js new file mode 100644 index 0000000..c831de6 --- /dev/null +++ b/assets/jquery/bootstrap-modal.js @@ -0,0 +1,218 @@ +/* ========================================================= + * bootstrap-modal.js v2.0.3 + * http://twitter.github.com/bootstrap/javascript.html#modals + * ========================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function (content, options) { + this.options = options + this.$element = $(content) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) + } + + Modal.prototype = { + + constructor: Modal + + , toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + , e = $.Event('show') + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + $('body').addClass('modal-open') + + this.isShown = true + + escape.call(this) + backdrop.call(this, function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(document.body) //don't move modals dom position + } + + that.$element + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + transition ? + that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + } + + , hide: function (e) { + e && e.preventDefault() + + var that = this + + e = $.Event('hide') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + $('body').removeClass('modal-open') + + escape.call(this) + + this.$element.removeClass('in') + + $.support.transition && this.$element.hasClass('fade') ? + hideWithTransition.call(this) : + hideModal.call(this) + } + + } + + + /* MODAL PRIVATE METHODS + * ===================== */ + + function hideWithTransition() { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end) + hideModal.call(that) + }, 500) + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout) + hideModal.call(that) + }) + } + + function hideModal(that) { + this.$element + .hide() + .trigger('hidden') + + backdrop.call(this) + } + + function backdrop(callback) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('
') + .appendTo(document.body) + + if (this.options.backdrop != 'static') { + this.$backdrop.click($.proxy(this.hide, this)) + } + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + doAnimate ? + this.$backdrop.one($.support.transition.end, callback) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + $.support.transition && this.$element.hasClass('fade')? + this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) : + removeBackdrop.call(this) + + } else if (callback) { + callback() + } + } + + function removeBackdrop() { + this.$backdrop.remove() + this.$backdrop = null + } + + function escape() { + var that = this + if (this.isShown && this.options.keyboard) { + $(document).on('keyup.dismiss.modal', function ( e ) { + e.which == 27 && that.hide() + }) + } else if (!this.isShown) { + $(document).off('keyup.dismiss.modal') + } + } + + + /* MODAL PLUGIN DEFINITION + * ======================= */ + + $.fn.modal = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('modal') + , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option]() + else if (options.show) data.show() + }) + } + + $.fn.modal.defaults = { + backdrop: true + , keyboard: true + , show: true + } + + $.fn.modal.Constructor = Modal + + + /* MODAL DATA-API + * ============== */ + + $(function () { + $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data()) + + e.preventDefault() + $target.modal(option) + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/grunt.coffee b/grunt.coffee index f6a41a8..5f5c6f7 100644 --- a/grunt.coffee +++ b/grunt.coffee @@ -94,6 +94,7 @@ module.exports = (grunt) -> "assets/jquery/jquery-1.7.2.js" "assets/require.js" "assets/lawnchair/lawnchair.js" + "assets/jquery/bootstrap-modal.js" "assets/images/*.png" ] dest: "dist" diff --git a/grunt.js b/grunt.js index f27ac1d..43e463f 100644 --- a/grunt.js +++ b/grunt.js @@ -111,7 +111,7 @@ module.exports = function(grunt) { findNestedDependencies: true }, install: { - src: ["assets/jquery/jquery-1.7.2.js", "assets/require.js", "assets/lawnchair/lawnchair.js", "assets/images/*.png"], + src: ["assets/jquery/jquery-1.7.2.js", "assets/require.js", "assets/lawnchair/lawnchair.js", "assets/jquery/bootstrap-modal.js", "assets/images/*.png"], dest: "dist" }, mocha: { diff --git a/src/edit_priority_tmpl.haml b/src/edit_priority_tmpl.haml index 90df047..228b961 100644 --- a/src/edit_priority_tmpl.haml +++ b/src/edit_priority_tmpl.haml @@ -1,3 +1,3 @@ .edit-priority %input.edit-priority-field(type="text" value="<%= p.name %>" data-pos="<%= p.pos %>" data-ty="<%= type %>") - %button.delete-priority-field(data-for="<%= p.pos %>") + %button.delete-priority-field(data-for="<%= p.pos %>") × diff --git a/src/index.haml b/src/index.haml index c0b6498..1286489 100644 --- a/src/index.haml +++ b/src/index.haml @@ -12,6 +12,10 @@ %title Priority / Ignore %body + #gear + #gearbutton + %img(src="gear.png") + #grid.container .flow .row-fluid @@ -22,33 +26,30 @@ %h1#ignorize(data-pos="N" data-ty="ignore") Today I Will Ignore: #ignorities - #message(style="display:none") - %h2 How to Use - %p + #message.modal.hide.fade + .modal-header + %button.close(data-dismiss="modal" type="button") × + %h3 Prioritize / Ignore + %p + According to the Harvard School of Business, there are two + lists you should look at every morning, and perhaps + every chance you get: the list of things that are important + to you, and the list of things that you should ignore. - Priority / Ignore is a simple project reminder. It is - not a to-do list or project manager. It's just a - reminder. After downloading - and installing Priority / Ignore! locally, set the - index.html file as your home page, open a new tab and click on - either header. + %p + Prioritize / Ignore is an extension of that idea: a + reminder, each and every time you open your browser, of what + is important, and what is not. - %p + %p + To use: Just click on either header. A new entry will open + up in that list for you to fill out. Empty entries will be + deleted automatically. - The idea is simple: every time you open your browser or click - "New Tab," you'll get the Priority / Ignore page. - Every day we should remind ourselves not only of what must be - done, but what we must put aside as trivial and distracting. - - %p - All the data is stored locally using the browser's own local - storage mechanism. There is no server, and no data is ever - sent anywhere. Everything on your screen is yours, kept as - secure as you keep your physical hardware. - - %p - This message will disappear as soon as you've created your - first entry. You can also - \ No newline at end of file + %p + You can click on the gear icon in the lower + left-hand corner any time you like to review this page. + .model-body + .model-footer + %a.btn(href="#" data-dismiss="modal") Close diff --git a/src/priority.coffee b/src/priority.coffee index 5922747..7b48899 100644 --- a/src/priority.coffee +++ b/src/priority.coffee @@ -2,94 +2,100 @@ require.config paths: 'jquery': 'jquery-1.7.2' -require ['jquery', 'priority_tmpl', 'edit_priority_tmpl', 'lawnchair'], ($, priority_template, edit_priority_template) -> +require ['jquery', 'lawnchair'], ($) -> + require ['priority_tmpl', 'edit_priority_tmpl', 'bootstrap-modal'], (priority_template, edit_priority_template) -> - class Prioritize + class Prioritize - constructor: (@repo) -> - @repo.get 'priorities', (p) => - @priorities = if p? and p.priorities? then p.priorities else [] - @render() - - $('body').on 'click', @render - $('#prioritize').on 'click', @editPriority - $('#ignorize').on 'click', @editPriority + constructor: (@repo) -> + @repo.get 'priorities', (p) => + @priorities = if p? and p.priorities? then p.priorities else [] + @render() + $('body').on 'click', @render + $('#prioritize').on 'click', @editPriority + $('#ignorize').on 'click', @editPriority - editPriority: (ev) => - ev.stopPropagation() + editPriority: (ev) => + ev.stopPropagation() - # Only allow one edit-priority at a time! - return if $('.edit-priority').length > 0 + # Only allow one edit-priority at a time! + return if $('.edit-priority').length > 0 - tg = $(ev.currentTarget) - ty = tg.data('ty') - pos = tg.data('pos') + tg = $(ev.currentTarget) + ty = tg.data('ty') + pos = tg.data('pos') - # If the position is 'N', we're adding a new list item to - # the bottom of a list to be populated. + # If the position is 'N', we're adding a new list item to + # the bottom of a list to be populated. - if pos == 'N' - @priorities.push({name: '', cat: ty}) - pos = @priorities.length - 1 - (if ty == 'priority' then $('#priorities') else $('#ignorities')) - .append('') + if pos == 'N' + @priorities.push({name: '', cat: ty}) + pos = @priorities.length - 1 + (if ty == 'priority' then $('#priorities') else $('#ignorities')) + .append('') - # Replace the list item's contents with the editor's - # content. - li = $('#pos-' + pos) - li.html edit_priority_template - p: - name: @priorities[pos].name - pos: pos - type: ty + # Replace the list item's contents with the editor's + # content. + li = $('#pos-' + pos) + li.html edit_priority_template + p: + name: @priorities[pos].name + pos: pos + type: ty - input = $('input.edit-priority-field', li) + input = $('input.edit-priority-field', li) - maybePrioritySave = (ev) => - prioritySave = => - @priorities[pos] = {cat: ty, name: input.val()} + maybePrioritySave = (ev) => + prioritySave = => + @priorities[pos] = {cat: ty, name: input.val()} + @save() + + code = if ev.keyCode then ev.keyCode else ev.which + return prioritySave() if code == 13 + return @cleanAndRender() if code == 27 + + deletePriority = (ev) => + ev.stopPropagation() + @priorities[pos].name = "" @save() - code = if ev.keyCode then ev.keyCode else ev.which - return prioritySave() if code == 13 - return @cleanAndRender() if code == 27 + input.on 'keyup', maybePrioritySave + $('.delete-priority-field', li).on 'click', deletePriority + input.focus() - deletePriority = (ev) => - ev.stopPropagation() - @priorities[pos].name = "" - @save() + save: -> + @clean() + @repo.save {key: 'priorities', 'priorities': @priorities}, () => + @render() - input.on 'keyup', maybePrioritySave - $('.delete-priority-field', li).on 'click', deletePriority - input.focus() + clean: -> + @priorities = ({name: p.name, cat: p.cat} for p in @priorities when p.name.trim() != "") - save: -> - @clean() - @repo.save {key: 'priorities', 'priorities': @priorities}, () => - @render() + save: -> + @clean() + @repo.save {key: 'priorities', 'priorities': @priorities}, => + @render() - clean: -> - @priorities = ({name: p.name, cat: p.cat} for p in @priorities when p.name.trim() != "") + render: => + priority_enumerate = (cat) => + r = [] + for i in [0...@priorities.length] + if @priorities[i].cat == cat + r.push({name: @priorities[i].name, cat: @priorities[i].cat, pos: i}) + r - save: -> - @clean() - @repo.save {key: 'priorities', 'priorities': @priorities}, => - @render() + $('#priorities').html(priority_template({priorities: priority_enumerate('priority'), type: 'priority'})) + $('#ignorities').html(priority_template({priorities: priority_enumerate('ignore'), type: 'ignore'})) + $('.priorityc').bind 'click', @editPriority - render: => - priority_enumerate = (cat) => - r = [] - for i in [0...@priorities.length] - if @priorities[i].cat == cat - r.push({name: @priorities[i].name, cat: @priorities[i].cat, pos: i}) - r + $ -> + prioritize = new Lawnchair {name: 'Prioritize'}, -> + handler = new Prioritize(this) + handler.render() - $('#priorities').html(priority_template({priorities: priority_enumerate('priority'), type: 'priority'})) - $('#ignorities').html(priority_template({priorities: priority_enumerate('ignore'), type: 'ignore'})) - $('.priorityc').bind 'click', @editPriority + $('#gearbutton').bind 'click', () => $('#message').modal() - $ -> - prioritize = new Lawnchair {name: 'Prioritize'}, -> - handler = new Prioritize(this) - handler.render() + this.get 'priorities', (p) -> + if not p? or not p.priorities? or p.priorities.length == 0 + $('#message').modal() diff --git a/src/style.less b/src/style.less index caa6bbd..ede2d10 100644 --- a/src/style.less +++ b/src/style.less @@ -56,6 +56,8 @@ input[type="search"]::-webkit-search-cancel-button { body { + margin: 0; + padding: 0; font: 13pt/190% Montserrat,Calibri,Georgia,"Lucida Bright",Lucidabright,"Bitstream Vera Serif",serif; background-color: #fff; } @@ -517,43 +519,22 @@ ul + p { margin-left: 10px; } -#newcat h1, div.category, .editcat, .addstory, li.task { +#newcat h1, div.category, .editcat, .addstory, li.priority { cursor: pointer; } +.edit-category, .edit-priority { -.edit-category, .edit-task { - padding: 4px 2px 0px 5px; - text-shadow: 0px 1px 0px #fff; - -webkit-border-radius: 20px; - -moz-border-radius: 20px; - border-radius: 20px; - border: 1px solid #ddd; + .edit-priority-field { + margin-top: 8px; + } - -webkit-box-shadow: 2px 1px 0px #efefef; - -moz-box-shadow: 2px 1px 0px #efefef; - box-shadow: 2px 1px 0px #efefef; - - width: 80%; - input.edit-task-field { - width: 80%; - border: 1px solid #eee; - padding-bottom: 0; - margin-bottom: 0; - margin-left: 5px; - -webkit-border-radius: 20px; - -moz-border-radius: 20px; - border-radius: 20px; - } - - button.delete-task-field { - margin-top: 4px; - padding: 1px 4px 3px 3px; + .delete-priority-field { + padding: 0px 4px 3px 3px; font-weight: bold; - display: block; - float: right; - margin-right: 10px; + display: inline-block; background-color: #ff9f00; + vertical-align: middle; color: #333; -webkit-border-radius: 20px; -moz-border-radius: 20px; @@ -563,16 +544,248 @@ ul + p { #message { font-family: Constantina,Georgia,"Lucida Bright",Lucidabright,"Bitstream Vera Serif",serif; - display: block; - position:absolute; - top: 1.0416666%; - right: 1.0416666%; - width: 35.0416666%; - max-width: 32em; - padding: 1.0416666%; - background:#eee; - border:1px solid #ddd; - -webkit-border-radius: 20px; - -moz-border-radius: 20px; - border-radius: 20px; } + +#gear { + position: fixed; + bottom: 0px; + height: 20px; + width: 100%; +} + +#gearbutton { + position: relative; + -webkit-border-top-left-radius: 2px; + -moz-border-radius-top-left: 2px; + border-top-left-radius: 2px; + float: right; + margin-right: 0px; + background-color: black; + height: 20px; + img { + vertical-align: top; + padding: 2px 2px 0px 2px; + } +} + +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1020; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +.modal-open { + .dropdown-menu { z-index: @zindexDropdown + @zindexModal; } + .dropdown.open { *z-index: @zindexDropdown + @zindexModal; } + .popover { z-index: @zindexPopover + @zindexModal; } + .tooltip { z-index: @zindexTooltip + @zindexModal; } +} + +// Background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindexModalBackdrop; + background-color: black; + // Fade for backdrop + &.fade { opacity: 0; } +} + +.modal-backdrop, +.modal-backdrop.fade.in { + .opacity(80); +} + +#gradient { + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(top, @startColor, @endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(top, @startColor, @endColor); // The standard + background-repeat: repeat-x; + } +} + +.buttonBackground(@startColor, @endColor) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + + // in these cases the gradient won't cover the background, so we override + &:hover, &:active, &.active, &.disabled, &[disabled] { + background-color: @endColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + + +.gradientBar(@primaryColor, @secondaryColor) { + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +@btnBackground: #fff; +@btnBackgroundHighlight: darken(#fff, 10%); +@btnBorder: #ccc; + +.btn { + display: inline-block; + padding: 4px 10px 4px; + margin-bottom: 0; // For input.btn + font-size: @baseFontSize; + line-height: @baseLineHeight; + *line-height: 20px; + color: #333; + text-align: center; + text-shadow: 0 1px 1px rgba(255,255,255,.75); + vertical-align: middle; + cursor: pointer; + .buttonBackground(@btnBackground, @btnBackgroundHighlight); + border: 1px solid @btnBorder; + *border: 0; // Remove the border to prevent IE7's black border on input:focus + border-bottom-color: darken(@btnBorder, 10%); + .border-radius(4px); + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); +} + +// Hover state +.btn:hover { + color: #333; + text-decoration: none; + background-color: darken(white, 10%); + *background-color: darken(white, 15%); /* Buttons in IE7 don't get borders, so darken on hover */ + background-position: 0 -15px; + + // transition is only when going to hover, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); +} + +// Focus state for keyboard and accessibility +.btn:focus { + .tab-focus(); +} + +.tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Active state +.btn.active, +.btn:active { + background-color: darken(#fff, 10%); + background-color: darken(#fff, 15%) e("\9"); + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); +} +// Disabled state +.btn.disabled, +.btn[disabled] { + cursor: default; + background-color: darken(#fff, 10%); + background-image: none; + .opacity(65); + .box-shadow(none); +} + +// Base modal +.modal { + position: fixed; + top: 50%; + left: 50%; + z-index: @zindexModal; + overflow: auto; + width: 560px; + margin: -250px 0 0 -280px; + background-color: white; + border: 1px solid #999; + border: 1px solid rgba(0,0,0,.3); + *border: 1px solid #999; /* IE6-7 */ + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + .background-clip(padding-box); + &.fade { + .transition(e('opacity .3s linear, top .3s ease-out')); + top: -25%; + } + &.fade.in { top: 50%; } +} +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; + // Close icon + .close { margin-top: 2px; float: left } +} + +// Body (where all modal content resides) +.modal-body { + overflow-y: auto; + max-height: 400px; + padding: 15px; +} +// Remove bottom margin if need be +.modal-form { + margin-bottom: 0; +} + +// Footer (for actions) +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; // right align buttons + background-color: #f5f5f5; + border-top: 1px solid #ddd; + .border-radius(0 0 6px 6px); + .box-shadow(inset 0 1px 0 white); + .clearfix(); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } +} + +.hide { + display: none; +} +.show { + display: block; +} + +