mirror of
				https://github.com/Foundry376/Mailspring.git
				synced 2025-11-01 00:46:25 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			134 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			CoffeeScript
		
	
	
	
	
	
			
		
		
	
	
			134 lines
		
	
	
	
		
			5.3 KiB
		
	
	
	
		
			CoffeeScript
		
	
	
	
	
	
| {shell} = require 'electron'
 | |
| GithubStore = require './github-store'
 | |
| {React} = require 'nylas-exports'
 | |
| {RetinaImg, KeyCommandsRegion} = require 'nylas-component-kit'
 | |
| 
 | |
| ###
 | |
| The `ViewOnGithubButton` displays a button whenever there's a relevant
 | |
| Github asset to link to.
 | |
| 
 | |
| When creating this React component the first consideration was when &
 | |
| where we'd be rendered. The next consideration was what data we need to
 | |
| display.
 | |
| 
 | |
| Unlike a traditional React application, N1 components have very few
 | |
| guarantees on who will render them and where they will be rendered. In our
 | |
| `lib/main.cjsx` file we registered this component with our
 | |
| {ComponentRegistry} for the `"message:Toolbar"` role. That means that
 | |
| whenever the "message:Toolbar" region gets rendered, we'll render
 | |
| everything registered with that area. Other buttons, such as "Archive" and
 | |
| the "Change Label" button are reigstered with that role, so we should
 | |
| expect ourselves to showup alongside them.
 | |
| 
 | |
| The only data we need is a single relevant to Github. If we have one,
 | |
| we'll open it up in a browser. If we don't have one, we'll hide the
 | |
| component.
 | |
| 
 | |
| Getting that url takes a bit of message parsing. We need to retrieve a
 | |
| message body then implement some kind of regex to find and parse out that
 | |
| link.
 | |
| 
 | |
| We could have put all of that logic in this React Component, but that's
 | |
| not what React components should be doing. In N1 a component's only job is
 | |
| to display known data and be the first responders to user interaction.
 | |
| 
 | |
| We instead create a {GithubStore} to handle the fetching and preparation
 | |
| of the data. See that file's documentation for more on how that works.
 | |
| 
 | |
| As far as this component is concerned, there will be an entity called
 | |
| `GitHubStore` that will expose the correct `link`. That store will then
 | |
| notify us when the `link` changes so we can update our state.
 | |
| 
 | |
| Once we know our `link` our `render` method can simply be a description of
 | |
| how we want to display that link. In this case we're going to make a
 | |
| simple button with a GitHub logo in it.
 | |
| 
 | |
| We'll also display nothing if there is no link.
 | |
| ###
 | |
| class ViewOnGithubButton extends React.Component
 | |
|   @displayName: "ViewOnGithubButton"
 | |
|   @containerRequired: false
 | |
| 
 | |
| 
 | |
|   #### React methods ####
 | |
|   # The following methods are React methods that we override. See {React}
 | |
|   # documentation for more info
 | |
| 
 | |
|   constructor: (@props) ->
 | |
|     @state = @_getStateFromStores()
 | |
| 
 | |
|   # When components mount, it's very common to have them listen to a
 | |
|   # `Store`. Since most of our React Components in N1 are registered into
 | |
|   # {ComponentRegistry} regions instead of manually rendered top-down much
 | |
|   # of our data is side-loaded from stores instead of passed in as props.
 | |
|   componentDidMount: ->
 | |
|     # The `listen` method of {NylasStore}s (which {GithubStore}
 | |
|     # subclasses) returns an "unlistener" function. When the unlistener is
 | |
|     # invoked (as it is in `componentWillUnmount`) the listener references
 | |
|     # are cleaned up. Every time the `GithubStore` calls its `trigger`
 | |
|     # method, the `_onStoreChanged` callback will be fired.
 | |
|     @_unlisten = GithubStore.listen(@_onStoreChanged)
 | |
| 
 | |
|   componentWillUnmount: ->
 | |
|     @_unlisten?()
 | |
| 
 | |
|   _keymapHandlers: ->
 | |
|     'github:open': @_openLink
 | |
| 
 | |
|   render: ->
 | |
|     return null unless @state.link
 | |
|     <KeyCommandsRegion globalHandlers={@_keymapHandlers()}>
 | |
|       <button className="btn btn-toolbar"
 | |
|               onClick={@_openLink}
 | |
|               title={"Visit Thread on GitHub"}>
 | |
|         <RetinaImg
 | |
|           mode={RetinaImg.Mode.ContentIsMask}
 | |
|           url="nylas://message-view-on-github/assets/github@2x.png" />
 | |
|       </button>
 | |
|     </KeyCommandsRegion>
 | |
| 
 | |
| 
 | |
|   #### Super common N1 Component private methods ####
 | |
| 
 | |
|   # An extremely common pattern for all N1 components are the methods
 | |
|   # `onStoreChanged` and `getStateFromStores`.
 | |
|   #
 | |
|   # Most N1 components listen to some source of data, which is usally a
 | |
|   # Store. When the store notifies that something has changed, we need to
 | |
|   # fetch the fresh data and updated our state.
 | |
|   #
 | |
|   # Note that when a Store updates it does not let us know what changed.
 | |
|   # This is intentional! This forces us to fresh the full latest state
 | |
|   # from the stores in a more declarative, easy-to-follow way. There are a
 | |
|   # couple rare exceptions that are only used for performance
 | |
|   # optimizations.
 | |
| 
 | |
|   # Note that we bind this method to the class instance's `this`. Any
 | |
|   # method used as a callback must be bound. In Coffeescript we use the
 | |
|   # fat arrow (`=>`)
 | |
|   _onStoreChanged: =>
 | |
|     @setState(@_getStateFromStores())
 | |
| 
 | |
|   # getStateFromStores fetches the data the view needs from the
 | |
|   # appropriate data source (our GithubStore). We return a basic object
 | |
|   # that can be passed directly into `setState`.
 | |
|   _getStateFromStores: ->
 | |
|     return {link: GithubStore.link()}
 | |
| 
 | |
| 
 | |
|   #### Other utility "private" methods ####
 | |
| 
 | |
|   # This responds to user interaction. Since it's a callback we have to
 | |
|   # bind it to the instances's `this` (Coffeescript fat arrow `=>`)
 | |
|   #
 | |
|   # In the case of this component we use the Electron `shell` module to
 | |
|   # request the computer to open the default browser.
 | |
|   #
 | |
|   # In other very common cases, user interaction handlers may fire an
 | |
|   # `Action` across the system for other Stores to respond to. They may
 | |
|   # also queue a {Task} to eventually perform a mutating API POST or PUT
 | |
|   # request.
 | |
|   _openLink: =>
 | |
|     shell.openExternal(@state.link) if @state.link
 | |
| 
 | |
| module.exports = ViewOnGithubButton
 |