Add settings page

This commit is contained in:
Thibaut Courouble 2017-02-26 09:58:41 -05:00
parent ce8079fff3
commit e94af979d9
59 changed files with 495 additions and 393 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -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: =>

View file

@ -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

View file

@ -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, '')

View file

@ -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"

View file

@ -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>. """

View file

@ -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>"

View file

@ -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>
"""

View file

@ -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>

View file

@ -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>
"""

View file

@ -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>"""

View file

@ -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

View file

@ -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

View file

@ -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 ->

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

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

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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 {

View file

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

View file

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

View file

@ -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;
}
}

View file

@ -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; }
}
}

View file

@ -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;

View file

@ -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; }

View file

@ -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;

View file

@ -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;

View file

@ -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}"

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 B

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

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

View file

@ -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>