Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,24 +103,30 @@ You can use <kbd>ctrl k</kbd> (or your custom shortcut) to navigate in your DMs

You can use <kbd>ctrl d</kbd> (or your custom shortcut) to set snooze time.

### Get permalink

Focus on message and press <kbd>r</kbd> (or your custom shortcut) to get permalink (Quote message) and it will be put into your chat box.

### Default keybindings
```json
{
"keymap": {
"cursor_down": "j",
"cursor_left": "h",
"cursor_right":"l",
"cursor_up": "k",
"delete_message": "d",
"edit_message": "e",
"go_to_chatbox": "c",
"go_to_profile": "p",
"go_to_sidebar": "esc",
"open_quick_switcher": "ctrl k",
"quit_application": "q",
"set_edit_topic_mode": "t",
"set_insert_mode": "i",
"yank_message": "y"
"keymap": {
"cursor_down": "j",
"cursor_left": "h",
"cursor_right": "l",
"cursor_up": "k",
"delete_message": "d",
"edit_message": "e",
"go_to_chatbox": "c",
"go_to_profile": "p",
"go_to_sidebar": "esc",
"open_quick_switcher": "ctrl k",
"quit_application": "q",
"set_edit_topic_mode": "t",
"set_insert_mode": "i",
"yank_message": "y",
"get_permalink": "r",
"set_snooze": "ctrl d"
}
}
```
Expand Down
14 changes: 13 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import urwid
from datetime import datetime
from sclack.components import Attachment, Channel, ChannelHeader, ChatBox, Dm
from sclack.components import Indicators, MarkdownText, Message, MessageBox
from sclack.components import Indicators, MarkdownText, MessageBox
from sclack.component.message import Message
from sclack.components import NewMessagesDivider, Profile, ProfileSideBar
from sclack.components import Reaction, SideBar, TextDivider
from sclack.components import User, Workspaces
Expand Down Expand Up @@ -314,6 +315,16 @@ def edit_message(self, widget, user_id, ts, original_text):
self.chatbox.message_box.text = original_text
widget.set_edit_mode()

def get_permalink(self, widget, channel_id, ts):
try:
permalink = self.store.get_permalink(channel_id, ts)
if permalink and permalink.get('permalink'):
text = permalink.get('permalink')
self.set_insert_mode()
self.chatbox.message_box.text = text
except:
pass

def delete_message(self, widget, user_id, ts):
if self.store.state.auth['user_id'] == user_id:
if self.store.delete_message(self.store.state.channel['id'], ts)['ok']:
Expand Down Expand Up @@ -487,6 +498,7 @@ def render_message(self, message, channel_id=None):
self.lazy_load_images(files, message)

urwid.connect_signal(message, 'edit_message', self.edit_message)
urwid.connect_signal(message, 'get_permalink', self.get_permalink)
urwid.connect_signal(message, 'go_to_profile', self.go_to_profile)
urwid.connect_signal(message, 'go_to_sidebar', self.go_to_sidebar)
urwid.connect_signal(message, 'delete_message', self.delete_message)
Expand Down
1 change: 1 addition & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"set_edit_topic_mode": "t",
"set_insert_mode": "i",
"yank_message": "y",
"get_permalink": "r",
"set_snooze": "ctrl d"
},
"sidebar": {
Expand Down
116 changes: 116 additions & 0 deletions sclack/component/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import re

import urwid
import pyperclip
import webbrowser
from sclack.store import Store
from sclack.component.time import Time


class Message(urwid.AttrMap):
__metaclass__ = urwid.MetaSignals
signals = [
'delete_message',
'edit_message',
'get_permalink',
'go_to_profile',
'go_to_sidebar',
'quit_application',
'set_insert_mode',
'mark_read',
]

def __init__(self, ts, channel_id, user, text, indicators, reactions=(), attachments=()):
self.ts = ts
self.channel_id = channel_id
self.user_id = user.id
self.markdown_text = text
self.original_text = text.original_text
self.text_widget = urwid.WidgetPlaceholder(text)
main_column = [urwid.Columns([('pack', user), self.text_widget])]
main_column.extend(attachments)
self._file_index = len(main_column)
if reactions:
main_column.append(urwid.Columns([
('pack', reaction) for reaction in reactions
]))
self.main_column = urwid.Pile(main_column)
columns = [
('fixed', 7, Time(ts)),
self.main_column,
('fixed', indicators.size, indicators)
]
self.contents = urwid.Columns(columns)
super(Message, self).__init__(self.contents, None, {
None: 'active_message',
'message': 'active_message'
})

def keypress(self, size, key):
keymap = Store.instance.config['keymap']

if key == keymap['delete_message']:
urwid.emit_signal(self, 'delete_message', self, self.user_id, self.ts)
return True
elif key == keymap['edit_message']:
urwid.emit_signal(self, 'edit_message', self, self.user_id, self.ts, self.original_text)
return True
elif key == keymap['go_to_profile']:
urwid.emit_signal(self, 'go_to_profile', self.user_id)
return True
elif key == keymap['go_to_sidebar'] or key == keymap['cursor_left']:
urwid.emit_signal(self, 'go_to_sidebar')
return True
elif key == keymap['quit_application']:
urwid.emit_signal(self, 'quit_application')
return True
elif key == keymap['set_insert_mode']:
urwid.emit_signal(self, 'set_insert_mode')
return True
elif key == keymap['yank_message']:
try:
pyperclip.copy(self.original_text)
except pyperclip.PyperclipException:
pass
return True
elif key == keymap['get_permalink']:
# FIXME
urwid.emit_signal(self, 'get_permalink', self, self.channel_id, self.ts)
elif key == 'enter':
browser_name = Store.instance.config['features']['browser']

for item in self.markdown_text.markup:
type, value = item

if type == 'link' and re.compile(r'^https?://').search(value):
browser_instance = webbrowser if browser_name == '' else webbrowser.get(browser_name)
browser_instance.open(value, new=2)
break

return super(Message, self).keypress(size, key)

def set_text(self, text):
self.text_widget.original_widget = text

def set_edit_mode(self):
self.set_attr_map({
None: 'editing_message',
'message': 'editing_message'
})

def unset_edit_mode(self):
self.set_attr_map({
None: None,
'message': None
})

def selectable(self):
return True

@property
def file(self):
return None

@file.setter
def file(self, file):
self.main_column.contents.insert(self._file_index, (file, ('pack', 1)))
9 changes: 9 additions & 0 deletions sclack/component/time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from datetime import datetime

import urwid


class Time(urwid.Text):
def __init__(self, timestamp):
time = datetime.fromtimestamp(float(timestamp)).strftime('%H:%M')
super(Time, self).__init__(('datetime', ' {} '.format(time)))
110 changes: 0 additions & 110 deletions sclack/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,110 +517,6 @@ def __init__(self, is_edited=False, is_starred=False):
super(Indicators, self).__init__(indicators)


class Message(urwid.AttrMap):
__metaclass__ = urwid.MetaSignals
signals = [
'delete_message',
'edit_message',
'go_to_profile',
'go_to_sidebar',
'quit_application',
'set_insert_mode',
'mark_read',
]

def __init__(self, ts, channel_id, user, text, indicators, reactions=(), attachments=()):
self.ts = ts
self.channel_id = channel_id
self.user_id = user.id
self.markdown_text = text
self.original_text = text.original_text
self.text_widget = urwid.WidgetPlaceholder(text)
main_column = [urwid.Columns([('pack', user), self.text_widget])]
main_column.extend(attachments)
self._file_index = len(main_column)
if reactions:
main_column.append(urwid.Columns([
('pack', reaction) for reaction in reactions
]))
self.main_column = urwid.Pile(main_column)
columns = [
('fixed', 7, Time(ts)),
self.main_column,
('fixed', indicators.size, indicators)
]
self.contents = urwid.Columns(columns)
super(Message, self).__init__(self.contents, None, {
None: 'active_message',
'message': 'active_message'
})

def keypress(self, size, key):
keymap = Store.instance.config['keymap']

if key == keymap['delete_message']:
urwid.emit_signal(self, 'delete_message', self, self.user_id, self.ts)
return True
elif key == keymap['edit_message']:
urwid.emit_signal(self, 'edit_message', self, self.user_id, self.ts, self.original_text)
return True
elif key == keymap['go_to_profile']:
urwid.emit_signal(self, 'go_to_profile', self.user_id)
return True
elif key == keymap['go_to_sidebar'] or key == keymap['cursor_left']:
urwid.emit_signal(self, 'go_to_sidebar')
return True
elif key == keymap['quit_application']:
urwid.emit_signal(self, 'quit_application')
return True
elif key == keymap['set_insert_mode']:
urwid.emit_signal(self, 'set_insert_mode')
return True
elif key == keymap['yank_message']:
try:
pyperclip.copy(self.original_text)
except pyperclip.PyperclipException:
pass
return True
elif key == 'enter':
browser_name = Store.instance.config['features']['browser']

for item in self.markdown_text.markup:
type, value = item

if type == 'link' and re.compile(r'^https?://').search(value):
browser_instance = webbrowser if browser_name == '' else webbrowser.get(browser_name)
browser_instance.open(value, new=2)
break

return super(Message, self).keypress(size, key)

def set_text(self, text):
self.text_widget.original_widget = text

def set_edit_mode(self):
self.set_attr_map({
None: 'editing_message',
'message': 'editing_message'
})

def unset_edit_mode(self):
self.set_attr_map({
None: None,
'message': None
})

def selectable(self):
return True

@property
def file(self):
return None

@file.setter
def file(self, file):
self.main_column.contents.insert(self._file_index, (file, ('pack', 1)))

class MessageBox(urwid.AttrMap):
def __init__(self, user, typing=None, is_read_only=False):
self.read_only_widget = urwid.Text('You have no power here!', align='center')
Expand Down Expand Up @@ -971,12 +867,6 @@ def __init__(self, text='', align='left', char='─'):
]
super(TextDivider, self).__init__(body)


class Time(urwid.Text):
def __init__(self, timestamp):
time = datetime.fromtimestamp(float(timestamp)).strftime('%H:%M')
super(Time, self).__init__(('datetime', ' {} '.format(time)))

def shorten_hex(color):
if color.startswith('#'):
color = color[1:]
Expand Down
5 changes: 1 addition & 4 deletions sclack/quick_switcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
import time
import unicodedata
from .store import Store


def get_icon(name):
return Store.instance.config['icons'][name]
from sclack.components import get_icon


def remove_diacritic(input):
Expand Down
4 changes: 4 additions & 0 deletions sclack/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ def mark_read(self, channel_id, ts):
elif self.is_dm(channel_id):
return self.slack.api_call('im.mark', channel=channel_id, ts=ts)

def get_permalink(self, channel_id, ts):
# https://api.slack.com/methods/chat.getPermalink
return self.slack.api_call('chat.getPermalink', channel=channel_id, message_ts=ts)

def set_snooze(self, snoozed_time):
return self.slack.api_call('dnd.setSnooze', num_minutes=snoozed_time)

Expand Down
6 changes: 1 addition & 5 deletions sclack/widgets/set_snooze.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import urwid
from sclack.store import Store


def get_icon(name):
return Store.instance.config['icons'][name]
from sclack.components import get_icon


class SetSnoozeWidgetItem(urwid.AttrMap):
Expand Down