Skip to content

Commit 22bfce7

Browse files
committed
add event editing
1 parent c9c214c commit 22bfce7

File tree

8 files changed

+185
-20
lines changed

8 files changed

+185
-20
lines changed

events/buttons/edit_event.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from slack_sdk import WebClient
2+
3+
from views.edit_event import get_edit_event_modal
4+
5+
from typing import Any, Callable
6+
7+
8+
def handle_edit_event_btn(ack: Callable, body: dict[str, Any], client: WebClient):
9+
ack()
10+
value = body["actions"][0]["value"]
11+
client.views_open(
12+
view=get_edit_event_modal(value), trigger_id=body["trigger_id"]
13+
)

events/views/create_event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def handle_create_event_view(ack: Callable, body: dict[str, Any], client: WebCli
2727
host_pfp = user["user"]["profile"]["image_192"]
2828

2929
event = env.airtable.create_event(
30-
title[0], md, start_time[0], end_time[0], location, host_id, host_name, host_pfp
30+
title[0], md, description, start_time[0], end_time[0], location, host_id, host_name, host_pfp
3131
)
3232
if not event:
3333
client.chat_postEphemeral(

events/views/edit_event.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from typing import Any, Callable
2+
from slack_sdk import WebClient
3+
from datetime import datetime, timezone
4+
5+
from utils.env import env
6+
from utils.utils import rich_text_to_md, md_to_mrkdwn
7+
from views.app_home import get_home
8+
9+
import json
10+
11+
12+
def handle_edit_event_view(ack: Callable, body: dict[str, Any], client: WebClient):
13+
ack()
14+
view = body["view"]
15+
values = view["state"]["values"]
16+
title = (values["title"]["title"]["value"],)
17+
description = values["description"]["description"]["rich_text_value"]["elements"]
18+
md = rich_text_to_md(description)
19+
start_time = (values["start_time"]["start_time"]["selected_date_time"],)
20+
end_time = (values["end_time"]["end_time"]["selected_date_time"],)
21+
host_id = values["host"]["host"]["selected_user"]
22+
location = (
23+
values.get("location", {}).get("location", {}).get("value")
24+
or "https://hackclub.slack.com/archives/C07TNAZGMHS"
25+
)
26+
27+
user = client.users_info(user=host_id)
28+
host_name = user["user"]["real_name"]
29+
host_pfp = user["user"]["profile"]["image_192"]
30+
31+
raw_description_string = json.dumps({
32+
"type": "rich_text",
33+
"elements": description,
34+
})
35+
36+
event = env.airtable.update_event(
37+
id=body["view"]["private_metadata"],
38+
**{
39+
"Title": title[0],
40+
"Description": md,
41+
"Raw Description": raw_description_string,
42+
"Start Time": datetime.fromtimestamp(start_time[0], timezone.utc).isoformat(),
43+
"End Time": datetime.fromtimestamp(end_time[0], timezone.utc).isoformat(),
44+
"Event Link": location,
45+
"Leader Slack ID": host_id,
46+
"Leader": host_name,
47+
"Avatar": [{"url": host_pfp}],
48+
}
49+
)
50+
if not event:
51+
client.chat_postEphemeral(
52+
user=body["user"]["id"],
53+
channel=body["user"]["id"],
54+
text=f'An error occurred whilst creating the event "{title[0]}".',
55+
)
56+
57+
fallback_start_time = datetime.fromtimestamp(
58+
start_time[0], timezone.utc
59+
).isoformat()
60+
fallback_end_time = datetime.fromtimestamp(end_time[0], timezone.utc).isoformat()
61+
62+
user_id = body.get("user", {}).get("id", "")
63+
host_mention = f"for <@{host_id}>" if host_id != user_id else ""
64+
host_str = f"<@{user_id}> {host_mention}"
65+
mrkdwn = md_to_mrkdwn(md)
66+
client.chat_postMessage(
67+
channel=env.slack_approval_channel,
68+
text=f"New event request by <@{body['user']['id']}>!\nTitle: {title[0]}\nDescription: {mrkdwn}\nStart Time: {start_time[0]}\nEnd Time: {end_time[0]}",
69+
blocks=[
70+
{
71+
"type": "section",
72+
"text": {
73+
"type": "mrkdwn",
74+
"text": f"New event request by {host_str}!\n*Title:* {title[0]}\n*Description:* {mrkdwn}\n*Start Time (local time):* <!date^{start_time[0]}^{{date_num}} at {{time_secs}}|{fallback_start_time}>\n*End Time (local time):* <!date^{end_time[0]}^{{date_num}} at {{time_secs}}|{fallback_end_time}>",
75+
},
76+
}
77+
],
78+
)
79+
80+
client.views_publish(user_id=user_id, view=get_home(user_id, client))

utils/airtable.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from pyairtable import Api
22
from datetime import datetime, timezone
3-
3+
import json
44

55
class AirtableManager:
66
def __init__(self, api_key: str, base_id: str):
@@ -12,18 +12,24 @@ def __init__(self, api_key: str, base_id: str):
1212
def create_event(
1313
self,
1414
title: str,
15-
description: str,
15+
md_description: str,
16+
raw_description: list,
1617
start_time: str,
1718
end_time: str,
1819
location: str,
1920
host_id: str,
2021
host_name: str,
2122
host_pfp: str,
2223
):
24+
raw_description_string = json.dumps({
25+
"type": "rich_text",
26+
"elements": raw_description,
27+
})
2328
event = self.events_table.create(
2429
{
2530
"Title": title,
26-
"Description": description,
31+
"Description": md_description,
32+
"Raw Description": raw_description_string,
2733
"Start Time": datetime.fromtimestamp(
2834
start_time, timezone.utc
2935
).isoformat(),

utils/slack.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
from .env import env
55
from events.buttons.create_event import handle_create_event_btn
6+
from events.buttons.edit_event import handle_edit_event_btn
67
from events.commands.create_event import handle_create_event_cmd
78
from events.views.create_event import handle_create_event_view
9+
from events.views.edit_event import handle_edit_event_view
810
from events.buttons.propose_event import handle_propose_event_btn
911
from events.buttons.approve_event import handle_approve_event_btn
1012
from events.buttons.rsvp import handle_rsvp_btn
@@ -26,6 +28,10 @@ def create_event(ack: Callable, body: dict[str, Any], client: WebClient):
2628
def create_event_view(ack: Callable, body: dict[str, Any], client: WebClient):
2729
handle_create_event_view(ack, body, client)
2830

31+
@app.view("edit_event")
32+
def edit_event_view(ack: Callable, body: dict[str, Any], client: WebClient):
33+
handle_edit_event_view(ack, body, client)
34+
2935

3036
@app.action("approve-event")
3137
def approve_event(ack: Callable, body: dict[str, Any], client: WebClient):
@@ -44,6 +50,11 @@ def create_event(ack: Callable, body: dict[str, Any], client: WebClient):
4450
handle_create_event_btn(ack, body, client)
4551

4652

53+
@app.action("edit-event")
54+
def edit_event(ack: Callable, body: dict[str, Any], client: WebClient):
55+
handle_edit_event_btn(ack, body, client)
56+
57+
4758
@app.action("propose-event")
4859
def create_event(ack: Callable, body: dict[str, Any], client: WebClient):
4960
handle_propose_event_btn(ack, body, client)

utils/utils.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,54 @@ def rich_text_to_md(input_data, indent_level=0, in_quote=False):
5959
return markdown
6060

6161

62+
def md_to_rich_text(md):
63+
rich_text = []
64+
65+
# Convert code blocks
66+
code_block_pattern = re.compile(r'```(.*?)```', re.DOTALL)
67+
md = code_block_pattern.sub(lambda m: rich_text.append({"type": "rich_text_preformatted", "elements": [{"type": "text", "text": m.group(1)}]}) or "", md)
68+
69+
# Convert blockquotes
70+
blockquote_pattern = re.compile(r'^> (.*)', re.MULTILINE)
71+
md = blockquote_pattern.sub(lambda m: rich_text.append({"type": "rich_text_quote", "elements": [{"type": "text", "text": m.group(1)}]}) or "", md)
72+
73+
# Convert unordered lists
74+
unordered_list_pattern = re.compile(r'^\s*-\s+(.*)', re.MULTILINE)
75+
md = unordered_list_pattern.sub(lambda m: rich_text.append({"type": "rich_text_list", "style": "bullet", "elements": [{"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1)}]}]}) or "", md)
76+
77+
# Convert ordered lists
78+
ordered_list_pattern = re.compile(r'^\s*\d+\.\s+(.*)', re.MULTILINE)
79+
md = ordered_list_pattern.sub(lambda m: rich_text.append({"type": "rich_text_list", "style": "numbered", "elements": [{"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1)}]}]}) or "", md)
80+
81+
# Convert inline code
82+
inline_code_pattern = re.compile(r'`(.*?)`')
83+
md = inline_code_pattern.sub(lambda m: rich_text.append({"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1), "style": {"code": True}}]}) or "", md)
84+
85+
# Convert bold and italic text
86+
bold_italic_pattern = re.compile(r'\*\*\*(.*?)\*\*\*')
87+
md = bold_italic_pattern.sub(lambda m: rich_text.append({"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1), "style": {"bold": True, "italic": True}}]}) or "", md)
88+
89+
bold_pattern = re.compile(r'\*\*(.*?)\*\*')
90+
md = bold_pattern.sub(lambda m: rich_text.append({"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1), "style": {"bold": True}}]}) or "", md)
91+
92+
italic_pattern = re.compile(r'\*(.*?)\*')
93+
md = italic_pattern.sub(lambda m: rich_text.append({"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1), "style": {"italic": True}}]}) or "", md)
94+
95+
# Convert strikethrough text
96+
strikethrough_pattern = re.compile(r'~~(.*?)~~')
97+
md = strikethrough_pattern.sub(lambda m: rich_text.append({"type": "rich_text_section", "elements": [{"type": "text", "text": m.group(1), "style": {"strike": True}}]}) or "", md)
98+
99+
# Convert links
100+
link_pattern = re.compile(r'\[(.*?)\]\((.*?)\)')
101+
md = link_pattern.sub(lambda m: rich_text.append({"type": "rich_text_section", "elements": [{"type": "link", "url": m.group(2), "text": m.group(1)}]}) or "", md)
102+
103+
# Convert plain text
104+
if md.strip():
105+
rich_text.append({"type": "rich_text_section", "elements": [{"type": "text", "text": md.strip()}]})
106+
107+
return rich_text
108+
109+
62110
def md_to_mrkdwn(md):
63111
# Convert bold and italic text (bold first to avoid conflicts)
64112
md = re.sub(r"\*\*\*(.*?)\*\*\*", r"***\1***", md) # Bold and italic

views/app_home.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def get_home(user_id: str, client: WebClient):
8989
{
9090
"type": "button",
9191
"text": {"type": "plain_text", "text": "Edit", "emoji": True},
92-
"value": "edit-event",
92+
"value": event['id'],
9393
"action_id": "edit-event",
9494
}
9595
)
@@ -148,7 +148,7 @@ def get_home(user_id: str, client: WebClient):
148148
{
149149
"type": "button",
150150
"text": {"type": "plain_text", "text": "Edit", "emoji": True},
151-
"value": "edit-event",
151+
"value": event['id'],
152152
"action_id": "edit-event",
153153
}
154154
)

views/edit_event.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from utils.env import env
2-
2+
from datetime import datetime
3+
import json
34

45
def get_edit_event_modal(event_id: str):
56
event = env.airtable.get_event(event_id)
7+
raw_desc = json.loads(event["fields"]["Raw Description"])
68
return {
79
"type": "modal",
8-
"callback_id": "create_event",
10+
"callback_id": "edit_event",
11+
"private_metadata": event_id,
912
"title": {"type": "plain_text", "text": "Add Event", "emoji": True},
1013
"submit": {"type": "plain_text", "text": "Submit", "emoji": True},
1114
"close": {"type": "plain_text", "text": "Cancel", "emoji": True},
@@ -21,38 +24,42 @@ def get_edit_event_modal(event_id: str):
2124
{
2225
"type": "input",
2326
"block_id": "title",
24-
"element": {"type": "plain_text_input", "action_id": "title"},
27+
"element": {
28+
"type": "plain_text_input",
29+
"action_id": "title",
30+
"initial_value": event["fields"]["Title"]
31+
},
2532
"label": {"type": "plain_text", "text": "Title", "emoji": True},
26-
"initial_value": event["fields"]["Name"],
2733
},
2834
{
2935
"type": "input",
3036
"block_id": "description",
3137
"element": {
3238
"type": "rich_text_input",
3339
"action_id": "description",
34-
"dispatch_action_config": {
35-
"trigger_actions_on": ["on_character_entered"]
36-
},
37-
"focus_on_load": False,
38-
"placeholder": {"type": "plain_text", "text": "Enter text"},
40+
"initial_value": raw_desc,
3941
},
4042
"label": {"type": "plain_text", "text": "Description", "emoji": True},
41-
"initial_value": event["fields"]["Description"],
4243
},
4344
{
4445
"type": "input",
4546
"block_id": "start_time",
46-
"element": {"type": "datetimepicker", "action_id": "start_time"},
47+
"element": {
48+
"type": "datetimepicker",
49+
"action_id": "start_time",
50+
"initial_date_time": datetime.fromisoformat(event["fields"]["Start Time"]).timestamp(),
51+
},
4752
"label": {"type": "plain_text", "text": "Start Time", "emoji": True},
48-
"initial_date": event["fields"]["Start Time"],
4953
},
5054
{
5155
"type": "input",
5256
"block_id": "end_time",
53-
"element": {"type": "datetimepicker", "action_id": "end_time"},
57+
"element": {
58+
"type": "datetimepicker",
59+
"action_id": "end_time",
60+
"initial_date_time": datetime.fromisoformat(event["fields"]["End Time"]).timestamp()
61+
},
5462
"label": {"type": "plain_text", "text": "End Time", "emoji": True},
55-
"initial_date": event["fields"]["End Time"],
5663
},
5764
{
5865
"type": "input",

0 commit comments

Comments
 (0)