@verygoodplugins/mcp-freescout 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +2 -1
- package/README.md +95 -16
- package/dist/freescout-api.d.ts +4 -0
- package/dist/freescout-api.d.ts.map +1 -1
- package/dist/freescout-api.js +119 -1
- package/dist/freescout-api.js.map +1 -1
- package/package.json +1 -1
- package/test-markdown.js +150 -0
package/README.md
CHANGED
|
@@ -143,7 +143,9 @@ Fetch a FreeScout ticket with all its details and conversation threads.
|
|
|
143
143
|
}
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
**Example: Fetching a FreeScout ticket with conversation threads**
|
|
147
|
+
|
|
148
|
+

|
|
147
149
|
|
|
148
150
|
|
|
149
151
|
#### `freescout_analyze_ticket`
|
|
@@ -160,6 +162,10 @@ Analyze a ticket to determine issue type, root cause, and suggested solutions.
|
|
|
160
162
|
- Root cause analysis
|
|
161
163
|
- Bug vs feature request vs third-party issue determination
|
|
162
164
|
|
|
165
|
+
**Example: Intelligent ticket analysis with issue classification**
|
|
166
|
+
|
|
167
|
+

|
|
168
|
+
|
|
163
169
|
#### `freescout_add_note`
|
|
164
170
|
Add an internal note to a ticket for team communication.
|
|
165
171
|
|
|
@@ -168,8 +174,6 @@ Add an internal note to a ticket for team communication.
|
|
|
168
174
|
- `note` (required): The note content
|
|
169
175
|
- `userId` (optional): User ID for the note (defaults to env setting)
|
|
170
176
|
|
|
171
|
-
<img width="570" height="691" alt="image" src="https://github.com/user-attachments/assets/19080021-1f29-45a4-8601-556b55d379c3" />
|
|
172
|
-
|
|
173
177
|
#### `freescout_update_ticket`
|
|
174
178
|
Update ticket status and/or assignment.
|
|
175
179
|
|
|
@@ -178,15 +182,44 @@ Update ticket status and/or assignment.
|
|
|
178
182
|
- `status` (optional): New status ('active', 'pending', 'closed', 'spam')
|
|
179
183
|
- `assignTo` (optional): User ID to assign the ticket to
|
|
180
184
|
|
|
181
|
-
#### `
|
|
182
|
-
|
|
185
|
+
#### `freescout_create_draft_reply`
|
|
186
|
+
Create a draft reply in FreeScout that can be edited before sending. This tool lets the LLM generate the reply content and saves it directly to FreeScout as a draft. **Automatically converts Markdown formatting to HTML** for proper display in FreeScout.
|
|
187
|
+
|
|
188
|
+
**Parameters:**
|
|
189
|
+
- `ticket` (required): Ticket ID, number, or FreeScout URL
|
|
190
|
+
- `replyText` (required): The draft reply content (generated by the LLM, supports Markdown formatting)
|
|
191
|
+
- `userId` (optional): User ID creating the draft (defaults to env setting)
|
|
192
|
+
|
|
193
|
+
**Markdown Support:**
|
|
194
|
+
- **Bold text**: `**text**` or `__text__` → **text**
|
|
195
|
+
- *Italic text*: `*text*` or `_text_` → *text*
|
|
196
|
+
- `Code`: `` `code` `` → `code`
|
|
197
|
+
- Numbered lists: `1. item` → proper ordered lists
|
|
198
|
+
- Bullet lists: `- item` or `* item` → proper unordered lists
|
|
199
|
+
- Line breaks: Double newlines create paragraphs, single newlines create line breaks
|
|
200
|
+
|
|
201
|
+
**Workflow:**
|
|
202
|
+
1. Use `freescout_get_ticket_context` to get customer info and ticket details
|
|
203
|
+
2. Let the LLM craft a personalized reply using Markdown formatting
|
|
204
|
+
3. Use `freescout_create_draft_reply` to save the draft in FreeScout (Markdown automatically converted to HTML)
|
|
205
|
+
4. Review and edit the draft in FreeScout before sending
|
|
206
|
+
|
|
207
|
+
**Example: Draft reply workflow with personalized customer response**
|
|
208
|
+
|
|
209
|
+

|
|
210
|
+
|
|
211
|
+
#### `freescout_get_ticket_context`
|
|
212
|
+
Get ticket context and customer information to help craft personalized replies.
|
|
183
213
|
|
|
184
214
|
**Parameters:**
|
|
185
215
|
- `ticket` (required): Ticket ID, number, or FreeScout URL
|
|
186
|
-
- `fixDescription` (optional): Description of the implemented fix
|
|
187
|
-
- `isExplanatory` (optional): Whether this is an explanatory reply (no code changes)
|
|
188
216
|
|
|
189
|
-
|
|
217
|
+
**Returns:**
|
|
218
|
+
- Customer name and email
|
|
219
|
+
- Ticket subject and status
|
|
220
|
+
- Issue description and analysis
|
|
221
|
+
- Recent customer and team messages
|
|
222
|
+
- Analysis results (bug vs feature vs third-party issue)
|
|
190
223
|
|
|
191
224
|
#### `freescout_search_tickets`
|
|
192
225
|
Search for tickets across your FreeScout instance.
|
|
@@ -280,19 +313,33 @@ Fixes the validation error reported in the checkout process.
|
|
|
280
313
|
ticketId: '12345' // Automatically adds FreeScout link to PR
|
|
281
314
|
});
|
|
282
315
|
|
|
283
|
-
// 3.
|
|
284
|
-
const
|
|
285
|
-
ticket: '12345'
|
|
286
|
-
fixDescription: 'Fixed the validation error in the checkout process'
|
|
316
|
+
// 3. Get ticket context to craft a personalized reply
|
|
317
|
+
const context = await mcp.callTool('freescout_get_ticket_context', {
|
|
318
|
+
ticket: '12345'
|
|
287
319
|
});
|
|
288
320
|
|
|
289
|
-
// 4.
|
|
290
|
-
await mcp.callTool('
|
|
321
|
+
// 4. Create a draft reply directly in FreeScout (LLM generates the content with Markdown)
|
|
322
|
+
await mcp.callTool('freescout_create_draft_reply', {
|
|
291
323
|
ticket: '12345',
|
|
292
|
-
|
|
324
|
+
replyText: `Hi ${context.customer.name},
|
|
325
|
+
|
|
326
|
+
Thank you for working through that validation issue with us! Your detailed report was really helpful.
|
|
327
|
+
|
|
328
|
+
I've just implemented a fix that addresses the checkout validation error you experienced. The fix includes:
|
|
329
|
+
|
|
330
|
+
1. **Improved validation logic** in the checkout process
|
|
331
|
+
2. **Better error handling** for edge cases
|
|
332
|
+
3. **Additional safeguards** to prevent similar issues
|
|
333
|
+
|
|
334
|
+
The fix has been submitted for review and will be included in the next plugin update. You'll receive the update through WordPress's automatic update system.
|
|
335
|
+
|
|
336
|
+
Thanks again for your patience and for helping us improve the plugin!
|
|
337
|
+
|
|
338
|
+
Best regards,
|
|
339
|
+
[Your name]`
|
|
293
340
|
});
|
|
294
341
|
|
|
295
|
-
// 5. Update ticket status and assignment
|
|
342
|
+
// 5. Update ticket status and assignment
|
|
296
343
|
await mcp.callTool('freescout_update_ticket', {
|
|
297
344
|
ticket: '12345',
|
|
298
345
|
status: 'active',
|
|
@@ -305,6 +352,38 @@ await mcp.callTool('git_remove_worktree', {
|
|
|
305
352
|
});
|
|
306
353
|
```
|
|
307
354
|
|
|
355
|
+
### Draft Reply Workflow
|
|
356
|
+
```javascript
|
|
357
|
+
// 1. Get ticket context for personalized reply
|
|
358
|
+
const context = await mcp.callTool('freescout_get_ticket_context', {
|
|
359
|
+
ticket: '34811'
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// 2. Create draft reply in FreeScout (LLM crafts the content)
|
|
363
|
+
await mcp.callTool('freescout_create_draft_reply', {
|
|
364
|
+
ticket: '34811',
|
|
365
|
+
replyText: `Hi ${context.customer.name},
|
|
366
|
+
|
|
367
|
+
Thank you for reporting the HighLevel OAuth authorization issue! Your experience with the EngageBay LiveChat plugin conflict has been really valuable.
|
|
368
|
+
|
|
369
|
+
Based on what we learned from your case, I've added a new plugin conflict detection system to WP Fusion. In the next update (v3.46.7), users will see:
|
|
370
|
+
|
|
371
|
+
🔍 **Plugin Conflict Detection**
|
|
372
|
+
- Automatic detection of known conflicting plugins
|
|
373
|
+
- Warning messages before HighLevel authorization
|
|
374
|
+
- Clear guidance when conflicts are detected
|
|
375
|
+
|
|
376
|
+
This should prevent the confusion you experienced and help other users avoid similar issues.
|
|
377
|
+
|
|
378
|
+
The update should be available within the next few weeks. Thanks for your patience and for helping us improve the plugin!
|
|
379
|
+
|
|
380
|
+
Best regards,
|
|
381
|
+
Jack`
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// The draft is now saved in FreeScout and can be reviewed/edited before sending
|
|
385
|
+
```
|
|
386
|
+
|
|
308
387
|
### Handling Non-Bug Issues
|
|
309
388
|
```javascript
|
|
310
389
|
// For third-party issues or feature requests
|
package/dist/freescout-api.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export declare class FreeScoutAPI {
|
|
|
3
3
|
private baseUrl;
|
|
4
4
|
private apiKey;
|
|
5
5
|
constructor(baseUrl: string, apiKey: string);
|
|
6
|
+
/**
|
|
7
|
+
* Convert Markdown formatting to HTML for FreeScout
|
|
8
|
+
*/
|
|
9
|
+
private markdownToHtml;
|
|
6
10
|
private request;
|
|
7
11
|
getConversation(ticketId: string, includeThreads?: boolean): Promise<FreeScoutConversation>;
|
|
8
12
|
addThread(ticketId: string, type: 'note' | 'message' | 'customer', text: string, userId?: number, state?: 'draft' | 'published'): Promise<FreeScoutThread>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"freescout-api.d.ts","sourceRoot":"","sources":["../src/freescout-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"freescout-api.d.ts","sourceRoot":"","sources":["../src/freescout-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAK3C;;OAEG;IACH,OAAO,CAAC,cAAc;YAgIR,OAAO;IA6Bf,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAc,GAC7B,OAAO,CAAC,qBAAqB,CAAC;IAO3B,SAAS,CACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,EACrC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW,GAC5B,OAAO,CAAC,eAAe,CAAC;IAqBrB,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC;IAMrB,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QACP,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;QAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GACA,OAAO,CAAC,qBAAqB,CAAC;IAQ3B,mBAAmB,CACvB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAC;IAUvD,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQlD,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAoBxC"}
|
package/dist/freescout-api.js
CHANGED
|
@@ -6,6 +6,122 @@ export class FreeScoutAPI {
|
|
|
6
6
|
this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
7
7
|
this.apiKey = apiKey;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Convert Markdown formatting to HTML for FreeScout
|
|
11
|
+
*/
|
|
12
|
+
markdownToHtml(text) {
|
|
13
|
+
// Convert bold text: **text** or __text__ -> <strong>text</strong>
|
|
14
|
+
let html = text
|
|
15
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
16
|
+
.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
|
17
|
+
// Convert italic text: *text* or _text_ -> <em>text</em> (avoid conflicts with bold)
|
|
18
|
+
html = html.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
|
|
19
|
+
html = html.replace(/(?<!_)_([^_]+)_(?!_)/g, '<em>$1</em>');
|
|
20
|
+
// Convert code: `text` -> <code>text</code>
|
|
21
|
+
html = html.replace(/`(.*?)`/g, '<code>$1</code>');
|
|
22
|
+
// Process the entire text to handle lists that span paragraph breaks
|
|
23
|
+
const lines = html.split('\n');
|
|
24
|
+
const processedLines = [];
|
|
25
|
+
let inOrderedList = false;
|
|
26
|
+
let inUnorderedList = false;
|
|
27
|
+
let currentParagraph = [];
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
const trimmedLine = line.trim();
|
|
31
|
+
// Check for empty lines (paragraph breaks)
|
|
32
|
+
if (!trimmedLine) {
|
|
33
|
+
// Check if the next non-empty line is also a list item
|
|
34
|
+
const nextListItemIndex = lines.slice(i + 1).findIndex(nextLine => {
|
|
35
|
+
const nextTrimmed = nextLine.trim();
|
|
36
|
+
return nextTrimmed && (/^\d+\.\s+/.test(nextTrimmed) || /^[-*]\s+/.test(nextTrimmed));
|
|
37
|
+
});
|
|
38
|
+
// If we're in a list and the next item is also a list item, continue the list
|
|
39
|
+
if ((inOrderedList || inUnorderedList) && nextListItemIndex !== -1) {
|
|
40
|
+
continue; // Skip this empty line but keep the list open
|
|
41
|
+
}
|
|
42
|
+
// Close any current lists before starting new paragraph
|
|
43
|
+
if (inOrderedList) {
|
|
44
|
+
processedLines.push('</ol>');
|
|
45
|
+
inOrderedList = false;
|
|
46
|
+
}
|
|
47
|
+
if (inUnorderedList) {
|
|
48
|
+
processedLines.push('</ul>');
|
|
49
|
+
inUnorderedList = false;
|
|
50
|
+
}
|
|
51
|
+
// Process accumulated paragraph
|
|
52
|
+
if (currentParagraph.length > 0) {
|
|
53
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
54
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
55
|
+
currentParagraph = [];
|
|
56
|
+
}
|
|
57
|
+
// Skip extra empty lines
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Check for numbered list items
|
|
61
|
+
if (/^\d+\.\s+/.test(trimmedLine)) {
|
|
62
|
+
// Finish any current paragraph
|
|
63
|
+
if (currentParagraph.length > 0) {
|
|
64
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
65
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
66
|
+
currentParagraph = [];
|
|
67
|
+
}
|
|
68
|
+
if (!inOrderedList) {
|
|
69
|
+
if (inUnorderedList) {
|
|
70
|
+
processedLines.push('</ul>');
|
|
71
|
+
inUnorderedList = false;
|
|
72
|
+
}
|
|
73
|
+
processedLines.push('<ol>');
|
|
74
|
+
inOrderedList = true;
|
|
75
|
+
}
|
|
76
|
+
const content = trimmedLine.replace(/^\d+\.\s+/, '');
|
|
77
|
+
processedLines.push(`<li>${content}</li>`);
|
|
78
|
+
}
|
|
79
|
+
// Check for bullet list items
|
|
80
|
+
else if (/^[-*]\s+/.test(trimmedLine)) {
|
|
81
|
+
// Finish any current paragraph
|
|
82
|
+
if (currentParagraph.length > 0) {
|
|
83
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
84
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
85
|
+
currentParagraph = [];
|
|
86
|
+
}
|
|
87
|
+
if (!inUnorderedList) {
|
|
88
|
+
if (inOrderedList) {
|
|
89
|
+
processedLines.push('</ol>');
|
|
90
|
+
inOrderedList = false;
|
|
91
|
+
}
|
|
92
|
+
processedLines.push('<ul>');
|
|
93
|
+
inUnorderedList = true;
|
|
94
|
+
}
|
|
95
|
+
const content = trimmedLine.replace(/^[-*]\s+/, '');
|
|
96
|
+
processedLines.push(`<li>${content}</li>`);
|
|
97
|
+
}
|
|
98
|
+
// Regular line
|
|
99
|
+
else {
|
|
100
|
+
// Close any lists
|
|
101
|
+
if (inOrderedList) {
|
|
102
|
+
processedLines.push('</ol>');
|
|
103
|
+
inOrderedList = false;
|
|
104
|
+
}
|
|
105
|
+
if (inUnorderedList) {
|
|
106
|
+
processedLines.push('</ul>');
|
|
107
|
+
inUnorderedList = false;
|
|
108
|
+
}
|
|
109
|
+
// Add to current paragraph
|
|
110
|
+
currentParagraph.push(trimmedLine);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Close any remaining lists
|
|
114
|
+
if (inOrderedList)
|
|
115
|
+
processedLines.push('</ol>');
|
|
116
|
+
if (inUnorderedList)
|
|
117
|
+
processedLines.push('</ul>');
|
|
118
|
+
// Process any remaining paragraph
|
|
119
|
+
if (currentParagraph.length > 0) {
|
|
120
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
121
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
122
|
+
}
|
|
123
|
+
return processedLines.join('\n\n');
|
|
124
|
+
}
|
|
9
125
|
async request(path, method = 'GET', body) {
|
|
10
126
|
const url = `${this.baseUrl}/api${path}`;
|
|
11
127
|
const headers = {
|
|
@@ -43,7 +159,9 @@ export class FreeScoutAPI {
|
|
|
43
159
|
return this.request(`/conversations/${ticketId}/threads`, 'POST', body);
|
|
44
160
|
}
|
|
45
161
|
async createDraftReply(ticketId, text, userId) {
|
|
46
|
-
|
|
162
|
+
// Convert Markdown formatting to HTML for proper display in FreeScout
|
|
163
|
+
const htmlText = this.markdownToHtml(text);
|
|
164
|
+
return this.addThread(ticketId, 'message', htmlText, userId, 'draft');
|
|
47
165
|
}
|
|
48
166
|
async updateConversation(ticketId, updates) {
|
|
49
167
|
return this.request(`/conversations/${ticketId}`, 'PUT', updates);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"freescout-api.js","sourceRoot":"","sources":["../src/freescout-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAO/B,MAAM,OAAO,YAAY;IACf,OAAO,CAAS;IAChB,MAAM,CAAS;IAEvB,YAAY,OAAe,EAAE,MAAc;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,wBAAwB;QACnE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,IAAY,EACZ,SAAiB,KAAK,EACtB,IAAU;QAEV,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,OAAO,IAAI,EAAE,CAAC;QAEzC,MAAM,OAAO,GAA2B;YACtC,qBAAqB,EAAE,IAAI,CAAC,MAAM;SACnC,CAAC;QAEF,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC/C,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM;YACN,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9C,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,MAAM,SAAS,EAAE,CAAC,CAAC;QAC5E,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,QAAgB,EAChB,iBAA0B,IAAI;QAE9B,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,CAAC,OAAO,CACjB,kBAAkB,QAAQ,GAAG,KAAK,EAAE,CACrC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CACb,QAAgB,EAChB,IAAqC,EACrC,IAAY,EACZ,MAAe,EACf,KAA6B;QAE7B,MAAM,IAAI,GAAQ;YAChB,IAAI;YACJ,IAAI;SACL,CAAC;QAEF,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC;QACrB,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CACjB,kBAAkB,QAAQ,UAAU,EACpC,MAAM,EACN,IAAI,CACL,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,QAAgB,EAChB,IAAY,EACZ,MAAc;QAEd,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"freescout-api.js","sourceRoot":"","sources":["../src/freescout-api.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/test-markdown.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Quick test of the markdown converter
|
|
2
|
+
const testInput = `Hi Victor,
|
|
3
|
+
|
|
4
|
+
Thanks for helping us identify this issue! Based on your experience with the EngageBay LiveChat plugin conflict, we've implemented a solution to prevent this confusion for future users.
|
|
5
|
+
|
|
6
|
+
**What we've added:**
|
|
7
|
+
|
|
8
|
+
1. **Proactive Warning System** - When users have conflicting plugins installed (like EngageBay LiveChat, LeadConnector, or GHL Connect), WP Fusion will now display a warning message before they attempt OAuth authorization, letting them know these plugins may interfere with the connection process.
|
|
9
|
+
|
|
10
|
+
2. **Enhanced Error Messages** - If the \`invalid_grant\` error does occur, users will now receive specific guidance about potential plugin conflicts rather than the cryptic error message you experienced.
|
|
11
|
+
|
|
12
|
+
This improvement will be included in the next WP Fusion update and should help other users avoid the frustration you encountered. The system will specifically detect the EngageBay LiveChat plugin (and others we discover) and provide clear guidance on temporarily disabling them during the OAuth setup process.
|
|
13
|
+
|
|
14
|
+
Thanks again for your patience in working through this issue and helping us identify the root cause. Your feedback directly led to this improvement that will benefit all WP Fusion users integrating with HighLevel!
|
|
15
|
+
|
|
16
|
+
Best regards,
|
|
17
|
+
Jack`;
|
|
18
|
+
|
|
19
|
+
function markdownToHtml(text) {
|
|
20
|
+
// Convert bold text: **text** or __text__ -> <strong>text</strong>
|
|
21
|
+
let html = text
|
|
22
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
23
|
+
.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
|
24
|
+
|
|
25
|
+
// Convert italic text: *text* or _text_ -> <em>text</em> (avoid conflicts with bold)
|
|
26
|
+
html = html.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
|
|
27
|
+
html = html.replace(/(?<!_)_([^_]+)_(?!_)/g, '<em>$1</em>');
|
|
28
|
+
|
|
29
|
+
// Convert code: `text` -> <code>text</code>
|
|
30
|
+
html = html.replace(/`(.*?)`/g, '<code>$1</code>');
|
|
31
|
+
|
|
32
|
+
// Process the entire text to handle lists that span paragraph breaks
|
|
33
|
+
const lines = html.split('\n');
|
|
34
|
+
const processedLines = [];
|
|
35
|
+
let inOrderedList = false;
|
|
36
|
+
let inUnorderedList = false;
|
|
37
|
+
let currentParagraph = [];
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < lines.length; i++) {
|
|
40
|
+
const line = lines[i];
|
|
41
|
+
const trimmedLine = line.trim();
|
|
42
|
+
|
|
43
|
+
// Check for empty lines (paragraph breaks)
|
|
44
|
+
if (!trimmedLine) {
|
|
45
|
+
// Check if the next non-empty line is also a list item
|
|
46
|
+
const nextListItemIndex = lines.slice(i + 1).findIndex(nextLine => {
|
|
47
|
+
const nextTrimmed = nextLine.trim();
|
|
48
|
+
return nextTrimmed && (/^\d+\.\s+/.test(nextTrimmed) || /^[-*]\s+/.test(nextTrimmed));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// If we're in a list and the next item is also a list item, continue the list
|
|
52
|
+
if ((inOrderedList || inUnorderedList) && nextListItemIndex !== -1) {
|
|
53
|
+
continue; // Skip this empty line but keep the list open
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Close any current lists before starting new paragraph
|
|
57
|
+
if (inOrderedList) {
|
|
58
|
+
processedLines.push('</ol>');
|
|
59
|
+
inOrderedList = false;
|
|
60
|
+
}
|
|
61
|
+
if (inUnorderedList) {
|
|
62
|
+
processedLines.push('</ul>');
|
|
63
|
+
inUnorderedList = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Process accumulated paragraph
|
|
67
|
+
if (currentParagraph.length > 0) {
|
|
68
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
69
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
70
|
+
currentParagraph = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Skip extra empty lines
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for numbered list items
|
|
78
|
+
if (/^\d+\.\s+/.test(trimmedLine)) {
|
|
79
|
+
// Finish any current paragraph
|
|
80
|
+
if (currentParagraph.length > 0) {
|
|
81
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
82
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
83
|
+
currentParagraph = [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!inOrderedList) {
|
|
87
|
+
if (inUnorderedList) {
|
|
88
|
+
processedLines.push('</ul>');
|
|
89
|
+
inUnorderedList = false;
|
|
90
|
+
}
|
|
91
|
+
processedLines.push('<ol>');
|
|
92
|
+
inOrderedList = true;
|
|
93
|
+
}
|
|
94
|
+
const content = trimmedLine.replace(/^\d+\.\s+/, '');
|
|
95
|
+
processedLines.push(`<li>${content}</li>`);
|
|
96
|
+
}
|
|
97
|
+
// Check for bullet list items
|
|
98
|
+
else if (/^[-*]\s+/.test(trimmedLine)) {
|
|
99
|
+
// Finish any current paragraph
|
|
100
|
+
if (currentParagraph.length > 0) {
|
|
101
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
102
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
103
|
+
currentParagraph = [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!inUnorderedList) {
|
|
107
|
+
if (inOrderedList) {
|
|
108
|
+
processedLines.push('</ol>');
|
|
109
|
+
inOrderedList = false;
|
|
110
|
+
}
|
|
111
|
+
processedLines.push('<ul>');
|
|
112
|
+
inUnorderedList = true;
|
|
113
|
+
}
|
|
114
|
+
const content = trimmedLine.replace(/^[-*]\s+/, '');
|
|
115
|
+
processedLines.push(`<li>${content}</li>`);
|
|
116
|
+
}
|
|
117
|
+
// Regular line
|
|
118
|
+
else {
|
|
119
|
+
// Close any lists
|
|
120
|
+
if (inOrderedList) {
|
|
121
|
+
processedLines.push('</ol>');
|
|
122
|
+
inOrderedList = false;
|
|
123
|
+
}
|
|
124
|
+
if (inUnorderedList) {
|
|
125
|
+
processedLines.push('</ul>');
|
|
126
|
+
inUnorderedList = false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add to current paragraph
|
|
130
|
+
currentParagraph.push(trimmedLine);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Close any remaining lists
|
|
135
|
+
if (inOrderedList) processedLines.push('</ol>');
|
|
136
|
+
if (inUnorderedList) processedLines.push('</ul>');
|
|
137
|
+
|
|
138
|
+
// Process any remaining paragraph
|
|
139
|
+
if (currentParagraph.length > 0) {
|
|
140
|
+
const paragraphContent = currentParagraph.join('<br>');
|
|
141
|
+
processedLines.push(`<p>${paragraphContent}</p>`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return processedLines.join('\n\n');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log('=== INPUT ===');
|
|
148
|
+
console.log(testInput);
|
|
149
|
+
console.log('\n=== OUTPUT ===');
|
|
150
|
+
console.log(markdownToHtml(testInput));
|