Closes #88.
Unified · Split
| @@ -2,6 +2,7 @@ class app.Settings · 1 unmodified line | ||
| 2 | 2 | SETTINGS_KEY = 'settings' |
| 3 | 3 | DOCS_KEY = 'docs' |
| 4 | 4 | DARK_KEY = 'dark' |
| 5 | + SIZE_KEY = 'size' | |
| 5 | 6 | |
| 6 | 7 | @defaults: -> |
| 7 | 8 | count: 0 |
| @@ -59,8 +60,15 @@ class app.Settings · 51 unmodified lines | ||
| 59 | 60 | catch |
| 60 | 61 | return |
| 61 | 62 | |
| 63 | + setSize: (value) -> | |
| 64 | + try | |
| 65 | + Cookies.set SIZE_KEY, value, path: '/', expires: 1e8 | |
| 66 | + catch | |
| 67 | + return | |
| 68 | + | |
| 62 | 69 | reset: -> |
| 63 | 70 | try Cookies.expire DOCS_KEY |
| 64 | 71 | try Cookies.expire DARK_KEY |
| 72 | + try Cookies.expire SIZE_KEY | |
| 65 | 73 | try @store.del(SETTINGS_KEY) |
| 66 | 74 | return |
| @@ -1,5 +1,8 @@ | ||
| 1 | 1 | [ |
| 2 | − [ "2015-02-15", | |
| 2 | + [ "2015-02-16", | |
| 3 | + "The sidebar is now resizable (drag & drop)." | |
| 4 | + ], [ | |
| 5 | + "2015-02-15", | |
| 3 | 6 | "New <a href=\"/iojs/\">io.js</a>, <a href=\"/symfony/\">Symfony</a>, <a href=\"/clojure/\">Clojure</a>, <a href=\"/lua/\">Lua</a> and <a href=\"/yii1/\">Yii 1.1</a> documentations" |
| 4 | 7 | ], [ |
| 5 | 8 | "2015-02-08", |
| @@ -10,6 +10,7 @@ class app.views.Document extends app.View · 9 unmodified lines | ||
| 10 | 10 | init: -> |
| 11 | 11 | @addSubview @nav = new app.views.Nav, |
| 12 | 12 | @addSubview @sidebar = new app.views.Sidebar |
| 13 | + @addSubview @resizer = new app.views.Resizer if app.views.Resizer.isSupported() | |
| 13 | 14 | @addSubview @content = new app.views.Content |
| 14 | 15 | @addSubview @path = new app.views.Path unless app.isSingleDoc() or app.isMobile() |
| 15 | 16 | |
| @@ -0,0 +1,46 @@ | ||
| 1 | +class app.views.Resizer extends app.View | |
| 2 | + @className: '_resizer' | |
| 3 | + | |
| 4 | + @events: | |
| 5 | + dragstart: 'onDragStart' | |
| 6 | + dragend: 'onDragEnd' | |
| 7 | + drag: 'onDrag' | |
| 8 | + | |
| 9 | + @isSupported: -> | |
| 10 | + 'ondragstart' of document.createElement('div') and !app.isMobile() | |
| 11 | + | |
| 12 | + init: -> | |
| 13 | + @el.setAttribute('draggable', 'true') | |
| 14 | + @appendTo $('._app') | |
| 15 | + | |
| 16 | + @style = $('style[data-resizer]') | |
| 17 | + @size = @style.getAttribute('data-size') | |
| 18 | + return | |
| 19 | + | |
| 20 | + MIN = 250 | |
| 21 | + MAX = 600 | |
| 22 | + | |
| 23 | + resize: (newSize) -> | |
| 24 | + return unless newSize > 0 | |
| 25 | + newSize = Math.min(Math.max(Math.round(newSize), MIN), MAX) | |
| 26 | + app.settings.setSize(newSize) | |
| 27 | + newSize = "#{newSize}px" | |
| 28 | + @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize) | |
| 29 | + @size = newSize | |
| 30 | + return | |
| 31 | + | |
| 32 | + onDragStart: (event) => | |
| 33 | + @style.removeAttribute('disabled') | |
| 34 | + event.dataTransfer.effectAllowed = 'link' | |
| 35 | + event.dataTransfer.setData('text/plain', '') | |
| 36 | + return | |
| 37 | + | |
| 38 | + onDrag: (event) => | |
| 39 | + return if @lastDrag and @lastDrag > Date.now() - 50 | |
| 40 | + @lastDrag = Date.now() | |
| 41 | + @resize event.clientX | |
| 42 | + return | |
| 43 | + | |
| 44 | + onDragEnd: (event) => | |
| 45 | + @resize event.screenX - window.screenX | |
| 46 | + return | |
| @@ -35,6 +35,17 @@ · 34 unmodified lines | ||
| 35 | 35 | a:focus { outline: 0; } |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | +._resizer { | |
| 39 | + position: absolute; | |
| 40 | + z-index: $sidebarZ + 1; | |
| 41 | + top: $headerHeight; | |
| 42 | + bottom: 0; | |
| 43 | + left: $sidebarWidth; | |
| 44 | + margin-left: -2px; | |
| 45 | + width: 3px; | |
| 46 | + cursor: col-resize; | |
| 47 | +} | |
| 48 | + | |
| 38 | 49 | // |
| 39 | 50 | // List |
| 40 | 51 | // |
| @@ -136,6 +136,10 @@ class App < Sinatra::Application · 135 unmodified lines | ||
| 136 | 136 | dark: stylesheet_path('application-dark') |
| 137 | 137 | } |
| 138 | 138 | end |
| 139 | + | |
| 140 | + def size | |
| 141 | + @size ||= cookies[:size].nil? ? '18rem' : "#{cookies[:size]}px" | |
| 142 | + end | |
| 139 | 143 | end |
| 140 | 144 | |
| 141 | 145 | before do |
| @@ -20,6 +20,17 @@ class AppTest < MiniTest::Spec · 19 unmodified lines | ||
| 20 | 20 | assert last_response.redirect? |
| 21 | 21 | assert_equal 'http://example.org/', last_response['Location'] |
| 22 | 22 | end |
| 23 | + | |
| 24 | + it "sets default size" do | |
| 25 | + get '/' | |
| 26 | + assert_includes last_response.body, 'data-size="18rem"' | |
| 27 | + end | |
| 28 | + | |
| 29 | + it "sets size from cookie" do | |
| 30 | + set_cookie('size=42') | |
| 31 | + get '/' | |
| 32 | + assert_includes last_response.body, 'data-size="42px"' | |
| 33 | + end | |
| 23 | 34 | end |
| 24 | 35 | |
| 25 | 36 | describe "/[static-page]" do |
| @@ -44,3 +44,9 @@ · 43 unmodified lines | ||
| 44 | 44 | a.src="//secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0]; |
| 45 | 45 | b.parentNode.insertBefore(a,b)}(); |
| 46 | 46 | </script><% end %> |
| 47 | +<style data-size="<%= size %>" data-resizer disabled> | |
| 48 | + ._container { margin-left: <%= size %>; } | |
| 49 | + ._search, ._list, ._sidebar-footer { width: <%= size %>; } | |
| 50 | + ._list-hover.clone { min-width: <%= size %>; } | |
| 51 | + ._notice, ._path, ._resizer { left: <%= size %>; } | |
| 52 | +</style> | |