ソースを参照

Add settings page

Thibaut Courouble 8 年 前
コミット
e94af979d9
59 ファイル変更495 行追加393 行削除
  1. BIN
      assets/images/icons.png
  2. BIN
      assets/images/icons@2x.png
  3. 6 1
      assets/javascripts/app/appcache.coffee
  4. 15 10
      assets/javascripts/app/router.coffee
  5. 3 0
      assets/javascripts/app/settings.coffee
  6. 3 3
      assets/javascripts/news.json
  7. 1 1
      assets/javascripts/templates/notice_tmpl.coffee
  8. 1 1
      assets/javascripts/templates/notif_tmpl.coffee
  9. 1 1
      assets/javascripts/templates/pages/offline_tmpl.coffee
  10. 12 12
      assets/javascripts/templates/pages/root_tmpl.coffee.erb
  11. 27 0
      assets/javascripts/templates/pages/settings_tmpl.coffee
  12. 2 14
      assets/javascripts/templates/sidebar_tmpl.coffee
  13. 8 5
      assets/javascripts/views/content/content.coffee
  14. 52 0
      assets/javascripts/views/content/settings_page.coffee
  15. 11 26
      assets/javascripts/views/layout/document.coffee
  16. 42 10
      assets/javascripts/views/layout/mobile.coffee
  17. 0 7
      assets/javascripts/views/layout/resizer.coffee
  18. 80 0
      assets/javascripts/views/layout/settings.coffee
  19. 0 8
      assets/javascripts/views/search/search.coffee
  20. 2 42
      assets/javascripts/views/sidebar/doc_picker.coffee
  21. 2 26
      assets/javascripts/views/sidebar/sidebar.coffee
  22. 1 0
      assets/stylesheets/application-dark.css.scss
  23. 1 0
      assets/stylesheets/application.css.scss
  24. 1 4
      assets/stylesheets/components/_content.scss
  25. 3 5
      assets/stylesheets/components/_header.scss
  26. 20 19
      assets/stylesheets/components/_mobile.scss
  27. 2 1
      assets/stylesheets/components/_notice.scss
  28. 1 1
      assets/stylesheets/components/_path.scss
  29. 146 0
      assets/stylesheets/components/_settings.scss
  30. 5 162
      assets/stylesheets/components/_sidebar.scss
  31. 11 0
      assets/stylesheets/global/_base.scss
  32. 8 18
      assets/stylesheets/global/_icons.scss
  33. 1 1
      assets/stylesheets/global/_variables-dark.scss
  34. 1 1
      assets/stylesheets/global/_variables.scss
  35. 1 1
      lib/app.rb
  36. 0 1
      public/icons/ui/check-white/SOURCE
  37. BIN
      public/icons/ui/check-white/check-white.png
  38. BIN
      public/icons/ui/check-white/check-white@2x.png
  39. 0 1
      public/icons/ui/check/SOURCE
  40. BIN
      public/icons/ui/check/check.png
  41. BIN
      public/icons/ui/check/check@2x.png
  42. BIN
      public/icons/ui/contract/16.png
  43. BIN
      public/icons/ui/contract/16@2x.png
  44. BIN
      public/icons/ui/expand/16.png
  45. BIN
      public/icons/ui/expand/16@2x.png
  46. 0 1
      public/icons/ui/light-white/SOURCE
  47. BIN
      public/icons/ui/light-white/light.png
  48. BIN
      public/icons/ui/light-white/light@2x.png
  49. 0 1
      public/icons/ui/light/SOURCE
  50. BIN
      public/icons/ui/light/light.png
  51. BIN
      public/icons/ui/light/light@2x.png
  52. 0 1
      public/icons/ui/settings-white/SOURCE
  53. BIN
      public/icons/ui/settings-white/settings-white.png
  54. BIN
      public/icons/ui/settings-white/settings-white@2x.png
  55. 0 1
      public/icons/ui/settings/SOURCE
  56. BIN
      public/icons/ui/settings/settings.png
  57. BIN
      public/icons/ui/settings/settings@2x.png
  58. 1 1
      public/robots.txt
  59. 24 6
      views/app.erb

BIN
assets/images/icons.png


BIN
assets/images/icons@2x.png


+ 6 - 1
assets/javascripts/app/appcache.coffee

@@ -16,21 +16,26 @@ class app.AppCache
 
   update: ->
     @notifyUpdate = true
+    @notifyProgress = true
     try @cache.update() catch
     return
 
   updateInBackground: ->
     @notifyUpdate = false
+    @notifyProgress = false
     try @cache.update() catch
     return
 
   reload: ->
     $.on @cache, 'updateready noupdate error', -> window.location = '/'
     @updateInBackground()
+    @notifyUpdate = false
+    @notifyProgress = true
+    @cache.update()
     return
 
   onProgress: (event) =>
-    @trigger 'progress', event
+    @trigger 'progress', event if @notifyProgress
     return
 
   onUpdateReady: =>

+ 15 - 10
assets/javascripts/app/router.coffee

@@ -2,16 +2,17 @@ class app.Router
   $.extend @prototype, Events
 
   @routes: [
-    ['*',              'before'  ]
-    ['/',              'root'    ]
-    ['/offline',       'offline' ]
-    ['/about',         'about'   ]
-    ['/news',          'news'    ]
-    ['/help',          'help'    ]
-    ['/:doc-:type/',   'type'    ]
-    ['/:doc/',         'doc'     ]
-    ['/:doc/:path(*)', 'entry'   ]
-    ['*',              'notFound']
+    ['*',              'before'   ]
+    ['/',              'root'     ]
+    ['/settings',      'settings' ]
+    ['/offline',       'offline'  ]
+    ['/about',         'about'    ]
+    ['/news',          'news'     ]
+    ['/help',          'help'     ]
+    ['/:doc-:type/',   'type'     ]
+    ['/:doc/',         'doc'      ]
+    ['/:doc/:path(*)', 'entry'    ]
+    ['*',              'notFound' ]
   ]
 
   constructor: ->
@@ -76,6 +77,10 @@ class app.Router
       @triggerRoute 'root'
     return
 
+  settings: ->
+    @triggerRoute 'settings'
+    return
+
   offline: ->
     @triggerRoute 'offline'
     return

+ 3 - 0
assets/javascripts/app/settings.coffee

@@ -57,6 +57,9 @@ class app.Settings
     @store.set DARK_KEY, !!value
     return
 
+  getDark: ->
+    @store.get DARK_KEY
+
   setLayout: (name, enable) ->
     layout = (@store.get(LAYOUT_KEY) || '').split(' ')
     $.arrayDelete(layout, '')

+ 3 - 3
assets/javascripts/news.json

@@ -64,7 +64,7 @@
     "New documentations: <a href=\"/erlang/\">Erlang</a> and <a href=\"/tcl_tk/\">Tcl/Tk</a>"
   ], [
     "2016-01-24",
-    "&ldquo;Multi-version support&rdquo; has landed!\nClick <a href=\"#\" data-pick-docs>Select documentation</a> to pick which versions to use. More versions will be added in the coming weeks.\nIf you notice any bugs, please report them on <a href=\"https://github.com/Thibaut/devdocs/issues\" target=\"_blank\" rel=\"noopener\">GitHub</a>."
+    "&ldquo;Multi-version support&rdquo; has landed!"
   ], [
     "2015-11-22",
     "New documentations: <a href=\"/phoenix/\">Phoenix</a>, <a href=\"/dojo/\">Dojo</a>, <a href=\"/relay/\">Relay</a> and <a href=\"/flow/\">Flow</a>"
@@ -117,10 +117,10 @@
     "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"
   ], [
     "2015-02-08",
-    "New dark theme\nClick the icon in the bottom left corner to activate.\n<a href=\"https://github.com/Thibaut/devdocs/issues\" target=\"_blank\" rel=\"noopener\">Feedback</a> welcome :)"
+    "New dark theme"
   ], [
     "2015-01-13",
-    "<a href=\"/offline\">Offline mode</a> has landed!\nIf you notice any bugs, please report them on <a href=\"https://github.com/Thibaut/devdocs/issues\" target=\"_blank\" rel=\"noopener\">GitHub</a>."
+    "<a href=\"/offline\">Offline mode</a> has landed!"
   ], [
     "2014-12-21",
     "New <a href=\"/react/\">React</a>, <a href=\"/rethinkdb/\">RethinkDB</a>, <a href=\"/socketio/\">Socket.IO</a>, <a href=\"/modernizr/\">Modernizr</a> and <a href=\"/bower/\">Bower</a> documentations"

+ 1 - 1
assets/javascripts/templates/notice_tmpl.coffee

@@ -6,4 +6,4 @@ app.templates.singleDocNotice = (doc) ->
 
 app.templates.disabledDocNotice = ->
   notice """ <strong>This documentation is disabled.</strong>
-             To enable it, click <a href="#" class="_notice-link" data-pick-docs>Select documentation</a>. """
+             To enable it, go to <a href="/settings" class="_notice-link">Preferences</a>. """

+ 1 - 1
assets/javascripts/templates/notif_tmpl.coffee

@@ -47,7 +47,7 @@ app.templates.notifUpdates = (docs, disabledDocs) ->
     for doc in disabledDocs
       html += "<li>#{doc.name}"
       html += " <code>&rarr;</code> #{doc.release}" if doc.release
-      html += """<span class="_notif-info"><a data-pick-docs>Enable</a></span>"""
+      html += """<span class="_notif-info"><a href="/settings">Enable</a></span>"""
     html += '</ul></div>'
 
   notif 'Updates', "#{html}</div>"

+ 1 - 1
assets/javascripts/templates/pages/offline_tmpl.coffee

@@ -35,7 +35,7 @@ app.templates.offlinePage = (docs) -> """
     <dt>How do I uninstall/reset the app?
     <dd>Click <a href="#" data-behavior="reset">here</a>.
     <dt>Why aren't all documentations listed above?
-    <dd>You have to <a href="#" data-pick-docs>enable</a> them first.
+    <dd>You have to <a href="/settings">enable</a> them first.
   </dl>
 """
 

+ 12 - 12
assets/javascripts/templates/pages/root_tmpl.coffee.erb

@@ -32,14 +32,14 @@ app.templates.intro = """
     <p>DevDocs combines multiple API documentations in a fast, organized, and searchable interface.
        Here's what you should know before you start:
     <ol class="_intro-list">
-      <li>To enable more docs, click <a class="_intro-link" data-pick-docs>Select documentation</a> in the bottom left corner
-      <li>You don't have to use your mouse &mdash; see the list of <a href="/help#shortcuts">keyboard shortcuts</a>
-      <li>The search supports fuzzy matching (e.g. "bgcp" brings up "background-clip")
-      <li>To search a specific documentation, type its name (or an abbreviation), then Tab
-      <li>You can search using your browser's address bar &mdash; <a href="/help#browser_search">learn how</a>
+      <li>Open the <a href="/settings">Preferences</a> to enable more docs and customize the UI.
+      <li>You don't have to use your mouse &mdash; see the list of <a href="/help#shortcuts">keyboard shortcuts</a>.
+      <li>The search supports fuzzy matching (e.g. "bgcp" brings up "background-clip").
+      <li>To search a specific documentation, type its name (or an abbr.), then Tab.
+      <li>You can search using your browser's address bar &mdash; <a href="/help#browser_search">learn how</a>.
       <li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>.
-      <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>
-      <li>DevDocs is free and <a href="https://github.com/Thibaut/devdocs">open source</a>
+      <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
+      <li>DevDocs is free and <a href="https://github.com/Thibaut/devdocs">open source</a>.
           <iframe class="_github-btn" src="//ghbtns.com/github-btn.html?user=Thibaut&repo=devdocs&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="100" height="20"></iframe>
       <li>If you like the app, please consider supporting the project on <a href="https://gratipay.com/devdocs/">Gratipay</a>. Thanks!
     </ol>
@@ -54,11 +54,11 @@ app.templates.mobileIntro = """
     <p>DevDocs combines multiple API documentations in a fast, organized, and searchable interface.
        Here's what you should know before you start:
     <ol class="_intro-list">
-      <li>To pick your docs, click <a data-pick-docs>Select documentation</a> at the bottom of the menu
-      <li>The search supports fuzzy matching (e.g. "bgcp" matches "background-clip")
-      <li>To search a specific documentation, type its name (or an abbreviation), then Space
-      <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>
-      <li>DevDocs is <a href="https://github.com/Thibaut/devdocs">open source</a>
+      <li>Pick your docs in the <a href="/settings">Preferences</a>.
+      <li>The search supports fuzzy matching.
+      <li>To search a specific documentation, type its name (or an abbr.), then Space.
+      <li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
+      <li>DevDocs is <a href="https://github.com/Thibaut/devdocs">open source</a>.
     </ol>
     <p>Happy coding!
     <a class="_intro-hide" data-hide-intro>Stop showing this message</a>

+ 27 - 0
assets/javascripts/templates/pages/settings_tmpl.coffee

@@ -0,0 +1,27 @@
+app.templates.settingsPage = (settings) -> """
+  <h1 class="_lined-heading">Preferences</h1>
+
+  <div class="_settings-fieldset">
+    <h2 class="_settings-legend">General:</h2>
+
+    <div class="_settings-inputs">
+      <label class="_settings-label">
+        <input type="checkbox" name="dark" value="1"#{if settings.dark then ' checked' else ''}>Enable dark theme
+      </label>
+      <label class="_settings-label _settings-max-width">
+        <input type="checkbox" name="layout" value="_max-width"#{if settings['_max-width'] then ' checked' else ''}>Enable fixed-width layout
+      </label>
+      <label class="_settings-label">
+        <input type="checkbox" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
+      </label>
+    </div>
+  </div>
+
+  <div class="_settings-fieldset">
+    <h2 class="_settings-legend">Advanced:</h2>
+
+    <div class="_settings-inputs">
+      <a href="#" class="_settings-link" data-behavior="reset">Reset all settings and data</a>
+    </div>
+  </div>
+"""

+ 2 - 14
assets/javascripts/templates/sidebar_tmpl.coffee

@@ -30,7 +30,7 @@ templates.sidebarResult = (entry) ->
 templates.sidebarNoResults = ->
   html = """ <div class="_list-note">No results.</div> """
   html += """
-    <div class="_list-note">Note: documentations must be <a href="#" class="_list-note-link" data-pick-docs>enabled</a> to appear in the search.</div>
+    <div class="_list-note">Note: documentations must be <a href="/settings" class="_list-note-link">enabled</a> to appear in the search.</div>
   """ unless app.isSingleDoc() or app.disabledDocs.isEmpty()
   html
 
@@ -58,19 +58,7 @@ templates.sidebarDisabledList = (html) ->
 templates.sidebarDisabledVersionedDoc = (doc, versions) ->
   """<a class="_list-item _list-dir _icon-#{doc.icon} _list-disabled" data-slug="#{doc.slug_without_version}" tabindex="-1"><span class="_list-arrow"></span>#{doc.name}</a><div class="_list _list-sub">#{versions}</div>"""
 
-templates.sidebarPickerNote = """
+templates.docPickerNote = """
   <div class="_list-note">Tip: for faster and better search results, select only the docs you need.</div>
   <a href="https://trello.com/b/6BmTulfx/devdocs-documentation" class="_list-link" target="_blank" rel="noopener">Vote for new documentation</a>
   """
-
-sidebarFooter = (html) -> """<div class="_sidebar-footer">#{html}</div>"""
-
-templates.sidebarSettings = ->
-  sidebarFooter """
-    <button type="button" class="_sidebar-footer-link _sidebar-footer-edit" data-pick-docs>Select documentation</button>
-    <button type="button" class="_sidebar-footer-link _sidebar-footer-light" title="Toggle light" data-light>Toggle light</button>
-    <button type="button" class="_sidebar-footer-link _sidebar-footer-layout" title="Toggle layout" data-layout>Toggle layout</button>
-  """
-
-templates.sidebarSave = ->
-  sidebarFooter """<a class="_sidebar-footer-link _sidebar-footer-save" role="button">Save</a>"""

+ 8 - 5
assets/javascripts/views/content/content.coffee

@@ -23,11 +23,12 @@ class app.views.Content extends app.View
     @scrollMap = {}
     @scrollStack = []
 
-    @rootPage    = new app.views.RootPage
-    @staticPage  = new app.views.StaticPage
-    @offlinePage = new app.views.OfflinePage
-    @typePage    = new app.views.TypePage
-    @entryPage   = new app.views.EntryPage
+    @rootPage     = new app.views.RootPage
+    @staticPage   = new app.views.StaticPage
+    @settingsPage = new app.views.SettingsPage
+    @offlinePage  = new app.views.OfflinePage
+    @typePage     = new app.views.TypePage
+    @entryPage    = new app.views.EntryPage
 
     @entryPage
       .on 'loading', @onEntryLoading
@@ -148,6 +149,8 @@ class app.views.Content extends app.View
         @show @entryPage
       when 'type'
         @show @typePage
+      when 'settings'
+        @show @settingsPage
       when 'offline'
         @show @offlinePage
       else

+ 52 - 0
assets/javascripts/views/content/settings_page.coffee

@@ -0,0 +1,52 @@
+class app.views.SettingsPage extends app.View
+  LAYOUTS = ['_max-width', '_sidebar-hidden']
+  SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden'
+
+  @className: '_static'
+
+  @events:
+    change: 'onChange'
+
+  render: ->
+    @html @tmpl('settingsPage', @currentSettings())
+    return
+
+  currentSettings: ->
+    settings = {}
+    settings.dark = app.settings.getDark()
+    settings[layout] = app.settings.hasLayout(layout) for layout in LAYOUTS
+    settings
+
+  getTitle: ->
+    'Preferences'
+
+  toggleDark: (enable) ->
+    css = $('link[rel="stylesheet"][data-alt]')
+    alt = css.getAttribute('data-alt')
+    css.setAttribute('data-alt', css.getAttribute('href'))
+    css.setAttribute('href', alt)
+    app.settings.setDark(enable)
+    app.appCache?.updateInBackground()
+    return
+
+  toggleLayout: (layout, enable) ->
+    app.el.classList[if enable then 'add' else 'remove'](layout) unless layout is SIDEBAR_HIDDEN_LAYOUT
+    app.settings.setLayout(layout, enable)
+    app.appCache?.updateInBackground()
+    return
+
+  onChange: (event) =>
+    input = event.target
+    switch input.name
+      when 'dark'
+        @toggleDark input.checked
+      when 'layout'
+        @toggleLayout input.value, input.checked
+    return
+
+  onRoute: (route) =>
+    if app.isSingleDoc()
+      window.location = "/#/#{route.path}"
+    else
+      @render()
+    return

+ 11 - 26
assets/javascripts/views/layout/document.coffee

@@ -1,7 +1,4 @@
 class app.views.Document extends app.View
-  MAX_WIDTH_LAYOUT = '_max-width'
-  SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden'
-
   @el: document
 
   @events:
@@ -13,44 +10,32 @@ class app.views.Document extends app.View
     superLeft:  'onBack'
     superRight: 'onForward'
 
+  @routes:
+    after: 'afterRoute'
+
   init: ->
     @addSubview @menu    = new app.views.Menu,
     @addSubview @sidebar = new app.views.Sidebar
     @addSubview @resizer = new app.views.Resizer if app.views.Resizer.isSupported()
     @addSubview @content = new app.views.Content
     @addSubview @path    = new app.views.Path unless app.isSingleDoc() or app.isMobile()
+    @settings = new app.views.Settings unless app.isSingleDoc()
 
     $.on document.body, 'click', @onClick
 
     @activate()
     return
 
-  toggleLight: ->
-    css = $('link[rel="stylesheet"][data-alt]')
-    alt = css.getAttribute('data-alt')
-    css.setAttribute('data-alt', css.getAttribute('href'))
-    css.setAttribute('href', alt)
-    app.settings.setDark(alt.indexOf('dark') > 0)
-    app.appCache?.updateInBackground()
-    return
-
-  toggleLayout: ->
-    wantsMaxWidth = !app.el.classList.contains(MAX_WIDTH_LAYOUT)
-    app.el.classList[if wantsMaxWidth then 'add' else 'remove'](MAX_WIDTH_LAYOUT)
-    app.settings.setLayout(MAX_WIDTH_LAYOUT, wantsMaxWidth)
-    app.appCache?.updateInBackground()
-    return
-
-  toggleSidebarLayout: ->
-    shouldHide = !app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)
-    app.el.classList[if shouldHide then 'add' else 'remove'](SIDEBAR_HIDDEN_LAYOUT)
-    app.settings.setLayout(SIDEBAR_HIDDEN_LAYOUT, shouldHide)
-    app.appCache?.updateInBackground()
-    return
-
   setTitle: (title) ->
     @el.title = if title then "DevDocs — #{title}" else 'DevDocs API Documentation'
 
+  afterRoute: (route) =>
+    if route is 'settings'
+      @settings?.activate()
+    else
+      @settings?.deactivate()
+    return
+
   onVisibilityChange: =>
     return unless @el.visibilityState is 'visible'
     @delay ->

+ 42 - 10
assets/javascripts/views/layout/mobile.coffee

@@ -2,9 +2,10 @@ class app.views.Mobile extends app.View
   @className: '_mobile'
 
   @elements:
-    body:     'body'
-    content:  '._container'
-    sidebar:  '._sidebar'
+    body:      'body'
+    content:   '._container'
+    sidebar:   '._sidebar'
+    docPicker: '._settings ._sidebar'
 
   @routes:
     after: 'afterRoute'
@@ -34,7 +35,6 @@ class app.views.Mobile extends app.View
   init: ->
     FastClick.attach @body
 
-    $.on @body, 'click', @onClick
     $.on $('._search'), 'touchend', @onTapSearch
 
     @toggleSidebar = $('button[data-toggle-sidebar]')
@@ -49,6 +49,14 @@ class app.views.Mobile extends app.View
     @forward.removeAttribute('hidden')
     $.on @forward, 'click', @onClickForward
 
+    @docPickerTab = $('a[data-tab="doc-picker"]')
+    @docPickerTab.removeAttribute('hidden')
+    $.on @docPickerTab, 'click', @onClickDocPickerTab
+
+    @settingsTab = $('a[data-tab="settings"]')
+    @settingsTab.removeAttribute('hidden')
+    $.on @settingsTab, 'click', @onClickSettingsTab
+
     app.document.sidebar.search
       .on 'searching', @showSidebar
 
@@ -82,11 +90,6 @@ class app.views.Mobile extends app.View
   isSidebarShown: ->
     @sidebar.style.display isnt 'none'
 
-  onClick: (event) =>
-    if event.target.hasAttribute 'data-pick-docs'
-      @showSidebar()
-    return
-
   onClickBack: =>
     history.back()
 
@@ -97,12 +100,41 @@ class app.views.Mobile extends app.View
     if @isSidebarShown() then @hideSidebar() else @showSidebar()
     return
 
+  onClickDocPickerTab: (event) =>
+    $.stopEvent(event)
+    @showDocPicker()
+    return
+
+  onClickSettingsTab: (event) =>
+    $.stopEvent(event)
+    @showSettings()
+    return
+
+  showDocPicker: ->
+    @docPickerTab.classList.add 'active'
+    @settingsTab.classList.remove 'active'
+    @docPicker.style.display = 'block'
+    @content.style.display = 'none'
+    return
+
+  showSettings: ->
+    @docPickerTab.classList.remove 'active'
+    @settingsTab.classList.add 'active'
+    @docPicker.style.display = 'none'
+    @content.style.display = 'block'
+    return
+
   onTapSearch: =>
     window.scrollTo 0, 0
 
-  afterRoute: =>
+  afterRoute: (route) =>
     @hideSidebar()
 
+    if route is 'settings'
+      @showDocPicker()
+    else
+      @content.style.display = 'block'
+
     if page.canGoBack()
       @back.removeAttribute('disabled')
     else

+ 0 - 7
assets/javascripts/views/layout/resizer.coffee

@@ -1,12 +1,9 @@
 class app.views.Resizer extends app.View
   @className: '_resizer'
-  @attributes:
-    title: 'Click to toggle sidebar on/off'
 
   @events:
     dragstart: 'onDragStart'
     dragend: 'onDragEnd'
-    click: 'onClick'
 
   @isSupported: ->
     'ondragstart' of document.createElement('div') and !app.isMobile()
@@ -34,10 +31,6 @@ class app.views.Resizer extends app.View
       app.appCache?.updateInBackground()
     return
 
-  onClick: ->
-    app.document.toggleSidebarLayout()
-    return
-
   onDragStart: (event) =>
     @style.removeAttribute('disabled')
     event.dataTransfer.effectAllowed = 'link'

+ 80 - 0
assets/javascripts/views/layout/settings.coffee

@@ -0,0 +1,80 @@
+class app.views.Settings extends app.View
+  SIDEBAR_HIDDEN_LAYOUT = '_sidebar-hidden'
+
+  @el: '._settings'
+
+  @elements:
+    sidebar: '._sidebar'
+    saveBtn: 'button[type="submit"]'
+    backBtn: 'button[data-back]'
+
+  @events:
+    submit: 'onSubmit'
+    click: 'onClick'
+    focus: 'onFocus'
+
+  @shortcuts:
+    enter: 'onEnter'
+
+  init: ->
+    @addSubview @docPicker = new app.views.DocPicker
+    return
+
+  activate: ->
+    if super
+      @render()
+      app.el.classList.remove(SIDEBAR_HIDDEN_LAYOUT)
+      app.appCache?.on 'progress', @onAppCacheProgress
+    return
+
+  deactivate: ->
+    if super
+      @resetClass()
+      @docPicker.detach()
+      app.el.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)
+      app.appCache?.off 'progress', @onAppCacheProgress
+    return
+
+  render: ->
+    @docPicker.appendTo @sidebar
+    @refreshElements()
+    @addClass '_in'
+    return
+
+  save: ->
+    unless @saving
+      @saving = true
+      docs = @docPicker.getSelectedDocs()
+      app.settings.setDocs(docs)
+      @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'
+      disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)
+      disabledDocs.uninstall ->
+        app.db.migrate()
+        app.reload()
+    return
+
+  onEnter: =>
+    @save()
+    return
+
+  onSubmit: (event) =>
+    event.preventDefault()
+    @save()
+    return
+
+  onClick: (event) =>
+    return if event.which isnt 1
+    if event.target is @backBtn
+      $.stopEvent(event)
+      app.router.show '/'
+    return
+
+  onFocus: (event) =>
+    $.scrollTo event.target, @el, 'continuous', bottomGap: 2
+    return
+
+  onAppCacheProgress: (event) =>
+    if event.lengthComputable
+      percentage = Math.round event.loaded * 100 / event.total
+      @saveBtn.textContent = "Downloading\u2026 (#{percentage}%)"
+    return

+ 0 - 8
assets/javascripts/views/search/search.coffee

@@ -51,14 +51,6 @@ class app.views.Search extends app.View
     @autoFocus()
     return
 
-  disable: ->
-    @input.setAttribute('disabled', 'disabled')
-    return
-
-  enable: ->
-    @input.removeAttribute('disabled')
-    return
-
   onReady: =>
     @value = ''
     @delay @onInput

+ 2 - 42
assets/javascripts/views/sidebar/doc_picker.coffee

@@ -1,18 +1,9 @@
 class app.views.DocPicker extends app.View
   @className: '_list _list-picker'
-  @attributes:
-    role: 'form'
-
-  @elements:
-    saveLink: '._sidebar-footer-save'
 
   @events:
-    click: 'onClick'
     mousedown: 'onMouseDown'
 
-  @shortcuts:
-    enter: 'onEnter'
-
   init: ->
     @addSubview @listFold = new app.views.ListFold(@el)
     return
@@ -20,14 +11,12 @@ class app.views.DocPicker extends app.View
   activate: ->
     if super
       @render()
-      app.appCache?.on 'progress', @onAppCacheProgress
       $.on @el, 'focus', @onDOMFocus, true
     return
 
   deactivate: ->
     if super
       @empty()
-      app.appCache?.off 'progress', @onAppCacheProgress
       $.off @el, 'focus', @onDOMFocus, true
       @focusEl = null
     return
@@ -43,8 +32,7 @@ class app.views.DocPicker extends app.View
       else
         html += @tmpl('sidebarLabel', doc, checked: app.docs.contains(doc))
 
-    @html html + @tmpl('sidebarPickerNote') + @tmpl('sidebarSave')
-    @refreshElements()
+    @html html + @tmpl('docPickerNote')
 
     $.requestAnimationFrame =>
       @addClass '_in'
@@ -68,31 +56,13 @@ class app.views.DocPicker extends app.View
     super
     return
 
-  save: ->
-    unless @saving
-      @saving = true
-      docs = @getSelectedDocs()
-      app.settings.setDocs(docs)
-      @saveLink.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'
-      disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is -1)
-      disabledDocs.uninstall ->
-        app.db.migrate()
-        app.reload()
-    return
-
   getSelectedDocs: ->
     for input in @findAllByTag 'input' when input?.checked
       input.name
 
-  onClick: (event) =>
-    return if event.which isnt 1
-    if event.target is @saveLink
-      $.stopEvent(event)
-      @save()
-    return
-
   onMouseDown: =>
     @mouseDown = Date.now()
+    return
 
   onDOMFocus: (event) =>
     target = event.target
@@ -112,13 +82,3 @@ class app.views.DocPicker extends app.View
           @delay -> $('input', target.nextElementSibling).focus()
     @focusEl = target
     return
-
-  onEnter: =>
-    @save()
-    return
-
-  onAppCacheProgress: (event) =>
-    if event.lengthComputable
-      percentage = Math.round event.loaded * 100 / event.total
-      @saveLink.textContent = "Downloading\u2026 (#{percentage}%)"
-    return

+ 2 - 26
assets/javascripts/views/sidebar/sidebar.coffee

@@ -22,10 +22,8 @@ class app.views.Sidebar extends app.View
 
     @results = new app.views.Results @, @search
     @docList = new app.views.DocList
-    @docPicker = new app.views.DocPicker unless app.isSingleDoc()
 
     app.on 'ready', @onReady
-    $.on document, 'click', @onGlobalClick if @docPicker
     return
 
   display: ->
@@ -45,23 +43,18 @@ class app.views.Sidebar extends app.View
       @render()
       @view.activate()
       @restoreScrollPosition()
-      if view is @docPicker then @search.disable() else @search.enable()
     return
 
   render: ->
     @html @view
-    @append @tmpl('sidebarSettings') if @view is @docList and @docPicker
     return
 
   showDocList: ->
     @showView @docList
     return
 
-  showDocPicker: =>
-    @showView @docPicker
-    return
-
   showResults: =>
+    @display()
     @showView @results
     return
 
@@ -101,7 +94,6 @@ class app.views.Sidebar extends app.View
     return
 
   onSearching: =>
-    @display()
     @showResults()
     return
 
@@ -124,23 +116,6 @@ class app.views.Sidebar extends app.View
     if event.target.hasAttribute? 'data-reset-list'
       $.stopEvent(event)
       @onAltR()
-    else if event.target.hasAttribute? 'data-light'
-      $.stopEvent(event)
-      document.activeElement?.blur()
-      app.document.toggleLight()
-    else if event.target.hasAttribute? 'data-layout'
-      $.stopEvent(event)
-      document.activeElement?.blur()
-      app.document.toggleLayout()
-    return
-
-  onGlobalClick: (event) =>
-    return if event.which isnt 1
-    if event.target.hasAttribute? 'data-pick-docs'
-      $.stopEvent(event)
-      @showDocPicker()
-    else if @view is @docPicker
-      @showDocList() unless $.hasChild @el, event.target
     return
 
   onAltR: =>
@@ -157,3 +132,4 @@ class app.views.Sidebar extends app.View
   onDocEnabled: ->
     @docList.onEnabled()
     @reset()
+    return

+ 1 - 0
assets/stylesheets/application-dark.css.scss

@@ -20,6 +20,7 @@
         'components/header',
         'components/notif',
         'components/sidebar',
+        'components/settings',
         'components/content',
         'components/page',
         'components/fail',

+ 1 - 0
assets/stylesheets/application.css.scss

@@ -20,6 +20,7 @@
         'components/header',
         'components/notif',
         'components/sidebar',
+        'components/settings',
         'components/content',
         'components/page',
         'components/fail',

+ 1 - 4
assets/stylesheets/components/_content.scss

@@ -12,7 +12,7 @@
 
   @media #{$mediumScreen} { margin-left: $sidebarMediumWidth; }
 
-  ._sidebar-hidden & { margin-left: $sidebarHiddenWidth; }
+  ._sidebar-hidden & { margin-left: 0; }
 }
 
 ._content {
@@ -283,7 +283,6 @@
 }
 
 ._docs-label {
-  display: block;
   overflow: hidden;
   margin: 1px 0;
   padding: .375rem .5rem;
@@ -292,8 +291,6 @@
     display: inline-block;
     vertical-align: top;
     margin: .25rem;
-    width: 1rem;
-    height: 1rem;
   }
 }
 

+ 3 - 5
assets/stylesheets/components/_header.scss

@@ -12,6 +12,8 @@
   height: $headerHeight;
   background: $headerBackground;
   border-bottom: 1px solid $headerBorder;
+  border-right: 1px solid $headerBorder;
+  @extend %border-box;
   @extend %user-select-none;
 
   @media #{$mediumScreen} { width: $sidebarMediumWidth; }
@@ -55,17 +57,13 @@
 // Menu
 //
 
-._menu-btn {
-  border-right: 1px solid $headerBorder;
-}
-
 ._menu {
   position: absolute;
   z-index: 1;
   top: .25rem;
   right: .25rem;
   width: 8rem;
-  height: calc(11.5rem + 1px);
+  height: calc(13.75rem + 1px);
   font-size: .875rem;
   background: $contentBackground;
   border: 1px solid $headerBorder;

+ 20 - 19
assets/stylesheets/components/_mobile.scss

@@ -11,30 +11,42 @@
   body { -ms-overflow-style: -ms-autohiding-scrollbar; }
 
   ._app, ._content { overflow: visible; }
-  ._app { padding-top: $headerHeight; }
-  ._container { margin: 0; }
+
+  ._container, ._sidebar {
+    margin: 0;
+    padding-top: $headerHeight;
+  }
 
   ._content {
     position: static;
     height: auto;
     margin: 0;
-    padding: .75rem 1rem 2.5rem;
+    padding: .75rem 1rem 2rem;
 
     &:before { content: none; }
   }
 
   ._booting:before, ._content-loading:before { font-size: 3rem; }
 
-  // Header
-
-  ._header {
+  ._header, ._footer {
     position: fixed;
-    max-width: 100vw;
+    width: 100%;
   }
 
+  ._header, ._list, ._footer {
+    width: 100%;
+    border-right: 0;
+    box-shadow: none;
+  }
+
+  ._settings { position: static; }
+  ._settings ._sidebar { padding-bottom: $headerHeight; }
+  ._settings-tabs { display: block; }
+
+  // Header
+
   ._header-btn { width: 2.5rem; }
   ._header-btn[hidden] { display: block; }
-  ._menu-btn { border-right: 0; }
 
   ._search {
     padding-right: .125rem;
@@ -53,8 +65,6 @@
     overflow: visible;
   }
 
-  ._header, ._list, ._sidebar-footer { width: 100%; }
-
   ._list-item {
     white-space: normal;
     word-wrap: break-word;
@@ -72,15 +82,6 @@
     }
   }
 
-  ._list-link { display: none; }
-
-  ._sidebar-footer { box-shadow: none; }
-
-  ._sidebar-footer-save {
-    margin: 0;
-    box-shadow: 0 1px $noteGreenBorder, 0 -1px $noteGreenBorder;
-  }
-
   // Notice
 
   ._notice {

+ 2 - 1
assets/stylesheets/components/_notice.scss

@@ -11,7 +11,8 @@
 
   @media #{$mediumScreen} { left: $sidebarMediumWidth; }
 
-  ._sidebar-hidden & { left: $sidebarHiddenWidth; }
+  ._sidebar-hidden & { left: 0; }
+
   ~ ._container { padding-bottom: 2.5rem; }
 }
 

+ 1 - 1
assets/stylesheets/components/_path.scss

@@ -13,7 +13,7 @@
 
   @media #{$mediumScreen} { left: $sidebarMediumWidth; }
 
-  ._sidebar-hidden & { left: $sidebarHiddenWidth; }
+  ._sidebar-hidden & { left: 0; }
 
   ~ ._container { padding-bottom: 2rem; }
   a:focus { outline: 0; }

+ 146 - 0
assets/stylesheets/components/_settings.scss

@@ -0,0 +1,146 @@
+//
+// Settings
+//
+
+._settings {
+  display: none;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  z-index: $headerZ;
+
+  &._in { display: block; }
+
+  ._sidebar {
+    bottom: $headerHeight;
+
+    ._sidebar-hidden & { display: block; }
+  }
+
+  ._header {
+    justify-content: space-between;
+  }
+}
+
+._settings-fieldset {
+  display: flex;
+  margin: 1.5rem 0;
+  line-height: 1.5rem;
+}
+
+._settings-legend {
+  flex: 0 1 10rem;
+  margin: 0;
+  padding-right: .5rem;
+  line-height: inherit;
+  font-size: inherit;
+  font-weight: $boldFontWeight;
+  text-align: right;
+  @extend %border-box;
+}
+
+._settings-inputs {
+  flex: 1 1 20rem;
+}
+
+._settings-label {
+  margin: 0 0 .375rem;
+
+  > small {
+    display: block;
+    color: $textColorLight;
+    margin-left: 1.75rem;
+  }
+
+  input[type=checkbox] {
+    display: inline-block;
+    vertical-align: top;
+    margin: .25rem .375rem;
+  }
+}
+
+@media (max-width: $maxWidth) {
+  ._settings-max-width { display: none; }
+}
+
+._settings-link {
+  display: inline-block;
+  vertical-align: top;
+  margin-left: .375rem;
+
+  &[data-behavior=reset] {
+    font-size: .75rem;
+    color: $textColorRed;
+  }
+}
+
+._footer {
+  position: absolute;
+  z-index: $headerZ;
+  bottom: 0;
+  left: 0;
+  width: $sidebarWidth;
+  height: $headerHeight;
+  background: $noteGreenBackground;
+  border-top: 1px solid $noteGreenBorder;
+  border-right: 1px solid $noteGreenBorder;
+  @extend %border-box;
+  @extend %user-select-none;
+
+  @media #{$mediumScreen} { width: $sidebarMediumWidth; }
+}
+
+._settings-btn {
+  display: block;
+  height: 100%;
+  line-height: 1.5rem;
+  padding: 0 .75rem;
+  font-size: .875rem;
+  font-weight: $boldFontWeight;
+  color: inherit;
+  text-align: left;
+  cursor: pointer;
+  @extend %border-box;
+
+  > svg {
+    display: inline-block;
+    vertical-align: top;
+    width: 1.5rem;
+    height: 1.5rem;
+    margin-right: .125rem;
+    fill: currentColor;
+    pointer-events: none;
+  }
+}
+
+._save-btn { width: 100%; }
+
+//
+// Header tabs
+//
+
+._settings-tabs {
+  display: none; // mobile only
+  margin-right: .5rem;
+  line-height: $headerHeight;
+}
+
+._settings-tab {
+  position: relative;
+  display: inline-block;
+  vertical-align: top;
+  padding: 0 .75rem;
+  cursor: pointer;
+
+  &, &:hover {
+    color: $textColorLight;
+    text-decoration: none;
+  }
+
+  &.active {
+    color: $textColor;
+    font-weight: $boldFontWeight;
+    text-decoration: none;
+    box-shadow: inset 0 -2px $linkColor;
+  }
+}

+ 5 - 162
assets/stylesheets/components/_sidebar.scss

@@ -8,13 +8,13 @@
   top: $headerHeight;
   bottom: 0;
   left: 0;
-  margin-top: 1px;
   overflow-x: hidden;
   overflow-y: scroll;
   background: $sidebarBackground;
   background-clip: content-box;
   -webkit-overflow-scrolling: touch;
   -ms-overflow-style: none; // IE 10 doesn't support pointer-events
+  @extend %border-box;
   @extend %user-select-none;
 
   &::-webkit-scrollbar { -webkit-appearance: none; width: 10px; }
@@ -48,26 +48,7 @@
   width: 3px;
   cursor: col-resize;
 
-  ._sidebar-hidden & {
-    left: 0;
-    margin-left: 0;
-    background: $headerBackground;
-    border-right: 1px solid $headerBorder;
-    width: 8px;
-    cursor: pointer;
-
-    &:before {
-      content: '';
-      position: absolute;
-      top: 50%;
-      left: 3px;
-      margin-top: -.5rem;
-      width: 1px;
-      height: 1rem;
-      border-left: 1px solid $textColorLighter;
-      border-right: 1px solid $textColorLighter;
-    }
-  }
+  ._sidebar-hidden & { display: none; }
 }
 
 //
@@ -84,10 +65,7 @@
 
   @media #{$mediumScreen} { width: $sidebarMediumWidth; }
 
-  ._sidebar > & {
-    min-height: 100%;
-    padding-bottom: 3.5rem;
-  }
+  ._sidebar > & { min-height: 100%; }
 
   a:focus { outline: 0; }
 }
@@ -342,7 +320,7 @@
 ._list-picker {
   > ._list, > ._list-item {
     opacity: 0;
-    transition: .2s;
+    transition: opacity .2s;
   }
   &._in { > ._list, > ._list-item { opacity: 1; } }
 
@@ -354,14 +332,11 @@
   position: absolute;
   top: .5rem;
   right: .75rem;
-  width: 1rem;
-  height: 1rem;
-  transition: .2s;
 }
 
 ._list-link {
   display: block;
-  margin-top: .75rem;
+  padding: .75rem 0;
   font-size: .8125rem;
   text-align: center;
   @extend %external-link;
@@ -369,135 +344,3 @@
   &:after { visibility: hidden; }
   &:hover:after { visibility: visible; }
 }
-
-//
-// Footer
-//
-
-._sidebar-footer {
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  width: $sidebarWidth;
-  background: $sidebarBackground;
-  box-shadow: inset -1px 0 $sidebarBorder;
-  -webkit-transform: translateZ(0);
-          transform: translateZ(0);
-
-  @media #{$mediumScreen} { width: $sidebarMediumWidth; }
-
-  ._max-width & {
-    left: calc(50% - #{$maxWidth} / 2);
-    @media (max-width: #{$maxWidth}) { left: 0; }
-  }
-
-  &:before {
-    content: '';
-    position: absolute;
-    bottom: 100%;
-    left: 0;
-    right: 1px;
-    height: 1em;
-    background-image: -webkit-linear-gradient(top,       rgba($sidebarBackground, 0), rgba($sidebarBackground, .95));
-    background-image:         linear-gradient(to bottom, rgba($sidebarBackground, 0), rgba($sidebarBackground, .95));
-    pointer-events: none;
-  }
-}
-
-._sidebar-footer-link {
-  position: relative;
-  display: block;
-  overflow: hidden;
-  height: 2.5rem;
-  line-height: 1rem;
-  padding: .75rem .25rem .75rem .75rem;
-  font-size: .875em;
-  cursor: pointer;
-  @extend %border-box;
-
-  &, &:hover {
-    color: inherit;
-    text-decoration: none;
-  }
-
-  &:before {
-    float: left;
-    margin-right: .625rem;
-    @extend %icon;
-  }
-}
-
-._sidebar-footer-edit {
-  display: inline-block;
-
-  @if $style == 'dark' {
-    &:before { @extend %icon-settings-white; }
-  } @else {
-    &:before { @extend %icon-settings; }
-  }
-}
-
-._sidebar-footer-light {
-  float: right;
-  width: 2rem;
-  padding: 0;
-  opacity: .65;
-  @extend %hide-text;
-
-  &:before {
-    float: none;
-    position: absolute;
-    top: .75rem;
-    left: .25rem;
-
-    @if $style == 'dark' {
-      @extend %icon-light-white;
-    } @else {
-      @extend %icon-light;
-    }
-  }
-}
-
-._sidebar-footer-layout {
-  float: right;
-  width: 2rem;
-  padding: 0;
-  opacity: .65;
-  @extend %hide-text;
-
-  &:before {
-    float: none;
-    position: absolute;
-    top: .75rem;
-    left: .375rem;
-    @if $style == 'dark' {
-      @extend %icon-expand-white;
-    } @else {
-      @extend %icon-expand;
-    }
-
-    ._max-width & {
-      @if $style == 'dark' {
-        @extend %icon-contract-white;
-      } @else {
-        @extend %icon-contract;
-      }
-    }
-  }
-
-  @media (max-width: #{$maxWidth + .1rem}) { display: none; }
-}
-
-._sidebar-footer-save {
-  margin-right: 1px;
-  font-weight: $boldFontWeight;
-  background: $noteGreenBackground;
-  box-shadow: inset 0 1px $noteGreenBorder,
-                    1px 0 $noteGreenBorder;
-
-  @if $style == 'dark' {
-    &:before { @extend %icon-check-white; }
-  } @else {
-    &:before { @extend %icon-check; }
-  }
-}

+ 11 - 0
assets/stylesheets/global/_base.scss

@@ -166,6 +166,11 @@ section, main {
   outline: 0;
 }
 
+label {
+  display: block;
+  @extend %user-select-none;
+}
+
 input, button {
   margin: 0;
   font-family: inherit;
@@ -175,6 +180,12 @@ input, button {
   @extend %border-box;
 }
 
+input[type=checkbox] {
+  width: 1rem;
+  height: 1rem;
+  cursor: pointer;
+}
+
 button {
   padding: 0;
   background: none;

+ 8 - 18
assets/stylesheets/global/_icons.scss

@@ -4,7 +4,7 @@
   width: 1rem;
   height: 1rem;
   background-image: image-url('icons.png');
-  background-size: 4rem 6rem;
+  background-size: 4rem 3rem;
 }
 
 @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {
@@ -35,23 +35,13 @@
 %icon-search                { background-position: -1rem 0; }
 %icon-link                  { background-position: -2.25rem -.25rem; }
 %icon-clear                 { background-position: -3rem 0; }
-%icon-settings              { background-position: 0 -1rem; }
-%icon-check                 { background-position: -1rem -1rem; }
-%icon-path                  { background-position: 0 -2rem; }
-%icon-search-white          { background-position: -1rem -2rem; }
-%icon-dir-white             { background-position: -2rem -2rem; }
-%icon-link-white            { background-position: -3.25rem -2.25rem; }
-%icon-settings-white        { background-position: 0 -3rem; }
-%icon-check-white           { background-position: -1rem -3rem; }
-%icon-light                 { background-position: -2rem -3rem; }
-%icon-light-white           { background-position: -3rem -3rem; }
-%icon-expand                { background-position: 0 -4rem; }
-%icon-contract              { background-position: -1rem -4rem; }
-%icon-expand-white          { background-position: -2rem -4rem; }
-%icon-contract-white        { background-position: -3rem -4rem; }
-%icon-clipboard             { background-position: 0 -5rem; }
-%icon-clipboard-white       { background-position: -1rem -5rem; }
-%icon-close-white           { background-position: -2rem -5rem; }
+%icon-path                  { background-position: 0 -1rem; }
+%icon-search-white          { background-position: -1rem -1rem; }
+%icon-dir-white             { background-position: -2rem -1rem; }
+%icon-link-white            { background-position: -3.25rem -1.25rem; }
+%icon-clipboard             { background-position: 0 -2rem; }
+%icon-clipboard-white       { background-position: -1rem -2rem; }
+%icon-close-white           { background-position: -2rem -2rem; }
 
 ._icon-codeceptjs:before    { background-position: -3rem 0; }
 ._icon-codeception:before   { background-position: -4rem 0; }

+ 1 - 1
assets/stylesheets/global/_variables-dark.scss

@@ -9,7 +9,6 @@ $maxWidth: 80rem;
 $headerHeight: 3rem;
 $sidebarWidth: 20rem;
 $sidebarMediumWidth: 16rem;
-$sidebarHiddenWidth: 9px;
 
 $contentBackground: #33373a;
 $documentBackground: $contentBackground;
@@ -17,6 +16,7 @@ $documentBackground: $contentBackground;
 $textColor: #cbd0d0;
 $textColorLight: #9da5ad;
 $textColorLighter: #77787a;
+$textColorRed: #f44336;
 
 $inputFocusBorder: false;
 

+ 1 - 1
assets/stylesheets/global/_variables.scss

@@ -9,7 +9,6 @@ $maxWidth: 80rem;
 $headerHeight: 3rem;
 $sidebarWidth: 20rem;
 $sidebarMediumWidth: 16rem;
-$sidebarHiddenWidth: 9px;
 
 $contentBackground: #fff;
 $documentBackground: #fafafa;
@@ -17,6 +16,7 @@ $documentBackground: #fafafa;
 $textColor: #333;
 $textColorLight: #666;
 $textColorLighter: #888;
+$textColorRed: #f44336;
 
 $inputFocusBorder: #35b5f4;
 

+ 1 - 1
lib/app.rb

@@ -235,7 +235,7 @@ class App < Sinatra::Application
     erb :index
   end
 
-  %w(offline about news help).each do |page|
+  %w(settings offline about news help).each do |page|
     get "/#{page}" do
       if supports_js_redirection?
         redirect_via_js "/#{page}"

+ 0 - 1
public/icons/ui/check-white/SOURCE

@@ -1 +0,0 @@
-http://happytodesign.com/hicons/

BIN
public/icons/ui/check-white/check-white.png


BIN
public/icons/ui/check-white/check-white@2x.png


+ 0 - 1
public/icons/ui/check/SOURCE

@@ -1 +0,0 @@
-http://happytodesign.com/hicons/

BIN
public/icons/ui/check/check.png


BIN
public/icons/ui/check/check@2x.png


BIN
public/icons/ui/contract/16.png


BIN
public/icons/ui/contract/16@2x.png


BIN
public/icons/ui/expand/16.png


BIN
public/icons/ui/expand/16@2x.png


+ 0 - 1
public/icons/ui/light-white/SOURCE

@@ -1 +0,0 @@
-http://www.entypo.com/

BIN
public/icons/ui/light-white/light.png


BIN
public/icons/ui/light-white/light@2x.png


+ 0 - 1
public/icons/ui/light/SOURCE

@@ -1 +0,0 @@
-http://www.entypo.com/

BIN
public/icons/ui/light/light.png


BIN
public/icons/ui/light/light@2x.png


+ 0 - 1
public/icons/ui/settings-white/SOURCE

@@ -1 +0,0 @@
-http://gemicon.net/

BIN
public/icons/ui/settings-white/settings-white.png


BIN
public/icons/ui/settings-white/settings-white@2x.png


+ 0 - 1
public/icons/ui/settings/SOURCE

@@ -1 +0,0 @@
-http://gemicon.net/

BIN
public/icons/ui/settings/settings.png


BIN
public/icons/ui/settings/settings@2x.png


+ 1 - 1
public/robots.txt

@@ -1,2 +1,2 @@
 User-agent: *
-Allow: /
+Disallow: /settings

+ 24 - 6
views/app.erb

@@ -1,7 +1,7 @@
 <div class="_app<%= " #{app_layout}" if app_layout %>" role="application">
   <header class="_header" role="banner">
     <button type="button" aria-label="Toggle navigation" class="_header-btn" data-toggle-sidebar hidden>
-      <svg viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>
+      <svg viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" /></svg>
     </button>
     <form class="_search" role="search">
       <input type="search" name="q" class="_search-input" placeholder="Search&hellip;" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" maxlength="30" aria-label="Search">
@@ -9,17 +9,18 @@
       <div class="_search-tag"></div>
     </form>
     <button type="button" aria-label="Back" class="_header-btn" data-back hidden>
-      <svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></svg>
+      <svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" /></svg>
     </button>
     <button type="button" aria-label="Forward" class="_header-btn _forward-btn" data-forward hidden>
-      <svg viewBox="0 0 24 24"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"></svg>
+      <svg viewBox="0 0 24 24"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" /></svg>
     </button>
     <button type="button" aria-label="Toggle menu" title="Toggle menu" class="_header-btn _menu-btn" data-toggle-menu>
-      <svg viewBox="0 0 24 24"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></svg>
+      <svg viewBox="0 0 24 24"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" /></svg>
     </button>
     <nav class="_menu" role="navigation">
       <h1 class="_menu-title"><a href="/" class="_menu-title-link">DevDocs</a></h1>
-      <a href="/offline" class="_menu-link">Offline</a>
+      <a href="/settings" class="_menu-link">Preferences</a>
+      <a href="/offline" class="_menu-link">Offline Data</a>
       <a href="/news" class="_menu-link">Changelog</a>
       <a href="/help" class="_menu-link">Help</a>
       <a href="/about" class="_menu-link">About</a>
@@ -37,10 +38,27 @@
   <div class="_container" role="document">
     <main class="_content _content-loading" role="main"></main>
   </div>
+  <form class="_settings" id="settings">
+    <div class="_header">
+      <button type="button" aria-label="Back" class="_settings-btn" data-back>
+        <svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" /></svg> Back
+      </button>
+      <nav class="_settings-tabs">
+        <a class="_settings-tab active" data-tab="doc-picker" hidden>Docs</a>
+        <a class="_settings-tab" data-tab="settings" hidden>Settings</a>
+      </nav>
+    </div>
+    <div class="_sidebar"></div>
+    <div class="_footer">
+      <button type="submit" class="_settings-btn _save-btn">
+        <svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" /></svg> Save
+      </button>
+    </div>
+  </form>
 </div>
 <style data-size="<%= app_size %>" data-resizer>
   ._container { margin-left: <%= app_size %>; }
-  ._header, ._list, ._sidebar-footer { width: <%= app_size %>; }
+  ._header, ._list, ._footer { width: <%= app_size %>; }
   ._list-hover.clone { min-width: <%= app_size %>; }
   ._notice, ._path, ._resizer { left: <%= app_size %>; }
 </style>