diff --git a/README.md b/README.md index 03df752..2e851cb 100644 --- a/README.md +++ b/README.md @@ -103,24 +103,30 @@ You can use ctrl k (or your custom shortcut) to navigate in your DMs You can use ctrl d (or your custom shortcut) to set snooze time. +### Get permalink + +Focus on message and press r (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" } } ``` diff --git a/app.py b/app.py index 494cbac..c655c1a 100755 --- a/app.py +++ b/app.py @@ -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 @@ -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']: @@ -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) diff --git a/config.json b/config.json index 0ce592d..8f7c87a 100644 --- a/config.json +++ b/config.json @@ -16,6 +16,7 @@ "set_edit_topic_mode": "t", "set_insert_mode": "i", "yank_message": "y", + "get_permalink": "r", "set_snooze": "ctrl d" }, "sidebar": { diff --git a/sclack/component/message.py b/sclack/component/message.py new file mode 100644 index 0000000..1422dcd --- /dev/null +++ b/sclack/component/message.py @@ -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))) diff --git a/sclack/component/time.py b/sclack/component/time.py new file mode 100644 index 0000000..70eed44 --- /dev/null +++ b/sclack/component/time.py @@ -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))) diff --git a/sclack/components.py b/sclack/components.py index 8f6e461..9a2c369 100644 --- a/sclack/components.py +++ b/sclack/components.py @@ -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') @@ -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:] diff --git a/sclack/quick_switcher.py b/sclack/quick_switcher.py index 0c0a563..2af09df 100644 --- a/sclack/quick_switcher.py +++ b/sclack/quick_switcher.py @@ -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): diff --git a/sclack/store.py b/sclack/store.py index 77f688a..eec697f 100644 --- a/sclack/store.py +++ b/sclack/store.py @@ -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) diff --git a/sclack/widgets/set_snooze.py b/sclack/widgets/set_snooze.py index d089d33..abb9817 100644 --- a/sclack/widgets/set_snooze.py +++ b/sclack/widgets/set_snooze.py @@ -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):