@@ -4,7 +4,7 @@ import { createIntegration, EventCallback } from '@gitbook/runtime';
44import { SlackRuntimeContext } from './configuration' ;
55import { handleFetchEvent } from './router' ;
66import { slackAPI } from './slack' ;
7- import { Spacer } from './ui ' ;
7+ import { SlackBlock , SlackButtonElement } from './types ' ;
88
99/*
1010 * Handle content being updated: send a notification on Slack.
@@ -41,29 +41,6 @@ const handleSpaceContentUpdated: EventCallback<
4141 return ;
4242 }
4343
44- /*
45- * Build a notification that looks something like this:
46- *
47- * Content of *Space* has been updated.
48- *
49- * [Changes were merged from change request: #123 - My Change Request]
50- *
51- * *New pages:*
52- * • Page 7
53- * • Page 8
54- * • Page 9
55- *
56- * *Modified pages:*
57- * • Page 1
58- * • Page 2
59- *
60- * *New files:*
61- * • File 1
62- * • File 2
63- *
64- * And another X changes not listed here.
65- */
66-
6744 const createdPages : ChangedRevisionPage [ ] = [ ] ;
6845 const editedPages : ChangedRevisionPage [ ] = [ ] ;
6946 const deletedPages : ChangedRevisionPage [ ] = [ ] ;
@@ -100,75 +77,173 @@ const handleSpaceContentUpdated: EventCallback<
10077 }
10178 } ) ;
10279
103- let notificationText = `Content of *<${ space . urls . app } |${
104- space . title || 'Space'
105- } >* has been updated.\n\n`;
80+ const MAX_ITEMS_TO_SHOW = 5 ;
10681
107- if ( semanticChanges . mergedFrom ) {
108- const changeRequest = semanticChanges . mergedFrom ;
109- notificationText += `_Changes were merged from change request: <${ changeRequest . urls . app } |#${ changeRequest . number } - ${ changeRequest . subject } >_\n\n` ;
110- }
82+ const createChangeSection = (
83+ title : string ,
84+ items : string [ ] | ChangedRevisionPage [ ] ,
85+ emoji : string ,
86+ ) : SlackBlock [ ] => {
87+ if ( items . length === 0 ) return [ ] ;
11188
112- const renderList = ( list : string [ ] ) => {
113- return list . map ( ( item ) => `• ${ item } \n` ) . join ( '' ) ;
114- } ;
89+ const displayItems = items . slice ( 0 , MAX_ITEMS_TO_SHOW ) ;
90+ const remainingCount = items . length - displayItems . length ;
11591
116- const renderPageList = ( list : ChangedRevisionPage [ ] ) => {
117- return list . map ( ( item ) => `• <${ space . urls . app } ${ item . path } |${ item . title } >\n` ) . join ( '' ) ;
118- } ;
92+ let text = `${ emoji } *${ title } *\n` ;
93+ displayItems . forEach ( ( item ) => {
94+ if ( typeof item === 'string' ) {
95+ text += `• ${ item } \n` ;
96+ } else {
97+ // Page object with URL
98+ const pageUrl = `${ space . urls . published || space . urls . app } ${ item . path || '' } ` ;
99+ text += `• <${ pageUrl } |${ item . title } >\n` ;
100+ }
101+ } ) ;
119102
120- if (
121- createdPages . length > 0 ||
122- editedPages . length > 0 ||
123- deletedPages . length > 0 ||
124- movedPages . length > 0 ||
125- createdFiles . length > 0 ||
126- editedFiles . length > 0 ||
127- deletedFiles . length > 0
128- ) {
129- if ( createdPages . length > 0 ) {
130- notificationText += `\n*New pages:*\n${ renderPageList ( createdPages ) } \n\n` ;
131- }
132- if ( editedPages . length > 0 ) {
133- notificationText += `\n*Modified pages:*\n${ renderPageList ( editedPages ) } \n\n` ;
134- }
135- if ( deletedPages . length > 0 ) {
136- notificationText += `\n*Deleted pages:*\n${ renderPageList ( deletedPages ) } \n\n` ;
137- }
138- if ( movedPages . length > 0 ) {
139- notificationText += `\n*Moved pages:*\n${ renderPageList ( movedPages ) } \n\n` ;
140- }
141- if ( createdFiles . length > 0 ) {
142- notificationText += `\n*New files:*\n${ renderList ( createdFiles ) } \n\n` ;
143- }
144- if ( editedFiles . length > 0 ) {
145- notificationText += `\n*Modified files:*\n${ renderList ( editedFiles ) } \n\n` ;
146- }
147- if ( deletedFiles . length > 0 ) {
148- notificationText += `\n*Deleted files:*\n${ renderList ( deletedFiles ) } \n\n` ;
103+ if ( remainingCount > 0 ) {
104+ text += `_and ${ remainingCount } more..._` ;
149105 }
150106
151- if ( semanticChanges . more && semanticChanges . more > 0 ) {
152- notificationText += `\n\nAnd another ${ semanticChanges . more } changes not listed here.\n` ;
107+ return [
108+ {
109+ type : 'section' ,
110+ text : {
111+ type : 'mrkdwn' ,
112+ text : text . trim ( ) ,
113+ } ,
114+ } ,
115+ ] ;
116+ } ;
117+
118+ const getEmojiFromUnicode = ( unicodeHex : string ) => {
119+ if ( ! unicodeHex ) return '✨' ;
120+ try {
121+ // Convert hex string to actual emoji
122+ const codePoint = parseInt ( unicodeHex , 16 ) ;
123+ return String . fromCodePoint ( codePoint ) ;
124+ } catch {
125+ return '✨' ;
153126 }
127+ } ;
128+
129+ const spaceEmoji = space . emoji ? getEmojiFromUnicode ( space . emoji ) : '✨' ;
130+
131+ const blocks : SlackBlock [ ] = [
132+ {
133+ type : 'header' ,
134+ text : {
135+ type : 'plain_text' ,
136+ text : `${ spaceEmoji } ${ space . title || 'Space' } Updated` ,
137+ emoji : true ,
138+ } ,
139+ } ,
140+ ] ;
141+
142+ let changeRequest ;
143+ if ( semanticChanges . mergedFrom ) {
144+ changeRequest = semanticChanges . mergedFrom ;
145+ blocks . push ( {
146+ type : 'context' ,
147+ elements : [
148+ {
149+ type : 'mrkdwn' ,
150+ text : `🔀 Changes were merged from change request: <${ changeRequest . urls . app } |#${ changeRequest . number } ${ changeRequest . subject ? ` - ${ changeRequest . subject } ` : '' } >` ,
151+ } ,
152+ ] ,
153+ } ) ;
154154 }
155155
156+ blocks . push ( {
157+ type : 'section' ,
158+ text : {
159+ type : 'mrkdwn' ,
160+ text : '*Summary of Changes:*' ,
161+ } ,
162+ } ) ;
163+
164+ blocks . push ( ...createChangeSection ( 'New pages' , createdPages , '🆕' ) ) ;
165+ blocks . push ( ...createChangeSection ( 'Modified pages' , editedPages , '📝' ) ) ;
166+ blocks . push ( ...createChangeSection ( 'Deleted pages' , deletedPages , '🗑️' ) ) ;
167+ blocks . push ( ...createChangeSection ( 'Moved pages' , movedPages , '📁' ) ) ;
168+ blocks . push ( ...createChangeSection ( 'New files' , createdFiles , '📄' ) ) ;
169+ blocks . push ( ...createChangeSection ( 'Modified files' , editedFiles , '📝' ) ) ;
170+ blocks . push ( ...createChangeSection ( 'Deleted files' , deletedFiles , '🗂️' ) ) ;
171+
172+ if ( semanticChanges . more && semanticChanges . more > 0 ) {
173+ blocks . push ( {
174+ type : 'context' ,
175+ elements : [
176+ {
177+ type : 'mrkdwn' ,
178+ text : `➕ _And ${ semanticChanges . more } additional changes not listed here_` ,
179+ } ,
180+ ] ,
181+ } ) ;
182+ }
183+
184+ const actionButtons : SlackButtonElement [ ] = [
185+ {
186+ type : 'button' ,
187+ text : {
188+ type : 'plain_text' ,
189+ text : '🏠 View Main Space' ,
190+ emoji : true ,
191+ } ,
192+ url : space . urls . app ,
193+ action_id : 'view_main_space' ,
194+ style : 'primary' ,
195+ } ,
196+ ] ;
197+
198+ if ( space . urls . published ) {
199+ actionButtons . push ( {
200+ type : 'button' ,
201+ text : {
202+ type : 'plain_text' ,
203+ text : '🌐 View Docs Site' ,
204+ emoji : true ,
205+ } ,
206+ url : space . urls . published ,
207+ action_id : 'view_docs_site' ,
208+ } ) ;
209+ }
210+
211+ blocks . push ( { type : 'divider' } ) ;
212+
213+ // Add a nice footer section
214+ const totalChanges =
215+ createdPages . length +
216+ editedPages . length +
217+ deletedPages . length +
218+ movedPages . length +
219+ createdFiles . length +
220+ editedFiles . length +
221+ deletedFiles . length +
222+ ( semanticChanges . more || 0 ) ;
223+
224+ blocks . push ( {
225+ type : 'context' ,
226+ elements : [
227+ {
228+ type : 'mrkdwn' ,
229+ text : `📊 *${ totalChanges } total changes* • Updated just now` ,
230+ } ,
231+ ] ,
232+ } ) ;
233+
234+ blocks . push ( {
235+ type : 'actions' ,
236+ elements : actionButtons ,
237+ } ) ;
238+
156239 await slackAPI ( context , {
157240 method : 'POST' ,
158241 path : 'chat.postMessage' ,
159242 payload : {
160243 channel,
161- blocks : [
162- Spacer ,
163- {
164- type : 'section' ,
165- text : {
166- type : 'mrkdwn' ,
167- text : notificationText ,
168- } ,
169- } ,
170- Spacer ,
171- ] ,
244+ blocks,
245+ unfurl_links : false ,
246+ unfurl_media : false ,
172247 } ,
173248 } ) ;
174249} ;
0 commit comments