diff --git a/build/tasks/docs-task.coffee b/build/tasks/docs-task.coffee index 22b66d820..2e3bf1262 100644 --- a/build/tasks/docs-task.coffee +++ b/build/tasks/docs-task.coffee @@ -199,8 +199,6 @@ module.exports = (grunt) -> sidebarPath = path.join(docsOutputDir, '_sidebar.json') grunt.file.write(sidebarPath, sidebarJson) - - # Prepare to render by loading handlebars partials templatesPath = path.resolve(__dirname, '..', '..', 'docs-templates') @@ -214,40 +212,6 @@ module.exports = (grunt) -> for classname, val of apiJSON.classes knownClassnames[classname.toLowerCase()] = val - expandTypeReferences = (val) -> - refRegex = /{([\w.]*)}/g - while (match = refRegex.exec(val)) isnt null - term = match[1].toLowerCase() - label = match[1] - url = false - if term in standardClasses - url = standardClassURLRoot+term - else if thirdPartyClasses[term] - url = thirdPartyClasses[term] - else if knownClassnames[term] - url = relativePathForClass(term) - else - console.warn("Cannot find class named #{term}") - - if url - val = val.replace(match[0], "#{label}") - val - - expandFuncReferences = (val) -> - refRegex = /{([\w]*)?::([\w]*)}/g - while (match = refRegex.exec(val)) isnt null - [text, a, b] = match - url = false - if a and b - url = "#{relativePathForClass(a)}##{b}" - label = "#{a}::#{b}" - else - url = "##{b}" - label = "#{b}" - if url - val = val.replace(text, "#{label}") - val - # Render Class Pages classTemplatePath = path.join(templatesPath, 'class.html') @@ -256,8 +220,9 @@ module.exports = (grunt) -> for {name, documentation, section} in classes # Recursively process `description` and `type` fields to process markdown, # expand references to types, functions and other files. - processFields(documentation, ['description'], [marked.noMeta, expandTypeReferences, expandFuncReferences]) - processFields(documentation, ['type'], [expandTypeReferences]) + processFields(documentation, ['description'], [marked.noMeta]) + processFields(documentation, ['type'], []) result = classTemplate({name, documentation, section}) + console.log(outputPathFor(relativePathForClass(name))) grunt.file.write(outputPathFor(relativePathForClass(name)), result) diff --git a/exports/nylas-component-kit.coffee b/exports/nylas-component-kit.coffee index e1ab9fa63..4b36de1a9 100644 --- a/exports/nylas-component-kit.coffee +++ b/exports/nylas-component-kit.coffee @@ -7,6 +7,7 @@ module.exports = # Models Menu: require '../src/components/menu' + DropZone: require '../src/components/drop-zone' Spinner: require '../src/components/spinner' Popover: require '../src/components/popover' Flexbox: require '../src/components/flexbox' diff --git a/exports/nylas-exports.coffee b/exports/nylas-exports.coffee index 663dca160..981281d4a 100644 --- a/exports/nylas-exports.coffee +++ b/exports/nylas-exports.coffee @@ -45,6 +45,7 @@ Exports = # Utils Utils: Utils DOMUtils: require '../src/dom-utils' + CanvasUtils: require '../src/canvas-utils' RegExpUtils: require '../src/regexp-utils' MessageUtils: require '../src/flux/models/message-utils' diff --git a/internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx b/internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx index 142fb2f97..bd2b78fad 100644 --- a/internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx +++ b/internal_packages/account-sidebar/lib/account-sidebar-category-item.cjsx @@ -1,7 +1,15 @@ React = require 'react' classNames = require 'classnames' -{Actions, Utils, UnreadCountStore, WorkspaceStore} = require 'nylas-exports' -{RetinaImg} = require 'nylas-component-kit' +{Actions, + Utils, + UnreadCountStore, + WorkspaceStore, + NamespaceStore, + FocusedCategoryStore, + ChangeLabelsTask, + ChangeFolderTask, + CategoryStore} = require 'nylas-exports' +{RetinaImg, DropZone} = require 'nylas-component-kit' class AccountSidebarCategoryItem extends React.Component @displayName: 'AccountSidebarCategoryItem' @@ -19,9 +27,8 @@ class AccountSidebarCategoryItem extends React.Component _onUnreadCountChange: => @setState unreadCount: UnreadCountStore.count() - shouldComponentUpdate: (nextProps) => - @props?.item.name isnt nextProps.item.name or - @props?.select isnt nextProps.select + shouldComponentUpdate: (nextProps, nextState) => + !Utils.isEqualReact(@props, nextProps) or !Utils.isEqualReact(@state, nextState) render: => unread = [] @@ -31,12 +38,47 @@ class AccountSidebarCategoryItem extends React.Component containerClass = classNames 'item': true 'selected': @props.select + 'dropping': @state.isDropping -
+ @setState({isDropping}) } + onDrop={@_onDrop}> {unread} {@props.item.displayName} -
+ + + _shouldAcceptDrop: (e) => + return false if @props.item.name in CategoryStore.LockedCategoryNames + return false if @props.item.name is FocusedCategoryStore.categoryName() + 'nylas-thread-ids' in e.dataTransfer.types + + _onDrop: (e) => + jsonString = e.dataTransfer.getData('nylas-thread-ids') + try + ids = JSON.parse(jsonString) + catch err + console.error("AccountSidebarCategoryItem onDrop: JSON parse #{err}") + return unless ids + + if NamespaceStore.current().usesLabels() + currentLabel = FocusedCategoryStore.category() + if currentLabel and not (currentLabel in CategoryStore.LockedCategoryNames) + labelsToRemove = [currentLabel] + + task = new ChangeLabelsTask + threadIds: ids, + labelsToAdd: [@props.item], + labelsToRemove: labelsToRemove + else + task = new ChangeFolderTask + folderOrId: @props.item, + threadIds: ids + + Actions.queueTask(task) _onClick: (event) => event.preventDefault() diff --git a/internal_packages/account-sidebar/stylesheets/account-sidebar.less b/internal_packages/account-sidebar/stylesheets/account-sidebar.less index fc3fac102..1eabdb202 100644 --- a/internal_packages/account-sidebar/stylesheets/account-sidebar.less +++ b/internal_packages/account-sidebar/stylesheets/account-sidebar.less @@ -49,6 +49,11 @@ color: @source-list-active-color; img.content-mask { background-color: @source-list-active-color; } } + &.dropping { + background-color: lighten(@source-list-bg, 20%); + color: @source-list-active-color; + img.content-mask { background-color: @source-list-active-color; } + } &:hover { cursor: default; diff --git a/internal_packages/composer/lib/composer-view.cjsx b/internal_packages/composer/lib/composer-view.cjsx index af0af53d3..9a315ba26 100644 --- a/internal_packages/composer/lib/composer-view.cjsx +++ b/internal_packages/composer/lib/composer-view.cjsx @@ -14,6 +14,7 @@ _ = require 'underscore' InjectedComponentSet, InjectedComponent, ScrollRegion, + DropZone, RetinaImg} = require 'nylas-component-kit' FileUpload = require './file-upload' @@ -159,7 +160,10 @@ class ComposerView extends React.Component "composer-outer-wrap #{@props.className ? ""}" _renderComposer: => -
+ @setState({isDropping}) } + onDrop={@_onDrop}>
@@ -207,7 +211,7 @@ class ComposerView extends React.Component
{@_renderActionsRegion()}
-
+ _renderFields: => # Note: We need to physically add and remove these elements, not just hide them. @@ -470,7 +474,7 @@ class ComposerView extends React.Component else if @isForwardedMessage() then return true else return false - _shouldAcceptDrop: (event) -> + _shouldAcceptDrop: (event) => # Ensure that you can't pick up a file and drop it on the same draft existingFilePaths = @state.files.map (f) -> FileUploadStore.linkedUpload(f)?.filePath @@ -484,7 +488,7 @@ class ComposerView extends React.Component return hasNativeFile or hasNonNativeFilePath - _nonNativeFilePathForDrop: (event) -> + _nonNativeFilePathForDrop: (event) => if "text/nylas-file-url" in event.dataTransfer.types downloadURL = event.dataTransfer.getData("text/nylas-file-url") downloadFilePath = downloadURL.split('file://')[1] @@ -500,30 +504,7 @@ class ComposerView extends React.Component return null - # We maintain a "dragCounter" because dragEnter and dragLeave events *stack* - # when the user moves the item in and out of DOM elements inside of our container. - # It's really awful and everyone hates it. - # - # Alternative solution *maybe* is to set pointer-events:none; during drag. - - _onDragEnter: (e) => - return unless @_shouldAcceptDrop(e) - @_dragCounter ?= 0 - @_dragCounter += 1 - if @_dragCounter is 1 - @setState(isDropping: true) - - _onDragLeave: (e) => - return unless @_shouldAcceptDrop(e) - @_dragCounter -= 1 - if @_dragCounter is 0 - @setState(isDropping: false) - _onDrop: (e) => - return unless @_shouldAcceptDrop(e) - @setState(isDropping: false) - @_dragCounter = 0 - # Accept drops of real files from other applications for file in e.dataTransfer.files Actions.attachFilePath({path: file.path, messageLocalId: @props.localId}) @@ -532,8 +513,6 @@ class ComposerView extends React.Component if (uri = @_nonNativeFilePathForDrop(e)) Actions.attachFilePath({path: uri, messageLocalId: @props.localId}) - return - _onFilePaste: (path) => Actions.attachFilePath({path: path, messageLocalId: @props.localId}) diff --git a/internal_packages/thread-list/lib/thread-list.cjsx b/internal_packages/thread-list/lib/thread-list.cjsx index d4df0a131..04acf8c3e 100644 --- a/internal_packages/thread-list/lib/thread-list.cjsx +++ b/internal_packages/thread-list/lib/thread-list.cjsx @@ -5,6 +5,7 @@ classNames = require 'classnames' {timestamp, subject} = require './formatting-utils' {Actions, Utils, + CanvasUtils, Thread, WorkspaceStore, NamespaceStore, @@ -152,6 +153,7 @@ class ThreadList extends React.Component @itemPropsProvider = (item) -> className: classNames 'unread': item.unread + 'data-thread-id': item.id componentDidMount: => window.addEventListener('resize', @_onResize, true) @@ -171,6 +173,9 @@ class ThreadList extends React.Component className="thread-list" scrollTooltipComponent={ThreadListScrollTooltip} emptyComponent={EmptyState} + onDragStart={@_onDragStart} + onDragEnd={@_onDragEnd} + draggable="true" collection="thread" /> else if @state.style is 'narrow'
+ _threadIdAtPoint: (x, y) -> + item = document.elementFromPoint(event.clientX, event.clientY).closest('.list-item') + return null unless item + return item.dataset.threadId + + _onDragStart: (event) => + itemThreadId = @_threadIdAtPoint(event.clientX, event.clientY) + unless itemThreadId + event.preventDefault() + return + + if itemThreadId in ThreadListStore.view().selection.ids() + dragThreadIds = ThreadListStore.view().selection.ids() + else + dragThreadIds = [itemThreadId] + + event.dataTransfer.effectAllowed = "move" + event.dataTransfer.dragEffect = "move" + + canvas = CanvasUtils.canvasWithThreadDragImage(dragThreadIds.length) + event.dataTransfer.setDragImage(canvas, 10, 10) + event.dataTransfer.setData('nylas-thread-ids', JSON.stringify(dragThreadIds)) + return + + _onDragEnd: (event) => + _onResize: (event) => current = @state.style desired = if React.findDOMNode(@).offsetWidth < 540 then 'narrow' else 'wide' diff --git a/src/canvas-utils.coffee b/src/canvas-utils.coffee new file mode 100644 index 000000000..b90baf0f3 --- /dev/null +++ b/src/canvas-utils.coffee @@ -0,0 +1,73 @@ +ThreadDragImage = document.createElement("img") +ThreadDragImage.src = """data:image/png;base64,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""" + +DragCanvas = document.createElement("canvas") +document.body.appendChild(DragCanvas) + +CanvasUtils = + roundRect: (ctx, x, y, width, height, radius = 5, fill, stroke = true) -> + ctx.beginPath() + ctx.moveTo(x + radius, y) + ctx.lineTo(x + width - radius, y) + ctx.quadraticCurveTo(x + width, y, x + width, y + radius) + ctx.lineTo(x + width, y + height - radius) + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) + ctx.lineTo(x + radius, y + height) + ctx.quadraticCurveTo(x, y + height, x, y + height - radius) + ctx.lineTo(x, y + radius) + ctx.quadraticCurveTo(x, y, x + radius, y) + ctx.closePath() + ctx.stroke() if stroke + ctx.fill() if fill + + canvasWithThreadDragImage: (count) -> + canvas = DragCanvas + + # Make sure the canvas has a 2x pixel density on retina displays + scale = window.devicePixelRatio + canvas.width = 58 * scale + canvas.height = 55 * scale + canvas.style.position = "absolute" + canvas.style.width = "58px" + canvas.style.height = "55px" + + # necessary for setDragImage to work + ctx = canvas.getContext('2d') + + # mail background image + if count > 1 + ctx.rotate(-20*Math.PI/180) + ctx.drawImage(ThreadDragImage, -10*scale, 2*scale, 48*scale, 48*scale) + ctx.rotate(20*Math.PI/180) + ctx.drawImage(ThreadDragImage, 0, 0, 48*scale, 48*scale) + + # count bubble + dotGradient = ctx.createLinearGradient(0, 0, 0, 15 * scale) + dotGradient.addColorStop(0, "rgb(116, 124, 143)") + dotGradient.addColorStop(1, "rgb(67, 77, 104)") + ctx.strokeStyle = "rgba(39, 48, 68, 0.6)" + ctx.lineWidth = 1 + ctx.fillStyle = dotGradient + + textX = 49 + text = "#{count}" + + if (count < 10) + CanvasUtils.roundRect(ctx, 41 * scale, 1 * scale, 16 * scale, 14 * scale, 7 * scale, true, true) + else if (count < 100) + CanvasUtils.roundRect(ctx, 37 * scale, 1 * scale, 20 * scale, 14 * scale, 7 * scale, true, true) + textX = 46 + else + CanvasUtils.roundRect(ctx, 33 * scale, 1 * scale, 25 * scale, 14 * scale, 7 * scale, true, true) + text = "99+" + textX = 46 + + # count text + ctx.fillStyle = "rgba(255,255,255,0.9)" + ctx.font = "#{11 * scale}px sans-serif" + ctx.textAlign = "center" + ctx.fillText(text, textX * scale, 12 * scale, 30 * scale) + + return DragCanvas + +module.exports = CanvasUtils diff --git a/src/components/draggable-img.cjsx b/src/components/draggable-img.cjsx index a30a1925d..1c11fe5af 100644 --- a/src/components/draggable-img.cjsx +++ b/src/components/draggable-img.cjsx @@ -1,9 +1,10 @@ React = require 'react' ### -# Images are supposed to by default show a ghost image when dragging and -# dropping. Unfortunatley this does not work in Electron. Since we're a -# desktop app we don't want all images draggable, but we do want some (like attachments) to be able to be dragged away with a preview image. +Public: Images are supposed to by default show a ghost image when dragging and +dropping. Unfortunately this does not work in Electron. Since we're a +desktop app we don't want all images draggable, but we do want some (like +attachments) to be able to be dragged away with a preview image. ### class DraggableImg extends React.Component @displayName: 'DraggableImg' diff --git a/src/components/drop-zone.cjsx b/src/components/drop-zone.cjsx new file mode 100644 index 000000000..ef335a896 --- /dev/null +++ b/src/components/drop-zone.cjsx @@ -0,0 +1,47 @@ +React = require 'react' +_ = require 'underscore' + +class DropZone extends React.Component + @propTypes: + shouldAcceptDrop: React.PropTypes.func.isRequired + onDrop: React.PropTypes.func.isRequired + onDragStateChange: React.PropTypes.func + + constructor: -> + + render: -> + otherProps = _.omit(@props, _.keys(@constructor.propTypes)) +
+ {@props.children} +
+ + # We maintain a "dragCounter" because dragEnter and dragLeave events *stack* + # when the user moves the item in and out of DOM elements inside of our container. + # It's really awful and everyone hates it. + # + # Alternative solution *maybe* is to set pointer-events:none; during drag. + + _onDragEnter: (e) => + return unless @props.shouldAcceptDrop(e) + @_dragCounter ?= 0 + @_dragCounter += 1 + if @_dragCounter is 1 and @props.onDragStateChange + @props.onDragStateChange(isDropping: true) + return + + _onDragLeave: (e) => + return unless @props.shouldAcceptDrop(e) + @_dragCounter -= 1 + if @_dragCounter is 0 and @props.onDragStateChange + @props.onDragStateChange(isDropping: false) + return + + _onDrop: (e) => + return unless @props.shouldAcceptDrop(e) + if @props.onDragStateChange + @props.onDragStateChange(isDropping: false) + @_dragCounter = 0 + @props.onDrop(e) + return + +module.exports = DropZone diff --git a/src/components/multiselect-list.cjsx b/src/components/multiselect-list.cjsx index 4ecf7cb52..dadbae829 100644 --- a/src/components/multiselect-list.cjsx +++ b/src/components/multiselect-list.cjsx @@ -98,6 +98,8 @@ class MultiselectList extends React.Component className = @props.className className += " ready" if @state.ready + otherProps = _.omit(@props, _.keys(@constructor.propTypes)) + if @state.dataView and @state.handler className += " " + @state.handler.cssClass() @@ -116,7 +118,7 @@ class MultiselectList extends React.Component visible={@state.ready && @state.dataView.count() is 0} dataView={@state.dataView} /> -
+
else -
+
diff --git a/src/flux/stores/category-store.coffee b/src/flux/stores/category-store.coffee index 3e3725e71..8899a8a62 100644 --- a/src/flux/stores/category-store.coffee +++ b/src/flux/stores/category-store.coffee @@ -27,6 +27,10 @@ class CategoryStore extends NylasStore "trash" ] + LockedCategoryNames: [ + "sent" + ] + AllMailName: "all" byId: (id) -> @_categoryCache[id] diff --git a/src/sheet-toolbar.cjsx b/src/sheet-toolbar.cjsx index 3e485be98..35aa70575 100644 --- a/src/sheet-toolbar.cjsx +++ b/src/sheet-toolbar.cjsx @@ -88,6 +88,7 @@ class Toolbar extends React.Component @state = @_getStateFromStores() componentDidMount: => + @mounted = true @unlisteners = [] @unlisteners.push WorkspaceStore.listen (event) => @setState(@_getStateFromStores()) @@ -97,6 +98,7 @@ class Toolbar extends React.Component window.requestAnimationFrame => @recomputeLayout() componentWillUnmount: => + @mounted = false window.removeEventListener("resize", @_onWindowResize) unlistener() for unlistener in @unlisteners @@ -105,7 +107,6 @@ class Toolbar extends React.Component componentDidUpdate: => # Wait for other components that are dirty (the actual columns in the sheet) - # to update as well. window.requestAnimationFrame => @recomputeLayout() shouldComponentUpdate: (nextProps, nextState) => @@ -148,6 +149,9 @@ class Toolbar extends React.Component recomputeLayout: => + # Yes this really happens - do not remove! + return unless @mounted + # Find our item containers that are tied to specific columns columnToolbarEls = React.findDOMNode(@).querySelectorAll('[data-column]') diff --git a/static/components/extra.less b/static/components/extra.less index 0f6d167c3..9e863cfac 100644 --- a/static/components/extra.less +++ b/static/components/extra.less @@ -44,6 +44,7 @@ display: inline-block; cursor:default; line-height: 22px; + -webkit-user-select: none; } .mail-label.removable { padding-left:12px;