├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── autoload ├── ctrlp │ └── slackmemo.vim └── slackmemo.vim ├── doc └── slackmemo-vim.txt └── plugin └── slackmemo.vim /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | .cache 3 | .cache2 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.1 2 | 3 | * Replace `channels.xxx` with `conversations.xxx`, Thanks [cympfh](https://github.com/cympfh) ! 4 | 5 | 6 | ## 0.2.0 7 | 8 | * Fix refetch list errors. 9 | * Fix versioning. (Should made it 0.2.0 when added `:SlackMemoSearch`.) 10 | 11 | 12 | ## 0.1.3 13 | 14 | * Fix typo... 15 | 16 | 17 | ## 0.1.2 18 | 19 | * Add `:SlackMemoSearch` method. Thanks [@h-michael](https://github.com/h-michael) !. 20 | 21 | 22 | ## 0.1.1 23 | 24 | * Fix url formating decode 25 | - `` => `http://hoge.com/fuga/` 26 | * Fix deletion memo bugs. (ref: [issue-#1](https://github.com/tsuyoshiwada/slack-memo-vim/issues/1)) 27 | 28 | 29 | ## 0.1.0 30 | 31 | * Support CtrlP lists. 32 | 33 | 34 | ## 0.0.2 35 | 36 | * Fix error when buffer with same name already exists. 37 | 38 | 39 | ## 0.0.1 40 | 41 | * First release. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 by tsuyoshiwada. 2 | modification, are permitted provided that the following conditions are met: 3 | 4 | 1. Redistributions of source code must retain the above copyright notice, 5 | this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, 7 | this list of conditions and the following disclaimer in the documentation 8 | and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 11 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 12 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 13 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 14 | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 15 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 16 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 17 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 18 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 19 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 20 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 21 | OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | slack-memo.vim 2 | ============== 3 | 4 | Simple memo manager with Vim using Slack. 5 | 6 | 7 | ## Screenshots 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/tsuyoshiwada/slack-memo-vim/images/screenshot.gif) 10 | 11 | 12 | ## Usage 13 | 14 | Post a memo on Slack. 15 | 16 | ```vim 17 | :SlackMemoPost 18 | ``` 19 | 20 | List memos on Slack. 21 | 22 | ```vim 23 | :SlackMemoList 24 | ``` 25 | 26 | List memos on Slack with CtrlP. 27 | 28 | ```vim 29 | :SlackMemoCtrlP 30 | ``` 31 | 32 | List search result memos on Slack 33 | ```vim 34 | :SlackMemoSearch search query 35 | ``` 36 | 37 | 38 | ### Keymaps in the memo list 39 | 40 | The following keymaps are available in the buffer of the memo list. It does not work with CtrlP. 41 | 42 | | key | description | 43 | |:---------------|:------------------------| 44 | | `Enter` or `o` | Open memo on cursor. | 45 | | `Esc` or `q` | Close memo list window. | 46 | | `d` | Delete memo on cursor. | 47 | | `y` | Yank memo on cursor. | 48 | | `r` | Update list window. | 49 | 50 | You can udpate it by saving memo opened in buffer. 51 | 52 | 53 | ## Install 54 | 55 | It's easy to install using plugin manager. 56 | Depending on your plugin manager, please add the following to `.vimrc`. 57 | 58 | ### Install with [dein](https://github.com/Shougo/dein.vim) 59 | 60 | ```vim 61 | call dein#add('tsuyoshiwada/slack-memo-vim', {'depends': 'mattn/webapi-vim'}) 62 | ``` 63 | 64 | ### Install with [NeoBundle](https://github.com/Shougo/neobundle.vim) 65 | 66 | ```vim 67 | NeoBundle 'tsuyoshiwada/slack-memo-vim', {'depends': 'mattn/webapi-vim'} 68 | ``` 69 | 70 | 71 | 72 | ## Requirements 73 | 74 | * `curl` command. 75 | * [mattn/webapi-vim](https://github.com/mattn/webapi-vim). 76 | 77 | 78 | 79 | ## Setup 80 | 81 | You need a token to use [Slack Web API](https://api.slack.com/web). 82 | Please set your token on `g:slack_memo_token`. and set channel Id in `g:slack_memo_channel`. 83 | 84 | ```vim 85 | let g:slack_memo_token = '' 86 | let g:slack_memo_channel = '' 87 | ``` 88 | 89 | If you do not know the Id right away, you can easily check with the [channel.list](https://api.slack.com/methods/channels.list/test) tester. 90 | 91 | Setup is complete! 92 | 93 | 94 | 95 | ## Tips 96 | 97 | ### Example keymap 98 | 99 | You can post / list easily with the following keymap. 100 | 101 | ```vim 102 | nnoremap smp :SlackMemoPost 103 | nnoremap sml :SlackMemoList 104 | ``` 105 | 106 | ### GFM TODO list 107 | 108 | Support GFM TODO list. Posting following memo will be displayed with emoji on Slack. 109 | 110 | ```markdown 111 | * [x] Done1 112 | * [x] Done2 113 | * [ ] TODO... 114 | ``` 115 | 116 | 117 | 118 | ## Contributing 119 | 120 | ### Reporting issue 121 | 122 | * [Welcome issue!](https://github.com/tsuyoshiwada/slack-memo-vim/issues) 123 | 124 | ### Contributing to code 125 | 126 | * Fork it. 127 | * Commit your changes and give your commit message. 128 | * Push to your fork on GitHub. 129 | * Open a Pull Request. 130 | 131 | 132 | ## Credit 133 | 134 | * [Slack](https://slack.com/). 135 | * Inspired by [gist-vim](https://github.com/mattn/gist-vim). 136 | 137 | 138 | 139 | ## License 140 | 141 | See the [LICENSE](https://raw.githubusercontent.com/tsuyoshiwada/slack-memo-vim/master/LICENSE). 142 | 143 | 144 | 145 | ## TODO 146 | 147 | * [x] Support [CtrlP](https://github.com/ctrlpvim/ctrlp.vim). 148 | * [ ] Support sort list. 149 | * [ ] Support vertical split list window. 150 | * [ ] Support open memo in new tab. 151 | * [ ] Delete multiple memos. 152 | * [ ] List more than 1000 memos. 153 | 154 | -------------------------------------------------------------------------------- /autoload/ctrlp/slackmemo.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: slack-memo.vim 3 | " Version: 0.2.1 4 | " Author: tsuyoshiwada 5 | " WebPage: http://github.com/tsuyoshiwada/slack-memo-vim 6 | " License: BSD 7 | 8 | if exists('g:loaded_ctrlp_slackmemo') && g:loaded_ctrlp_slackmemo 9 | finish 10 | endif 11 | let g:loaded_ctrlp_slackmemo = 1 12 | 13 | 14 | let s:slackmemo_var = { 15 | \ 'init': 'ctrlp#slackmemo#init()', 16 | \ 'accept': 'ctrlp#slackmemo#accept', 17 | \ 'exit': 'ctrlp#slackmemo#exit()', 18 | \ 'lname': 'slackmemo', 19 | \ 'sname': 'slackmemo', 20 | \ 'type': 'path', 21 | \ 'sort': 0 22 | \ } 23 | 24 | if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars) 25 | let g:ctrlp_ext_vars = add(g:ctrlp_ext_vars, s:slackmemo_var) 26 | else 27 | let g:ctrlp_ext_vars = [s:slackmemo_var] 28 | endif 29 | 30 | 31 | function! s:format_memo(memo) abort 32 | let ts = a:memo.ts 33 | let datetime = strftime("%Y-%m-%d", substitute(ts, '^\(\d\+\)\.\d\+$', '\1', 'g')) 34 | let title = split(a:memo.text, '\n')[0] 35 | let title = substitute(title, ' ', ' ', 'g') 36 | return printf('[%s](%s): %s', ts, datetime, title) 37 | endfunction 38 | 39 | 40 | function! ctrlp#slackmemo#init() 41 | let res = slackmemo#fetch() 42 | if !res.ok 43 | return "" 44 | endif 45 | 46 | let s:list = res.messages 47 | let s:messages = map(deepcopy(s:list), 's:format_memo(v:val)') 48 | return s:messages 49 | endfunction 50 | 51 | 52 | function! ctrlp#slackmemo#accept(mode, str) 53 | let ts = matchlist(filter(deepcopy(s:messages), 'v:val ==# a:str')[0], '^\[\(\d\+\.\d\+\)\]')[1] 54 | let memo = filter(deepcopy(s:list), 'slackmemo#compareMemoWithTS(v:val, "'.slackmemo#escapeTS(ts).'", 0)')[0] 55 | 56 | call ctrlp#exit() 57 | redraw! 58 | call slackmemo#open(memo) 59 | endfunction 60 | 61 | 62 | function! ctrlp#slackmemo#exit() 63 | if exists('s:list') 64 | unlet! s:list 65 | endif 66 | 67 | if exists('s:messages') 68 | unlet! s:messages 69 | endif 70 | endfunction 71 | 72 | 73 | let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars) 74 | function! ctrlp#slackmemo#id() 75 | return s:id 76 | endfunction 77 | -------------------------------------------------------------------------------- /autoload/slackmemo.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: slack-memo.vim 3 | " Version: 0.2.1 4 | " Author: tsuyoshiwada 5 | " WebPage: http://github.com/tsuyoshiwada/slack-memo-vim 6 | " License: BSD 7 | 8 | let s:save_cpo = &cpoptions 9 | set cpoptions&vim 10 | 11 | 12 | if !executable('curl') 13 | echohl ErrorMsg | echomsg 'SlackMemo: require ''curl'' command' | echohl None 14 | finish 15 | endif 16 | 17 | 18 | if globpath(&rtp, 'autoload/webapi/http.vim') ==# '' 19 | echohl ErrorMsg | echomsg 'SlackMemo: require ''webapi'', install https://github.com/mattn/webapi-vim' | echohl None 20 | finish 21 | else 22 | call webapi#json#true() 23 | endif 24 | 25 | 26 | if !exists('g:slack_memo_token') 27 | echohl ErrorMsg | echomsg 'SlackMemo: You have not configured a token. Read '':help slack-memo-setup''.' | echohl None 28 | finish 29 | endif 30 | 31 | 32 | if !exists('g:slack_memo_channel') 33 | echohl ErrorMsg | echomsg 'SlackMemo: You have not configured a channel id. Read '':help slack-memo-setup''.' | echohl None 34 | finish 35 | endif 36 | 37 | 38 | function! s:setGlobalOptDefault(opt, val) 39 | if !exists('g:' . a:opt) 40 | let g:{a:opt} = a:val 41 | endif 42 | endfunction 43 | 44 | call s:setGlobalOptDefault('slack_memo_bot_username', 'Vim (bot)') 45 | call s:setGlobalOptDefault('slack_memo_bot_icon_url', '') 46 | call s:setGlobalOptDefault('slack_memo_bot_icon_emoji', '') 47 | call s:setGlobalOptDefault('slack_memo_list_count', 1000) 48 | 49 | 50 | let s:bufprefix = 'slackmemo' . (has('unix') ? ':' : '_') 51 | let s:listbufname = s:bufprefix.'-list' 52 | let s:slackapi = 'https://slack.com/api' 53 | 54 | 55 | " Utilities 56 | function! s:format_memo(memo) abort 57 | let ts = a:memo.ts 58 | let datetime = strftime("%Y-%m-%d %H:%M:%S", substitute(ts, '^\(\d\+\)\.\d\+$', '\1', 'g')) 59 | let title = split(a:memo.text, '\n')[0] 60 | let title = substitute(title, ' ', ' ', 'g') 61 | return printf('memo: %s [%s] %s', ts, datetime, title) 62 | endfunction 63 | 64 | 65 | function! slackmemo#compareMemoWithTS(memo, ts, cond) 66 | return a:cond == 0 ? a:memo.ts == a:ts : a:memo.ts != a:ts 67 | endfunction 68 | 69 | 70 | function! s:decode_memo_text(text) 71 | let text = a:text 72 | let text = join(map(split(text, "\n"), 's:encode_memo_line(v:val)'), "\n") 73 | return text 74 | endfunction 75 | 76 | function! s:encode_memo_line(line) 77 | let line = a:line 78 | 79 | " GFM - Checkbox 80 | if match(line, '[\*\-] \[ \]') > -1 81 | let line = substitute(line, '\([\*\-]\) \[ \]', '\1 :white_large_square: ', '') 82 | elseif match(line, '[\*\-] \[x\]') > -1 83 | let line = substitute(line, '\([\*\-]\) \[x\]', '\1 :ballot_box_with_check: ', '') 84 | endif 85 | 86 | return line 87 | endfunction 88 | 89 | 90 | function! s:decode_memo_line(line) 91 | let line = a:line 92 | 93 | " GFM - Checkbox 94 | if match(line, '[\*\-] :white_large_square: ') > -1 95 | let line = substitute(line, '\([\*\-]\) :white_large_square: ', '\1 [ ]', '') 96 | elseif match(line, '[\*\-] :ballot_box_with_check: ') > -1 97 | let line = substitute(line, '\([\*\-]\) :ballot_box_with_check: ', '\1 [x]', '') 98 | endif 99 | 100 | " URL 101 | if match(line, '') > -1 102 | let line = substitute(line, '<\(https\?:\/\/\S\+\)>', '\1', 'g') 103 | endif 104 | 105 | return line 106 | endfunction 107 | 108 | 109 | " SlackMemo 110 | function! slackmemo#escapeTS(ts) 111 | return substitute(a:ts, '\.', '\\.', 'g') 112 | endfunction 113 | 114 | 115 | function! slackmemo#decodeMemo(memo) 116 | let text = webapi#html#decodeEntityReference(a:memo.text) 117 | let text = join(map(split(text, "\n"), 's:decode_memo_line(v:val)'), "\n") 118 | let a:memo.text = text 119 | return a:memo 120 | endfunction 121 | 122 | 123 | function! slackmemo#fetch() abort 124 | let res = webapi#http#get(s:slackapi . '/conversations.history', { 125 | \ 'token': g:slack_memo_token, 126 | \ 'channel': g:slack_memo_channel, 127 | \ 'count': g:slack_memo_list_count 128 | \ }) 129 | let res = webapi#json#decode(res.content) 130 | 131 | if res.ok 132 | let messages = map(copy(res.messages), 'slackmemo#decodeMemo(v:val)') 133 | let messages = filter(res.messages, '!empty(v:val.text)') 134 | let res.messages = messages 135 | endif 136 | 137 | return res 138 | endfunction 139 | 140 | 141 | function! slackmemo#list(mode) abort 142 | redraw | echon 'Listing slack memo... ' 143 | 144 | let res = slackmemo#fetch() 145 | 146 | if !res.ok 147 | redraw | echon 'Bad request...' 148 | return 149 | endif 150 | 151 | if a:mode == "CtrlP" 152 | if globpath(&rtp, 'autoload/ctrlp.vim') ==# '' 153 | echohl ErrorMsg | echomsg 'SlackMemo: require ''CtrlP'', install https://github.com/ctrlpvim/ctrlp.vim' | echohl None 154 | return 155 | endif 156 | call ctrlp#init(ctrlp#slackmemo#id()) 157 | 158 | else 159 | call s:SlackMemoListOpen() 160 | let oldpos = getpos('.') 161 | let b:messages = res.messages 162 | call s:SlackMemoListRender() 163 | call cursor(oldpos[1], oldpos[2]) 164 | endif 165 | endfunction 166 | 167 | 168 | function! slackmemo#search(mode, ...) abort 169 | let query = join(a:000, ' ') 170 | let res = slackmemo#chnnel_name() 171 | 172 | if !res.ok 173 | redraw | echon 'Invalid channel id...' 174 | return 175 | endif 176 | 177 | let res = slackmemo#search_messages(res.channel.name, query) 178 | call s:SlackMemoListOpen() 179 | let oldpos = getpos('.') 180 | let b:messages = slackmemo#matches2memos(res.messages.matches) 181 | call s:SlackMemoListRender() 182 | call cursor(oldpos[1], oldpos[2]) 183 | endfunction 184 | 185 | 186 | function! slackmemo#search_messages(channel_name, query) abort 187 | let query = 'in:' . a:channel_name . ' ' . a:query 188 | 189 | let res = webapi#http#get(s:slackapi . '/search.messages', { 190 | \ 'token': g:slack_memo_token, 191 | \ 'query': query, 192 | \ 'count': g:slack_memo_list_count 193 | \ }) 194 | let res = webapi#json#decode(res.content) 195 | 196 | if res.ok 197 | let matches = map(copy(res.messages.matches), 'slackmemo#decodeMemo(v:val)') 198 | let messages = filter(res.messages.matches, '!empty(v:val.text)') 199 | let res.messages.matches = matches 200 | endif 201 | 202 | return res 203 | endfunction 204 | 205 | 206 | function! slackmemo#chnnel_name() 207 | let res = webapi#http#get(s:slackapi . '/conversations.info', { 208 | \ 'token': g:slack_memo_token, 209 | \ 'channel': g:slack_memo_channel 210 | \ }) 211 | let res = webapi#json#decode(res.content) 212 | 213 | return res 214 | endfunction 215 | 216 | 217 | function! slackmemo#matches2memos(matches) abort 218 | let messages = [] 219 | for m in a:matches 220 | let message = { 221 | \ 'type': m.type, 222 | \ 'ts': m.ts, 223 | \ 'user': m.user, 224 | \ 'text': m.text 225 | \ } 226 | call add(messages, message) 227 | endfor 228 | return messages 229 | endfunction 230 | 231 | 232 | function! slackmemo#post() abort 233 | let text = join(getline(0, '$'), "\n") 234 | 235 | if text == '' 236 | redraw | echon 'Post failed, current buffer is empty!' 237 | return 238 | endif 239 | 240 | redraw | echon 'Posting memo... ' 241 | 242 | let res = webapi#http#post(s:slackapi . '/chat.postMessage', { 243 | \ 'token': g:slack_memo_token, 244 | \ 'channel': g:slack_memo_channel, 245 | \ 'text': s:decode_memo_text(text), 246 | \ 'username': g:slack_memo_bot_username, 247 | \ 'icon_url': g:slack_memo_bot_icon_url, 248 | \ 'icon_emoji': g:slack_memo_bot_icon_emoji 249 | \ }) 250 | let res = webapi#json#decode(res.content) 251 | 252 | if !res.ok 253 | redraw | echon 'Post failed...' 254 | return 255 | endif 256 | 257 | let b:slackmemo = { 258 | \ 'ts': res.ts 259 | \ } 260 | 261 | silent exec 'noautocmd file' s:bufprefix.b:slackmemo.ts.'.md' 262 | call s:SlackMemoCurrentBufferToEditable() 263 | call s:SlackMemoAppend(res.message) 264 | 265 | redraw | echon 'Done !!' 266 | endfunction 267 | 268 | 269 | function! slackmemo#open(memo) 270 | let bufname = s:bufprefix . a:memo.ts . '.md' 271 | let winnum = bufwinnr(bufnr(bufname)) 272 | 273 | if winnum != -1 274 | if winnum != bufwinnr('%') 275 | exe winnum 'wincmd w' 276 | endif 277 | setlocal modifiable 278 | else 279 | for bufnr in range(1, bufnr('$')) 280 | if bufname == bufname(bufnr) 281 | silent exe 'bw!' bufnr 282 | endif 283 | endfor 284 | 285 | exec 'silent noautocmd rightbelow new' 286 | bw! 287 | 288 | setlocal noswapfile 289 | silent exec 'noautocmd file' bufname 290 | endif 291 | 292 | filetype detect 293 | silent %d _ 294 | 295 | call setline(1, split(a:memo.text, "\n")) 296 | call s:SlackMemoCurrentBufferToEditable() 297 | endfunction 298 | 299 | 300 | function! s:SlackMemoListWinnr() 301 | return bufwinnr(bufnr(s:listbufname)) 302 | endfunction 303 | 304 | 305 | function! s:SlackMemoListOpen() 306 | let winnum = s:SlackMemoListWinnr() 307 | if winnum != -1 308 | if winnum != bufwinnr('%') 309 | exe winnum 'wincmd w' 310 | endif 311 | else 312 | exec 'silent noautocmd split' s:listbufname 313 | endif 314 | endfunction 315 | 316 | 317 | function! s:SlackMemoListClose() 318 | let winnum = bufwinnr(bufnr(s:listbufname)) 319 | if winnum != -1 320 | if winnum != bufwinnr('%') 321 | exe winnum 'wincmd w' 322 | bw! 323 | endif 324 | endif 325 | endfunction 326 | 327 | 328 | function! s:SlackMemoListRender() 329 | if exists('b:messages') 330 | let messages = deepcopy(b:messages) 331 | else 332 | let messages = [] 333 | endif 334 | 335 | setlocal modifiable 336 | silent %d _ 337 | 338 | let lines = map(messages, 's:format_memo(v:val)') 339 | call setline(1, split(join(lines, "\n"), "\n")) 340 | sort! n /\%2c/ 341 | 342 | syntax match SpecialKey /^memo: /he=e-1 343 | syntax match Title /^memo: \S\+/hs=s+5 contains=ALL 344 | syntax match Type /\[\d\{4}-\d\{2}-\d\{2} \d\{2}:\d\{2}:\d\{2}\]/ contains=ALL 345 | 346 | setlocal buftype=nofile bufhidden=hide noswapfile 347 | setlocal cursorline 348 | setlocal nomodified 349 | setlocal nomodifiable 350 | nohlsearch 351 | 352 | nnoremap :call SlackMemoListAction(0) 353 | nnoremap o :call SlackMemoListAction(0) 354 | nnoremap d :call SlackMemoListAction(1) 355 | nnoremap y :call SlackMemoListAction(2) 356 | nnoremap r :call SlackMemoListAction(3) 357 | nnoremap :bw 358 | nnoremap q :bw 359 | 360 | redraw | echo '' 361 | endfunction 362 | 363 | 364 | function! s:SlackMemoAppend(memo) 365 | let winnum = s:SlackMemoListWinnr() 366 | if winnum < 0 367 | return 368 | endif 369 | 370 | let currentwinnum = bufwinnr(bufnr(bufname('%'))) 371 | 372 | call s:SlackMemoListOpen() 373 | let view = winsaveview() 374 | let b:messages = getbufvar('%', 'messages') 375 | call add(b:messages, a:memo) 376 | setlocal modifiable 377 | call append(0, s:format_memo(a:memo)) 378 | setlocal nomodifiable 379 | call winrestview(view) 380 | 381 | exe currentwinnum 'wincmd w' 382 | endfunction 383 | 384 | 385 | function! s:SlackMemoCurrentBufferToEditable() 386 | setlocal buftype=acwrite bufhidden=hide noswapfile 387 | setlocal nomodified 388 | doau StdinReadPost,BufRead,BufReadPost 389 | 390 | augroup SlackMemoWrite 391 | autocmd! BufWriteCmd call s:SlackMemoWrite(expand("")) 392 | augroup END 393 | endfunction 394 | 395 | 396 | function! s:SlackMemoListAction(mode) 397 | let line = getline('.') 398 | let ts = matchstr(line, '^memo:\s*\zs\(\d\+\.\d\+\)\ze\.*') 399 | 400 | if a:mode == 0 401 | call s:SlackMemoOpen(ts) 402 | call s:SlackMemoListClose() 403 | 404 | elseif a:mode == 1 405 | if confirm('Really force delete memo?', "&Yes\n&No", 2) == 1 406 | call s:SlackMemoDelete(ts) 407 | endif 408 | 409 | elseif a:mode == 2 410 | let memo = s:GetSlackMemoByTS(ts) 411 | let @" = memo.text 412 | let @* = memo.text 413 | redraw | echon 'Yanked!' 414 | 415 | elseif a:mode == 3 416 | call slackmemo#list("default") 417 | endif 418 | endfunction 419 | 420 | 421 | function! s:GetSlackMemoByTS(ts) 422 | if !exists('b:messages') 423 | return 0 424 | endif 425 | 426 | let messages = deepcopy(b:messages) 427 | let messages = filter(messages, 'slackmemo#compareMemoWithTS(v:val, "'.slackmemo#escapeTS(a:ts).'", 0)') 428 | return len(messages) > -1 ? messages[0] : 0 429 | endfunction 430 | 431 | 432 | function! s:SlackMemoOpen(ts) 433 | let memo = s:GetSlackMemoByTS(a:ts) 434 | call slackmemo#open(memo) 435 | endfunction 436 | 437 | 438 | function! s:SlackMemoWrite(fname) abort 439 | let text = join(getline(0, '$'), "\n") 440 | let bufname = bufname('%') 441 | let bufnamemx = '^' . s:bufprefix .'\(\d\+\.\d\+\)\.md$' 442 | let ts = '' 443 | 444 | if bufname =~# bufnamemx 445 | let ts = matchlist(bufname, bufnamemx)[1] 446 | elseif exists('b:slackmemo') && has_key(b:slackmemo, 'ts') 447 | let ts = b:slackmemo['ts'] 448 | endif 449 | 450 | if ts != '' 451 | call s:SlackMemoUpdate(ts, text) 452 | endif 453 | endfunction 454 | 455 | 456 | function! s:SlackMemoUpdate(ts, text) 457 | redraw | echon 'Updating memo...' 458 | 459 | let res = webapi#http#post(s:slackapi . '/chat.update', { 460 | \ 'token': g:slack_memo_token, 461 | \ 'channel': g:slack_memo_channel, 462 | \ 'ts': a:ts, 463 | \ 'text': s:decode_memo_text(a:text) 464 | \ }) 465 | let res = webapi#json#decode(res.content) 466 | 467 | if !res.ok 468 | redraw 469 | echomsg 'Bad request...' 470 | return 471 | endif 472 | 473 | let winnum = s:SlackMemoListWinnr() 474 | if winnum > -1 475 | let currentwinnum = bufwinnr(bufnr(bufname('%'))) 476 | 477 | call s:SlackMemoListOpen() 478 | let line = s:format_memo(res) 479 | let view = winsaveview() 480 | setlocal modifiable 481 | normal! gg 482 | if search('memo: '.a:ts) 483 | call setline('.', line) 484 | endif 485 | setlocal nomodifiable 486 | call winrestview(view) 487 | 488 | exe currentwinnum 'wincmd w' 489 | endif 490 | 491 | setlocal nomodified 492 | redraw | echon 'Updated!' 493 | endfunction 494 | 495 | 496 | function! s:SlackMemoDelete(ts) abort 497 | redraw | echon 'Deleting memo...' 498 | 499 | let res = webapi#http#post(s:slackapi . '/chat.delete', { 500 | \ 'token': g:slack_memo_token, 501 | \ 'channel': g:slack_memo_channel, 502 | \ 'ts': a:ts 503 | \ }) 504 | let res = webapi#json#decode(res.content) 505 | 506 | if !res.ok 507 | redraw 508 | echomsg 'Bad request...' 509 | return 510 | endif 511 | 512 | let view = winsaveview() 513 | setlocal modifiable 514 | normal! gg 515 | if search('memo: '.a:ts) 516 | exe ':normal! dd' 517 | endif 518 | setlocal nomodifiable 519 | call winrestview(view) 520 | 521 | if exists('b:slackmemo') 522 | unlet b:slackmemo 523 | endif 524 | 525 | redraw | echon 'Deleted!!' 526 | endfunction 527 | 528 | 529 | 530 | 531 | let &cpo = s:save_cpo 532 | unlet s:save_cpo 533 | -------------------------------------------------------------------------------- /doc/slackmemo-vim.txt: -------------------------------------------------------------------------------- 1 | *slackmemo-vim.txt* Simple memo manager with Vim using Slack. 2 | 3 | Version: 0.2.1 4 | Author: tsuyoshiwada 5 | 6 | ============================================================================== 7 | CONTENTS *slack-memo-contents* 8 | 9 | 1. Usage |slack-memo-usage| 10 | 2. Install |slack-memo-install| 11 | 3. Requirements |slack-memo-requirements| 12 | 4. Setup |slack-memo-setup| 13 | 5. Variables |slack-memo-variables| 14 | 6. License |slack-memo-license| 15 | 7. Tips |slack-memo-tips| 16 | 17 | ============================================================================== 18 | USAGE *slack-memo-usage* 19 | 20 | - Post a memo on Slack. > 21 | 22 | :SlackMemoPost 23 | < 24 | - List memos on Slack. > 25 | 26 | :SlackMemoList 27 | < 28 | - Keymaps in the memo list. 29 | 30 | Key Description ~ 31 | Open memo on cursor. 32 | o Same as above... 33 | Close memo on cursor. 34 | q Same as above... 35 | d Delete memo on cursor. 36 | y Yank memo on cursor. 37 | r Update list window. 38 | 39 | - Update a memo on Slack. 40 | You can update the gist with the {:w} command within the memo buffer. 41 | 42 | - List memos on Slack with CtrlP. > 43 | 44 | :SlackMemoCtrlP 45 | < 46 | - List search result memos on Slack. > 47 | 48 | :SlackMemoSearch search query parameters 49 | 50 | ============================================================================== 51 | INSTALL *slack-memo-install* 52 | 53 | It's easy to install using plugin manager. 54 | Depending on your plugin manager, please add the following to .vimrc. 55 | 56 | - Install with dein. (https://github.com/Shougo/dein.vim) > 57 | 58 | call dein#add('tsuyoshiwada/slack-memo-vim', { 59 | \ 'depends': 'mattn/webapi-vim' 60 | \ }) 61 | < 62 | - Install with NeoBundle. (https://github.com/Shougo/neobundle.vim) > 63 | 64 | NeoBundle 'tsuyoshiwada/slack-memo-vim', { 65 | \ 'depends': 'mattn/webapi-vim' 66 | \ } 67 | < 68 | You need to install webapi-vim also: 69 | http://www.vim.org/scripts/script.php?script_id=4019 70 | 71 | If you want to use latest one: 72 | https://github.com/mattn/webapi-vim 73 | 74 | ============================================================================== 75 | REQUIREMENTS *slack-memo-requirements* 76 | 77 | - curl command (http://curl.haxx.se/) 78 | - webapi-vim (https://github.com/mattn/webapi-vim) 79 | 80 | ============================================================================== 81 | SETUP *slack-memo-setup* 82 | 83 | You need a token to use Slack Web API. (https://api.slack.com/web) 84 | Please set your token on |g:slack_memo_token|. 85 | > 86 | let g:slack_memo_token = '' 87 | < 88 | Please set channel ID in |g:slack_memo_channel|. 89 | If you do not know the ID right away, 90 | you can easily check with the channel.list tester. 91 | (https://api.slack.com/methods/channels.list/test) 92 | > 93 | let g:slack_memo_channel = '' 94 | < 95 | Setup is complete! 96 | 97 | ============================================================================== 98 | VARIABLES *slack-memo-variables* 99 | 100 | g:slack_memo_token *g:slack_memo_token* 101 | Please specify the token for Slack Web API. 102 | Refer to Slack for acquisition method. (https://api.slack.com/web) 103 | 104 | Required. 105 | 106 | g:slack_memo_channel *g:slack_memo_channel* 107 | Please specify channel ID with string. 108 | 109 | Required. 110 | 111 | g:slack_memo_bot_username *g:slack_memo_bot_username* 112 | Please specify the name of the user who posts the memo. 113 | 114 | Default value is 'Vim (bot)'. 115 | 116 | g:slack_memo_bot_icon_url *g:slack_memo_bot_icon_url* 117 | Please specify the user icon. 118 | 119 | Default value is ''. 120 | 121 | g:slack_memo_bot_icon_emoji *g:slack_memo_bot_icon_emoji* 122 | Please specify the user emoji. 123 | Default value is ''. 124 | 125 | g:slack_memo_list_count *g:slack_memo_list_count* 126 | Please specify length to display in list of memos. 127 | NOTE: between 1 and 1000. 128 | 129 | Default value is 1000. 130 | 131 | ============================================================================== 132 | LICENSE *slack-memo-license* 133 | 134 | Copyright 2016 by tsuyoshiwada. 135 | modification, are permitted provided that the following conditions are met: 136 | 137 | 1. Redistributions of source code must retain the above copyright notice, 138 | this list of conditions and the following disclaimer. 139 | 2. Redistributions in binary form must reproduce the above copyright notice, 140 | this list of conditions and the following disclaimer in the documentation 141 | and/or other materials provided with the distribution. 142 | 143 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 144 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 145 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 146 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 147 | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 148 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 149 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 150 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 151 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 152 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 153 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 154 | OF THE POSSIBILITY OF SUCH DAMAGE. 155 | 156 | ============================================================================== 157 | TIPS *slack-memo-tips* 158 | 159 | You can post / list easily with the following keymap. 160 | > 161 | nnoremap smp :SlackMemoPost 162 | nnoremap sml :SlackMemoList 163 | < 164 | Support GFM TODO list. 165 | Posting following memo will be displayed with emoji on Slack. 166 | > 167 | * [x] Done1 168 | * [x] Done2 169 | * [ ] TODO... 170 | < 171 | 172 | vim:tw=78:ts=8:ft=help:norl:ai:et 173 | -------------------------------------------------------------------------------- /plugin/slackmemo.vim: -------------------------------------------------------------------------------- 1 | "============================================================================= 2 | " File: slack-memo.vim 3 | " Version: 0.2.1 4 | " Author: tsuyoshiwada 5 | " WebPage: http://github.com/tsuyoshiwada/slack-memo-vim 6 | " License: BSD 7 | 8 | if &compatible || (exists('g:loaded_slackmemo_vim') && g:loaded_slackmemo_vim) 9 | finish 10 | endif 11 | let g:loaded_slackmemo_vim = 1 12 | 13 | command! -nargs=0 SlackMemoPost :call slackmemo#post() 14 | command! -nargs=0 SlackMemoList :call slackmemo#list('default') 15 | command! -nargs=0 SlackMemoCtrlP :call slackmemo#list('CtrlP') 16 | command! -nargs=* SlackMemoSearch :call slackmemo#search('default', ) 17 | --------------------------------------------------------------------------------