├── .gitignore ├── LICENSE.md ├── README.md ├── keymaps └── gist.cson ├── lib ├── gist-it.coffee ├── gist-model.coffee └── gist-view.coffee ├── media └── screencast.gif ├── menus └── gist.cson ├── package.json ├── spec ├── gist-spec.coffee └── gist-view-spec.coffee └── styles └── gist.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Gist It 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gist It 2 | 3 | Quickly and easily post files from Atom to GitHub Gists. 4 | 5 | ![](https://raw.github.com/rpowelll/gist-it/master/media/screencast.gif) 6 | 7 | ## Commands 8 | 9 | - **Gist Current File (⌥⌘G)** Gists the contents of the current file in the editor 10 | 11 | - **Gist Selection (⇧⌥⌘G)** Gists the contents of the current selection. If more 12 | than one selection is active, uses the most recent one. 13 | 14 | - **Gist Open Buffers** Creates a gist with the content of all buffers in the active 15 | workspace 16 | 17 | ## Using Your GitHub Account 18 | 19 | Gist It will post gists anonymously by default, to post to your own GitHub 20 | account [generate a new token](https://github.com/settings/tokens/new) with the 21 | `gists` scope and copy it into the package's preferences (see the section below 22 | if you have trouble finding these). 23 | 24 | If you keep your Atom configuration in a public repo, or otherwise don't want 25 | your token to reside in the main user config, you can instead put it in 26 | `~/.atom/gist-it.token`. You can then add this file to a `.gitignore` file to 27 | keep it out of public repos. 28 | 29 | ## Preferences 30 | 31 | To configure your preferences for Gist It, open the Atom preferences with 32 | ⌘, and select 'Gist It' from the sidebar. From there 33 | you can modify a variety of settings: 34 | 35 | - **New Gists Default To Private (Boolean)** By default, new gists will be 36 | _public_. you can change this option manually when creating the gist, or 37 | set it to instead default to _private_ by enabling this option. 38 | 39 | - **User Token (String)** This field allows the user to enter a custom generated 40 | OAuth token to have Gists attributed to their GitHub account. A token can be 41 | created [here](https://github.com/settings/tokens/new) and must include the 42 | `gist` scope. 43 | 44 | - **GitHub Enterprise Host (String)** Configure the hostname of a GitHub enterprise 45 | instance where Gists should be created. 46 | 47 | - **Use Http (Boolean)** By default, all requests are made to the GitHub API over HTTPS. 48 | Setting this option allows communicating with an enterprise instance that is only 49 | served over HTTP. 50 | 51 | - **Open new Gist after create** If this option is set, new Gists will be opened 52 | in the default web browser immediately after they have been created. 53 | -------------------------------------------------------------------------------- /keymaps/gist.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | '.platform-darwin atom-text-editor:not(.mini)': 11 | 'alt-cmd-g': 'gist-it:gist-current-file' 12 | 'shift-alt-cmd-g': 'gist-it:gist-selection' 13 | 14 | '.platform-win32 atom-text-editor:not(.mini), .platform-linux atom-text-editor:not(.mini)': 15 | 'alt-ctrl-g': 'gist-it:gist-current-file' 16 | 'shift-alt-ctrl-g': 'gist-it:gist-selection' 17 | -------------------------------------------------------------------------------- /lib/gist-it.coffee: -------------------------------------------------------------------------------- 1 | GistView = require './gist-view' 2 | 3 | module.exports = 4 | gistView: null 5 | 6 | activate: (state) -> 7 | @gistView = new GistView(state.gistViewState) 8 | 9 | deactivate: -> 10 | @gistView.destroy() 11 | 12 | serialize: -> 13 | gistViewState: @gistView.serialize() 14 | -------------------------------------------------------------------------------- /lib/gist-model.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | # Enterprise hosts may be served on a protocol other than HTTPS 3 | if atom.config.get('gist-it.gitHubEnterpriseHost') and atom.config.get('gist-it.useHttp') 4 | protocol = require 'http' 5 | else 6 | protocol = require 'https' 7 | path = require 'path' 8 | 9 | module.exports = 10 | class Gist 11 | constructor: -> 12 | @isPublic = !atom.config.get('gist-it.newGistsDefaultToPrivate') 13 | @files = {} 14 | @description = "" 15 | 16 | # GitHub Enterprise Support https://enterprise.github.com/help/articles/using-the-api 17 | if atom.config.get('gist-it.gitHubEnterpriseHost') 18 | @hostname = atom.config.get('gist-it.gitHubEnterpriseHost') 19 | @path = '/api/v3/gists' 20 | else 21 | @hostname = 'api.github.com' 22 | @path = '/gists' 23 | 24 | getSecretTokenPath: -> 25 | path.join(atom.getConfigDirPath(), "gist-it.token") 26 | 27 | getToken: -> 28 | if not @token? 29 | config = atom.config.get("gist-it.userToken").trim() 30 | @token = if config? and config.toString().length > 0 31 | config 32 | else if fs.existsSync(@getSecretTokenPath()) 33 | fs.readFileSync(@getSecretTokenPath()) 34 | @token 35 | 36 | post: (callback) -> 37 | options = 38 | hostname: @hostname 39 | path: @path 40 | method: 'POST' 41 | headers: 42 | "User-Agent": "Atom" 43 | 44 | # Use the user's token if we have one 45 | if @getToken()? 46 | options.headers["Authorization"] = "token #{@getToken()}" 47 | 48 | request = protocol.request options, (res) -> 49 | res.setEncoding "utf8" 50 | body = '' 51 | res.on "data", (chunk) -> 52 | body += chunk 53 | res.on "end", -> 54 | response = JSON.parse(body) 55 | callback(response) 56 | 57 | request.write(JSON.stringify(@toParams())) 58 | 59 | request.end() 60 | 61 | toParams: -> 62 | description: @description 63 | files: @files 64 | public: @isPublic 65 | -------------------------------------------------------------------------------- /lib/gist-view.coffee: -------------------------------------------------------------------------------- 1 | {TextEditorView, View} = require 'atom-space-pen-views' 2 | {CompositeDisposable} = require 'atom' 3 | 4 | Gist = require './gist-model' 5 | shell = require 'shell' 6 | 7 | module.exports = 8 | class GistView extends View 9 | @content: -> 10 | @div tabIndex: -1, class: "gist overlay from-top padded", => 11 | @div class: "inset-panel", => 12 | @div class: "panel-heading", => 13 | @span outlet: "title" 14 | @div class: "btn-toolbar pull-right", outlet: 'toolbar', => 15 | @div class: "btn-group", => 16 | @button outlet: "privateButton", class: "btn", "Secret" 17 | @button outlet: "publicButton", class: "btn", "Public" 18 | @div class: "panel-body padded", => 19 | @div outlet: 'gistForm', class: 'gist-form', => 20 | @subview 'descriptionEditor', new TextEditorView(mini: true, placeholder: 'Gist Description') 21 | @div class: 'block pull-right', => 22 | @button outlet: 'cancelButton', class: 'btn inline-block-tight', "Cancel" 23 | @button outlet: 'gistButton', class: 'btn btn-primary inline-block-tight', "Gist It" 24 | @div class: 'clearfix' 25 | @div outlet: 'progressIndicator', => 26 | @span class: 'loading loading-spinner-medium' 27 | @div outlet: 'urlDisplay', => 28 | @span "All Done! the Gist's URL has been copied to your clipboard." 29 | 30 | initialize: (serializeState) -> 31 | @gist = null 32 | @subscriptions = new CompositeDisposable 33 | 34 | @handleEvents() 35 | 36 | atom.commands.add 'atom-text-editor', 37 | 'gist-it:gist-current-file': => @gistCurrentFile(), 38 | "gist-it:gist-selection": => @gistSelection(), 39 | "gist-it:gist-open-buffers": => @gistOpenBuffers() 40 | 41 | 42 | # Returns an object that can be retrieved when package is activated 43 | serialize: -> 44 | 45 | # Tear down any state and detach 46 | destroy: -> 47 | @subscriptions?.dispose() 48 | atom.views.getView(atom.workspace).focus() 49 | @detach() 50 | 51 | handleEvents: -> 52 | @gistButton.on 'click', => @gistIt() 53 | @cancelButton.on 'click', => @destroy() 54 | @publicButton.on 'click', => @makePublic() 55 | @privateButton.on 'click', => @makePrivate() 56 | 57 | @subscriptions.add atom.commands.add @descriptionEditor.element, 58 | 'core:confirm': => @gistIt() 59 | 'core:cancel': => @destroy() 60 | 61 | @subscriptions.add atom.commands.add @element, 62 | 'core:close': => @destroy 63 | 'core:cancel': => @destroy 64 | 65 | @on 'focus', => 66 | console.log("foo") 67 | @descriptionEditor.focus() 68 | 69 | gistCurrentFile: -> 70 | activeEditor = atom.workspace.getActiveTextEditor() 71 | fileContent = activeEditor.getText() 72 | 73 | if !!(fileContent.trim()) 74 | @gist = new Gist() 75 | 76 | @gist.files[activeEditor.getTitle()] = 77 | content: fileContent 78 | 79 | @title.text "Gist Current File" 80 | @presentSelf() 81 | 82 | else 83 | atom.notifications.addError('Gist could not be created: The current file is empty.') 84 | 85 | gistSelection: -> 86 | activeEditor = atom.workspace.getActiveTextEditor() 87 | selectedText = activeEditor.getSelectedText() 88 | 89 | if !!(selectedText.trim()) 90 | @gist = new Gist() 91 | 92 | @gist.files[activeEditor.getTitle()] = 93 | content: selectedText 94 | 95 | @title.text "Gist Selection" 96 | @presentSelf() 97 | else 98 | atom.notifications.addError('Gist could not be created: The current selection is empty.') 99 | 100 | gistOpenBuffers: -> 101 | skippedEmptyBuffers = false 102 | skippedAllBuffers = true 103 | @gist = new Gist() 104 | 105 | for editor in atom.workspace.getTextEditors() 106 | editorText = editor.getText() 107 | if !!(editorText.trim()) 108 | @gist.files[editor.getTitle()] = content: editorText 109 | skippedAllBuffers = false 110 | else 111 | skippedEmptyBuffers = true 112 | 113 | if !skippedAllBuffers 114 | @title.text "Gist Open Buffers" 115 | @presentSelf() 116 | else 117 | atom.notifications.addError('Gist could not be created: No open buffers with content.') 118 | return 119 | 120 | if skippedEmptyBuffers 121 | atom.notifications.addWarning('Some empty buffers will not be added to the Gist.') 122 | 123 | presentSelf: -> 124 | @showGistForm() 125 | atom.workspace.addTopPanel(item: this) 126 | 127 | @descriptionEditor.focus() 128 | 129 | gistIt: -> 130 | @showProgressIndicator() 131 | 132 | @gist.description = @descriptionEditor.getText() 133 | 134 | @gist.post (response) => 135 | atom.clipboard.write response.html_url 136 | 137 | if atom.config.get('gist-it.openAfterCreate') 138 | shell.openExternal(response.html_url) 139 | 140 | @showUrlDisplay() 141 | setTimeout (=> 142 | @destroy() 143 | ), 1000 144 | 145 | 146 | makePublic: -> 147 | @publicButton.addClass('selected') 148 | @privateButton.removeClass('selected') 149 | @gist.isPublic = true 150 | 151 | makePrivate: -> 152 | @privateButton.addClass('selected') 153 | @publicButton.removeClass('selected') 154 | @gist.isPublic = false 155 | 156 | showGistForm: -> 157 | if @gist.isPublic then @makePublic() else @makePrivate() 158 | @descriptionEditor.setText @gist.description 159 | 160 | @toolbar.show() 161 | @gistForm.show() 162 | @urlDisplay.hide() 163 | @progressIndicator.hide() 164 | 165 | showProgressIndicator: -> 166 | @toolbar.hide() 167 | @gistForm.hide() 168 | @urlDisplay.hide() 169 | @progressIndicator.show() 170 | 171 | showUrlDisplay: -> 172 | @toolbar.hide() 173 | @gistForm.hide() 174 | @urlDisplay.show() 175 | @progressIndicator.hide() 176 | -------------------------------------------------------------------------------- /media/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhysforyou/gist-it/fc452b52f85b63f42cc293f765474bd06e431033/media/screencast.gif -------------------------------------------------------------------------------- /menus/gist.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | { 3 | 'label': 'Packages' 4 | 'submenu': [ 5 | 'label': 'Gist It' 6 | 'submenu': [ 7 | { 'label': 'Gist Current File', 'command': 'gist-it:gist-current-file' } 8 | { 'label': 'Gist Selection', 'command': 'gist-it:gist-selection' } 9 | { 'label': 'Gist Open Buffers', 'command': 'gist-it:gist-open-buffers'} 10 | ] 11 | ] 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gist-it", 3 | "main": "./lib/gist-it", 4 | "version": "0.9.2", 5 | "private": true, 6 | "description": "Quickly and easily share your code on gist.github.com", 7 | "activationCommands": { 8 | "atom-workspace": [ 9 | "settings-view:open", 10 | "gist-it:gist-current-file", 11 | "gist-it:gist-selection", 12 | "gist-it:gist-open-buffers" 13 | ] 14 | }, 15 | "repository": "https://github.com/rpowelll/gist-it", 16 | "license": "MIT", 17 | "engines": { 18 | "atom": ">0.50.0" 19 | }, 20 | "dependencies": { 21 | "atom-space-pen-views": "^2.0.3" 22 | }, 23 | "keywords": [ 24 | "github", 25 | "gist", 26 | "share" 27 | ], 28 | "configSchema": { 29 | "userToken": { 30 | "order": 1, 31 | "title": "OAuth token", 32 | "description": "Enter an OAuth token to have Gists posted to your GitHub account. This token must include the gist scope.", 33 | "type": "string", 34 | "default": "" 35 | }, 36 | "newGistsDefaultToPrivate": { 37 | "order": 2, 38 | "title": "New Gists default to private", 39 | "description": "Make Gists private by default.", 40 | "type": "boolean", 41 | "default": false 42 | }, 43 | "gitHubEnterpriseHost": { 44 | "order": 3, 45 | "title": "GitHub Enterprise Host", 46 | "description": "If you want to publish Gists to a GitHub Enterprise instance, enter the hostname here.", 47 | "type": "string", 48 | "default": "" 49 | }, 50 | "useHttp": { 51 | "order": 4, 52 | "title": "Use HTTP", 53 | "description": "Enable if your GitHub Enterprise instance is only available via HTTP, not HTTPS.", 54 | "type": "boolean", 55 | "default": false 56 | }, 57 | "openAfterCreate": { 58 | "order": 5, 59 | "title": "Open new Gist after create", 60 | "description": "Automatically open newly created Gists in the default web browser.", 61 | "type": "boolean", 62 | "default": false 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /spec/gist-spec.coffee: -------------------------------------------------------------------------------- 1 | Gist = require '../lib/gist' 2 | 3 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs. 4 | # 5 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` 6 | # or `fdescribe`). Remove the `f` to unfocus the block. 7 | 8 | describe "Gist", -> 9 | activationPromise = null 10 | 11 | beforeEach -> 12 | atom.workspaceView = new WorkspaceView 13 | activationPromise = atom.packages.activatePackage('gist') 14 | 15 | describe "when the gist:toggle event is triggered", -> 16 | it "attaches and then detaches the view", -> 17 | expect(atom.workspaceView.find('.gist')).not.toExist() 18 | 19 | # This is an activation event, triggering it will cause the package to be 20 | # activated. 21 | atom.workspaceView.trigger 'gist:toggle' 22 | 23 | waitsForPromise -> 24 | activationPromise 25 | 26 | runs -> 27 | expect(atom.workspaceView.find('.gist')).toExist() 28 | atom.workspaceView.trigger 'gist:toggle' 29 | expect(atom.workspaceView.find('.gist')).not.toExist() 30 | -------------------------------------------------------------------------------- /spec/gist-view-spec.coffee: -------------------------------------------------------------------------------- 1 | GistView = require '../lib/gist-view' 2 | {WorkspaceView} = require 'atom' 3 | 4 | describe "GistView", -> 5 | it "has one valid test", -> 6 | expect("life").toBe "easy" 7 | -------------------------------------------------------------------------------- /styles/gist.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .gist { 8 | .panel-body.padded { 9 | padding-bottom: 0; 10 | } 11 | 12 | .loading { 13 | margin: auto; 14 | margin-top: 1em; 15 | margin-bottom: 1em; 16 | } 17 | } 18 | --------------------------------------------------------------------------------