diff --git a/package.json b/package.json
index e4dbad2..0bc5ffc 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"@angular/platform-browser-dynamic": "^21.0.8",
"@angular/router": "^21.0.8",
"@microsoft/fetch-event-source": "^2.0.1",
- "@tinymce/tinymce-angular": "9.1.2-rc.20260115041456592.sha0aa5d93",
+ "@tinymce/tinymce-angular": "^9.1.1",
"angular-cli-ghpages": "^2.0.3",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 2167df1..92bf786 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -4,7 +4,10 @@ import { generateConfig } from './configs/config';
import { full } from './snippets/snippets';
-const key = 'prsghhxax677rv082a1zj9b7cgjuoaqysf7h8ayxi5ao43ha';
+const params = {
+ jwtServerURL: 'https://demo.api.tiny.cloud',
+ apiKey: 'prsghhxax677rv082a1zj9b7cgjuoaqysf7h8ayxi5ao43ha'
+};
@Component({
selector: 'app-root',
@@ -40,8 +43,8 @@ export class AppComponent {
}
public configurations = {
- classicConf: generateConfig({ excludePlugins: ['tinydrive', 'uploadcare']}),
- inlineConf: generateConfig({ excludePlugins: ['tinydrive', 'editimage', 'image' ], overrides: { inline: true }})
+ classicConf: generateConfig(params, { excludePlugins: ['tinydrive', 'uploadcare']}),
+ inlineConf: generateConfig(params, { excludePlugins: ['tinydrive', 'editimage', 'image' ], overrideConfig: { inline: true }})
}
public channels = [
@@ -62,7 +65,7 @@ export class AppComponent {
template: `
{{ title }}
-
+
`,
standalone: false
@@ -75,7 +78,7 @@ export class TinyComponent {
public init: any = {};
public initialValue = '';
- public apiKey = key;
+ public apiKey = params.apiKey;
public ngOnInit(): void {
console.log('loading inner', this.channel);
@@ -85,48 +88,6 @@ export class TinyComponent {
}
}
-const configWrapRe = /^\s*\(\s*function\s*\(\s*\)\s*\{\s*return\s*([\s\S]*);\s*\}\s*\)\s*\(\s*\)\s*;\s*$/;
-
-/**
- * Escape text to make HTML.
- * @param {string} text text to escape as HTML.
- * @returns the text with special characters escaped.
- */
-const escapeHtml = (text: any) => {
- return text.replaceAll(/&/g, '&').replaceAll(//g, '>').replaceAll(/"/g, '"').replaceAll(/'/g, ''');
-}
-
-/**
- * Unwraps a config.
- * @param {string} config a config with a function wrapping it to make it easy to eval.
- * @returns {string} the unwrapped config.
- */
-const unwrapConfig = (config: string) => {
- const m = configWrapRe.exec(config);
- return m !== null ? m[1] : config;
-}
-
-/**
- * Process a snippet and insert the contained variables.
- * @param {string} snippet the HTML snippet with possible comment variables.
- * @param {string} title the title.
- * @param {string} config the config.
- * @returns {string} the snippet with variables inserted.
- * Not too sure what this is doing, snippet is working fine without it.
- */
-const replaceSnippetVars = (snippet: any, title: string, config: string) => {
- return snippet.replaceAll(//g, function (match: any, name: any) {
- if (name === 'title') {
- return escapeHtml(title);
- } else if (name === 'init') {
- return `<Editor init={${escapeHtml(unwrapConfig(config))}}\n/>`;
- } else {
- console.warn('Unknown variable', match);
- return match;
- }
- });
-};
-
/**
* Convet a config into the init parameter
* @param {string} stringConf config to parse
@@ -134,7 +95,6 @@ const replaceSnippetVars = (snippet: any, title: string, config: string) => {
*/
const initFromConf = (conf: any) => {
let next = {};
- // console.log('parsing: ', conf);
if (typeof conf === 'object') {
next = {...conf}
} else {
diff --git a/src/app/configs/a11ychecker.ts b/src/app/configs/a11ychecker.ts
new file mode 100644
index 0000000..75f805b
--- /dev/null
+++ b/src/app/configs/a11ychecker.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'a11ychecker',
+ config: {},
+ toolbar: 'a11ycheck'
+};
diff --git a/src/app/configs/accordion.ts b/src/app/configs/accordion.ts
new file mode 100644
index 0000000..4858f06
--- /dev/null
+++ b/src/app/configs/accordion.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'accordion',
+ toolbar: 'accordion',
+ config: {}
+};
diff --git a/src/app/configs/advcode.ts b/src/app/configs/advcode.ts
new file mode 100644
index 0000000..aaf10ce
--- /dev/null
+++ b/src/app/configs/advcode.ts
@@ -0,0 +1,6 @@
+export default {
+ name: 'advcode',
+ config: {
+ advcode_inline: true
+ }
+};
diff --git a/src/app/configs/advlist.ts b/src/app/configs/advlist.ts
new file mode 100644
index 0000000..d01cba3
--- /dev/null
+++ b/src/app/configs/advlist.ts
@@ -0,0 +1,27 @@
+export default {
+ toolbar: 'numlist bullist',
+ name: 'advlist',
+ config: {
+ advlist_number_styles: "default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman",
+ advlist_bullet_styles: "default,circle,disc,square",
+ },
+ // Code samples for each configuration option:
+ //
+ // advlist_number_styles
+ // Specifies the number styles available in the list style dropdown
+ // - default: Standard numbering (1, 2, 3...)
+ // - lower-alpha: Lowercase letters (a, b, c...)
+ // - lower-greek: Lowercase Greek letters (α, β, γ...)
+ // - lower-roman: Lowercase Roman numerals (i, ii, iii...)
+ // - upper-alpha: Uppercase letters (A, B, C...)
+ // - upper-roman: Uppercase Roman numerals (I, II, III...)
+ // Usage: advlist_number_styles: "default,lower-alpha,lower-roman"
+ //
+ // advlist_bullet_styles
+ // Specifies the bullet styles available in the list style dropdown
+ // - default: Standard bullet point (•)
+ // - circle: Open circle (○)
+ // - disc: Filled circle/disc (●)
+ // - square: Square bullet (■)
+ // Usage: advlist_bullet_styles: "default,circle,square"
+};
\ No newline at end of file
diff --git a/src/app/configs/advtable.ts b/src/app/configs/advtable.ts
new file mode 100644
index 0000000..c6946cc
--- /dev/null
+++ b/src/app/configs/advtable.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'advtable',
+ config: {}
+};
diff --git a/src/app/configs/advtemplate.ts b/src/app/configs/advtemplate.ts
new file mode 100644
index 0000000..d14c9a5
--- /dev/null
+++ b/src/app/configs/advtemplate.ts
@@ -0,0 +1,74 @@
+const advtemplate_templates = [
+ {
+ id: '1',
+ title: 'Quick replies',
+ items: [
+ {
+ id: '2',
+ title: 'Message received',
+ content: 'Hey {{Customer.FirstName}}!
\nJust a quick note to say we’ve received your message, and will get back to you within 48 hours.
\nFor reference, your ticket number is: {{Ticket.Number}}
\nShould you have any questions in the meantime, just reply to this email and it will be attached to this ticket.
\n
\nRegards,
\n{{Agent.FirstName}}
'
+ },
+ {
+ id: '3',
+ title: 'Thanks for the feedback',
+ content: 'Hi {{Customer.FirstName}},
\nWe appreciate you taking the time to provide feedback on {{Product.Name}}.
\nIt sounds like it wasn’t able to fully meet your expectations, for which we apologize. Rest assured our team looks at each piece of feedback and uses it to decide what to focus on next with {{Product.Name}}.
\n
\nAll the best, and let us know if there’s anything else we can do to help.
\n-{{Agent.FirstName}}
'
+ },
+ {
+ id: '6',
+ title: 'Still working on case',
+ content: '
\n
\nHi {{Customer.FirstName}},
\nJust a quick note to let you know we’re still working on your case. It’s taking a bit longer than we hoped, but we’re aiming to get you an answer in the next 48 hours.
\nStay tuned,
\n{{Agent.FirstName}}
'
+ }
+ ]
+ },
+ {
+ id: '4',
+ title: 'Closing tickets',
+ items: [
+ {
+ id: '7',
+ title: 'Closing ticket',
+ content: 'Hi {{Customer.FirstName}},
\nWe haven’t heard back from you in over a week, so we have gone ahead and closed your ticket number {{Ticket.Number}}.
\nIf you’re still running into issues, not to worry, just reply to this email and we will re-open your ticket.
\n
\nAll the best,
\n{{Agent.FirstName}}
'
+ },
+ {
+ id: '8',
+ title: 'Post-call survey',
+ content: 'Hey {{Customer.FirstName}}!
\n
\nHow did we do?
\nIf you have a few moments, we’d love you to fill out our post-support survey: {{Survey.Link}}
\n
\nThanks in advance!
{{Company.Name}} Customer Support
'
+ }
+ ]
+ },
+ {
+ id: '5',
+ title: 'Product support',
+ items: [
+ {
+ id: '9',
+ title: 'How to find model number',
+ content: 'Hi {{Customer.FirstName}},
\n
\nMy name is {{Agent.FirstName}} and I will be glad to assist you today.
\nTo troubleshoot your issue, we first need your model number, which can be found on the underside of your product beneath the safety warning label.
\nIt should look something like the following: XX.XXXXX.X
\nOnce you send it over, I will advise on next steps.
\n
\nThanks!
\n{{Agent.FirstName}}
'
+ },
+ {
+ id: '10',
+ title: 'Support escalation',
+ content: '
\n
\nHi {{Customer.FirstName}},
\nWe have escalated your ticket {{Ticket.Number}} to second-level support.
\nYou should hear back from the new agent on your case, {{NewAgent.FirstName}}, shortly.
\n
\nThanks,
\n{{Company.Name}} Customer Support
'
+ }
+ ]
+ },
+ {
+ id: '6',
+ title: 'Email Signatures',
+ items: [
+ {
+ id: '11',
+ title: 'Tiny Signature',
+ content: 'Regards

Shiridi Gandham
QA Template Manager
Email: shiridi.gandham@tiny.cloud
Phone: (+617) 3161 3557
Tiny Technologies
www.tiny.cloud

'
+ }
+ ]
+ }
+ ];
+
+export default {
+ toolbar: 'addtemplate inserttemplate',
+ name: 'advtemplate',
+ config: {
+ advtemplate_templates
+ }
+};
\ No newline at end of file
diff --git a/src/app/configs/ai.ts b/src/app/configs/ai.ts
new file mode 100644
index 0000000..f54314c
--- /dev/null
+++ b/src/app/configs/ai.ts
@@ -0,0 +1,246 @@
+// import { fetchEventSource } from '@sentool/fetch-event-source';
+import { fetchEventSource } from '@microsoft/fetch-event-source';
+
+// const ai_request_backup = (request: any, respondWith: any) => {
+// respondWith.stream((signal, streamMessage) => {
+// // Adds each previous query and response as individual messages
+// const conversation = request.thread.flatMap((event) => {
+// if (event.response) {
+// return [
+// { role: 'user', content: event.request.query },
+// { role: 'assistant', content: event.response.data }
+// ];
+// } else {
+// return [];
+// }
+// });
+
+// // System messages provided by the plugin to format the output as HTML content.
+// const systemMessages = request.system.map((content) => ({
+// role: 'system',
+// content
+// }));
+
+// // Forms the new query sent to the API
+// const content = request.context.length === 0 || conversation.length > 0
+// ? request.query
+// : `Question: ${request.query} Context: """${request.context}"""`;
+
+// const messages = [
+// ...conversation,
+// ...systemMessages,
+// { role: 'user', content }
+// ];
+
+// let hasHead = false;
+// let markdownHead = "";
+
+// const hasMarkdown = (message) => {
+// if (message.includes("`") && markdownHead !== "```") {
+// const numBackticks = message.split("`").length - 1;
+// markdownHead += "`".repeat(numBackticks);
+// if (hasHead && markdownHead === "```") {
+// markdownHead = "";
+// hasHead = false;
+// }
+// return true;
+// } else if (message.includes("html") && markdownHead === "```") {
+// markdownHead = "";
+// hasHead = true;
+// return true;
+// }
+// return false;
+// };
+
+// const requestBody = {
+// model: 'gpt-4o',
+// temperature: 0.7,
+// max_tokens: 4000,
+// messages,
+// stream: true
+// };
+
+// const openAiOptions = {
+// signal,
+// method: 'POST',
+// headers: {
+// 'Content-Type': 'application/json',
+// },
+// body: JSON.stringify(requestBody)
+// };
+
+// const onopen = async (response) => {
+// if (response) {
+// const contentType = response.headers.get('content-type');
+// if (response.ok && contentType?.includes('text/event-stream')) {
+// return;
+// } else if (contentType?.includes('application/json')) {
+// const data = await response.json();
+// if (data.error) {
+// throw new Error(`${data.error.type}: ${data.error.message}`);
+// }
+// }
+// } else {
+// throw new Error('Failed to communicate with the ChatGPT API');
+// }
+// };
+
+// // This function passes each new message into the plugin via the `streamMessage` callback.
+// const onmessage = (ev) => {
+// const data = ev.data;
+// if (data !== '[DONE]') {
+// const parsedData = JSON.parse(data);
+// const firstChoice = parsedData?.choices[0];
+// const message = firstChoice?.delta?.content;
+// if (message && message !== "") {
+// if (!hasMarkdown(message)) {
+// streamMessage(message);
+// }
+// }
+// }
+// };
+
+// const onerror = (error) => {
+// // Stop operation and do not retry by the fetch-event-source
+// console.log('an error', { error })
+// console.trace();
+// throw error;
+// };
+
+// return fetchEventSource('https://openai-dev-proxy.tiny.work/v1/chat/completions', {
+// ...openAiOptions,
+// openWhenHidden: true,
+// parseJson: false,
+// onopen,
+// onmessage,
+// onerror,
+// }
+// ).catch(onerror);
+// });
+// };
+
+
+const ai_request = (request: any, respondWith: any) => {
+ respondWith.stream((signal: any, streamMessage: any) => {
+ // Adds each previous query and response as individual messages
+ const conversation = request.thread.flatMap((event: any) => {
+ if (event.response) {
+ return [
+ { role: 'user', content: event.request.query },
+ { role: 'assistant', content: event.response.data }
+ ];
+ } else {
+ return [];
+ }
+ });
+
+ // System messages provided by the plugin to format the output as HTML content.
+ const systemMessages = request.system.map((content: string) => ({
+ role: 'system',
+ content
+ }));
+
+ // Forms the new query sent to the API
+ const content = request.context.length === 0 || conversation.length > 0
+ ? request.query
+ : `Question: ${request.query} Context: """${request.context}"""`;
+
+ const messages = [
+ ...conversation,
+ ...systemMessages,
+ { role: 'user', content }
+ ];
+
+ let hasHead = false;
+ let markdownHead = "";
+
+ const hasMarkdown = (message: string) => {
+ if (message.includes("`") && markdownHead !== "```") {
+ const numBackticks = message.split("`").length - 1;
+ markdownHead += "`".repeat(numBackticks);
+ if (hasHead && markdownHead === "```") {
+ markdownHead = "";
+ hasHead = false;
+ }
+ return true;
+ } else if (message.includes("html") && markdownHead === "```") {
+ markdownHead = "";
+ hasHead = true;
+ return true;
+ }
+ return false;
+ };
+
+ const requestBody = {
+ model: 'gpt-4o',
+ temperature: 0.7,
+ max_tokens: 4000,
+ messages,
+ stream: true
+ };
+
+ const openAiOptions = {
+ signal,
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(requestBody)
+ };
+
+ const onopen = async (response: any) => {
+ if (response) {
+ const contentType = response.headers.get('content-type');
+ if (response.ok && contentType?.includes('text/event-stream')) {
+ return;
+ } else if (contentType?.includes('application/json')) {
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(`${data.error.type}: ${data.error.message}`);
+ }
+ }
+ } else {
+ throw new Error('Failed to communicate with the ChatGPT API');
+ }
+ };
+
+ // This function passes each new message into the plugin via the `streamMessage` callback.
+ const onmessage = (ev: any) => {
+ const data = ev.data;
+ if (data !== '[DONE]') {
+ const parsedData = JSON.parse(data);
+ const firstChoice = parsedData?.choices[0];
+ const message = firstChoice?.delta?.content;
+ if (message && message !== "") {
+ if (!hasMarkdown(message)) {
+ streamMessage(message);
+ }
+ }
+ }
+ };
+
+ const onerror = (error: any) => {
+ // Stop operation and do not retry by the fetch-event-source
+ console.log('an error', { error })
+ console.trace();
+ throw error;
+ };
+
+ return fetchEventSource('https://openai-dev-proxy.tiny.work/v1/chat/completions', {
+ ...openAiOptions,
+ openWhenHidden: true,
+ onopen,
+ onmessage,
+ onerror,
+ }
+ ).catch(onerror);
+ });
+};
+
+
+export default {
+ name: 'ai',
+ config: {
+ ai_request
+ }
+};
diff --git a/src/app/configs/anchor.ts b/src/app/configs/anchor.ts
new file mode 100644
index 0000000..612b0c1
--- /dev/null
+++ b/src/app/configs/anchor.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'anchor',
+ toolbar: 'anchor',
+ config: {}
+};
diff --git a/src/app/configs/autocorrect.ts b/src/app/configs/autocorrect.ts
new file mode 100644
index 0000000..ccea807
--- /dev/null
+++ b/src/app/configs/autocorrect.ts
@@ -0,0 +1,7 @@
+export default {
+ name: 'autocorrect',
+ config: {
+ autocorrect_autocorrect: true,
+ autocorrect_capitalize: true,
+ }
+};
diff --git a/src/app/configs/autolink.ts b/src/app/configs/autolink.ts
new file mode 100644
index 0000000..e3ea7bf
--- /dev/null
+++ b/src/app/configs/autolink.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'autolink',
+ config: {}
+};
diff --git a/src/app/configs/autosave.ts b/src/app/configs/autosave.ts
new file mode 100644
index 0000000..d5b3db4
--- /dev/null
+++ b/src/app/configs/autosave.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'autosave',
+ config: {}
+};
diff --git a/src/app/configs/casechange.ts b/src/app/configs/casechange.ts
new file mode 100644
index 0000000..9f0e802
--- /dev/null
+++ b/src/app/configs/casechange.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'casechange',
+ toolbar: 'casechange',
+ config: {}
+};
diff --git a/src/app/configs/charmap.ts b/src/app/configs/charmap.ts
new file mode 100644
index 0000000..6b05c1c
--- /dev/null
+++ b/src/app/configs/charmap.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'charmap',
+ toolbar: 'charmap',
+ config: {}
+};
diff --git a/src/app/configs/checklist.ts b/src/app/configs/checklist.ts
new file mode 100644
index 0000000..5f622f6
--- /dev/null
+++ b/src/app/configs/checklist.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'checklist',
+ toolbar: 'checklist',
+ config: {}
+};
diff --git a/src/app/configs/code.ts b/src/app/configs/code.ts
new file mode 100644
index 0000000..b4fb4bc
--- /dev/null
+++ b/src/app/configs/code.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'code',
+ toolbar: 'code',
+ config: {}
+};
diff --git a/src/app/configs/codesample.ts b/src/app/configs/codesample.ts
new file mode 100644
index 0000000..44f7e9d
--- /dev/null
+++ b/src/app/configs/codesample.ts
@@ -0,0 +1,18 @@
+export default {
+ name: 'codesample',
+ toolbar: 'codesample',
+ config: {
+ codesample_languages: [
+ { text: 'HTML/XML', value: 'markup' },
+ { text: 'JavaScript', value: 'javascript' },
+ { text: 'CSS', value: 'css' },
+ { text: 'PHP', value: 'php' },
+ { text: 'Ruby', value: 'ruby' },
+ { text: 'Python', value: 'python' },
+ { text: 'Java', value: 'java' },
+ { text: 'C', value: 'c' },
+ { text: 'C#####', value: 'csharpppppp' },
+ { text: 'C++', value: 'cpp' }
+ ],
+ }
+};
\ No newline at end of file
diff --git a/src/app/configs/config.ts b/src/app/configs/config.ts
index eebbe86..2a920ff 100644
--- a/src/app/configs/config.ts
+++ b/src/app/configs/config.ts
@@ -1,1338 +1,172 @@
-import { fetchEventSource } from '@microsoft/fetch-event-source';
-import type { Editor } from 'tinymce';
-
- const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';
-
- const advtemplate_templates = [
- {
- id: '1',
- title: 'Quick replies',
- items: [
- {
- id: '2',
- title: 'Message received',
- content: 'Hey {{Customer.FirstName}}!
\nJust a quick note to say we’ve received your message, and will get back to you within 48 hours.
\nFor reference, your ticket number is: {{Ticket.Number}}
\nShould you have any questions in the meantime, just reply to this email and it will be attached to this ticket.
\n
\nRegards,
\n{{Agent.FirstName}}
'
- },
- {
- id: '3',
- title: 'Thanks for the feedback',
- content: 'Hi {{Customer.FirstName}},
\nWe appreciate you taking the time to provide feedback on {{Product.Name}}.
\nIt sounds like it wasn’t able to fully meet your expectations, for which we apologize. Rest assured our team looks at each piece of feedback and uses it to decide what to focus on next with {{Product.Name}}.
\n
\nAll the best, and let us know if there’s anything else we can do to help.
\n-{{Agent.FirstName}}
'
- },
- {
- id: '6',
- title: 'Still working on case',
- content: '
\n
\nHi {{Customer.FirstName}},
\nJust a quick note to let you know we’re still working on your case. It’s taking a bit longer than we hoped, but we’re aiming to get you an answer in the next 48 hours.
\nStay tuned,
\n{{Agent.FirstName}}
'
- }
- ]
- },
- {
- id: '4',
- title: 'Closing tickets',
- items: [
- {
- id: '7',
- title: 'Closing ticket',
- content: 'Hi {{Customer.FirstName}},
\nWe haven’t heard back from you in over a week, so we have gone ahead and closed your ticket number {{Ticket.Number}}.
\nIf you’re still running into issues, not to worry, just reply to this email and we will re-open your ticket.
\n
\nAll the best,
\n{{Agent.FirstName}}
'
- },
- {
- id: '8',
- title: 'Post-call survey',
- content: 'Hey {{Customer.FirstName}}!
\n
\nHow did we do?
\nIf you have a few moments, we’d love you to fill out our post-support survey: {{Survey.Link}}
\n
\nThanks in advance!
{{Company.Name}} Customer Support
'
- }
- ]
- },
- {
- id: '5',
- title: 'Product support',
- items: [
- {
- id: '9',
- title: 'How to find model number',
- content: 'Hi {{Customer.FirstName}},
\n
\nMy name is {{Agent.FirstName}} and I will be glad to assist you today.
\nTo troubleshoot your issue, we first need your model number, which can be found on the underside of your product beneath the safety warning label.
\nIt should look something like the following: XX.XXXXX.X
\nOnce you send it over, I will advise on next steps.
\n
\nThanks!
\n{{Agent.FirstName}}
'
- },
- {
- id: '10',
- title: 'Support escalation',
- content: '
\n
\nHi {{Customer.FirstName}},
\nWe have escalated your ticket {{Ticket.Number}} to second-level support.
\nYou should hear back from the new agent on your case, {{NewAgent.FirstName}}, shortly.
\n
\nThanks,
\n{{Company.Name}} Customer Support
'
- }
- ]
- },
- {
- id: '6',
- title: 'Email Signatures',
- items: [
- {
- id: '11',
- title: 'Tiny Signature',
- content: 'Regards

Shiridi Gandham
QA Template Manager
Email: shiridi.gandham@tiny.cloud
Phone: (+617) 3161 3557
Tiny Technologies
www.tiny.cloud

'
- }
- ]
- }
- ];
-
- const mergetags_list = [
- {
- value: 'Current.Date',
- title: 'Current date in DD/MM/YYYY format'
- },
- {
- value: 'Campaign.Toc',
- title: 'Linked table of contents in your campaign'
- },
- {
- title: 'Phone',
- menu: [
- {
- value: 'Phone.Home'
- },
- {
- value: 'Phone.work'
- }
- ]
- },
- {
- title: 'Person',
- menu: [
- {
- value: 'Person.Name'
- },
- {
- value: 'Person.Name.First'
- },
- {
- value: 'Person.Name.Last'
- },
- {
- value: 'Person.Name.Full'
- },
- {
- title: 'Email',
- menu: [
- {
- value: 'Person.Email.Work'
- },
- {
- value: 'Person.Email.Home'
- }
- ]
- }
- ]
- }
- ];
-
- interface UserInfo {
- id: string;
- name: string;
- avatar?: string;
- custom?: any;
- }
-
- const mentions_fetch = async (query: any, success: any) => {
- const searchPhrase = query.term.toLowerCase();
- await fetch(`${API_URL}?q=${encodeURIComponent(searchPhrase)}`)
- .then((response) => response.json())
- .then((users) => success(users.data.map((userInfo: UserInfo) => ({
- id: userInfo.id,
- name: userInfo.name,
- image: userInfo.avatar,
- description: userInfo.custom?.role
- }))))
- .catch((error) => console.log(error));
- };
-
- const createCard = (userInfo: any) => {
- const div = document.createElement('div');
- div.innerHTML = (
- '' +
- '

' +
- '
' + userInfo.name + '
' +
- '
' + userInfo.description + '
' +
- '
'
- );
- return div;
- };
-
- const mentions_menu_complete = (editor: Editor, userInfo: UserInfo) => {
- const span = editor.getDoc().createElement('span');
- span.className = 'mymention';
- span.setAttribute('data-mention-id', userInfo.id);
- span.appendChild(editor.getDoc().createTextNode('@' + userInfo.name));
- return span;
- };
-
- const mentions_menu_hover = async (userInfo: UserInfo, success: Function) => {
- const card = createCard(userInfo);
- success(card);
- };
-
- const mentions_select = async (mention: any, success: Function) => {
- const id = mention.getAttribute('data-mention-id');
- await fetch(`${API_URL}/${id}`)
+import AdvtemplateConfig from './advtemplate';
+import AccordionConfig from './accordion';
+import MergetagsConfig from './mergetags';
+import MentionsConfig from './mentions';
+import TinyCommentsConfig from './tinycomments';
+import AdvlistConfig from './advlist';
+import SuggestedEditConfig from './suggestededits';
+import CodeSampleConfig from './codesample';
+import ExportWordConfig from './exportword';
+import InsertDatetimeConfig from './insertdatetime';
+import AnchorConfig from './anchor';
+import AutolinkConfig from './autolink';
+import AutosaveConfig from './autosave';
+import CharmapConfig from './charmap';
+import CodeConfig from './code';
+import EditimageConfig from './editimage';
+import DirectionalityConfig from './directionality';
+import EmoticonsConfig from './emoticons';
+import FullscreenConfig from './fullscreen';
+import HelpConfig from './help';
+import ImageConfig from './image';
+import ImportcssConfig from './importcss';
+import LinkConfig from './link';
+import ListsConfig from './lists';
+import MediaConfig from './media';
+import NonbreakingConfig from './nonbreaking';
+import PagebreakConfig from './pagebreak';
+import PreviewConfig from './preview';
+import QuickbarsConfig from './quickbars';
+import SaveConfig from './save';
+import SearchreplaceConfig from './searchreplace';
+import TableConfig from './table';
+import VisualblocksConfig from './visualblocks';
+import VisualcharsConfig from './visualchars';
+import WordcountConfig from './wordcount';
+import A11ycheckerConfig from './a11ychecker';
+import AdvcodeConfig from './advcode';
+import AdvtableConfig from './advtable';
+import AutocorrectConfig from './autocorrect';
+import CasechangeConfig from './casechange';
+import ChecklistConfig from './checklist';
+import EditimageConfig2 from './editimage2';
+import ExportpdfConfig from './exportpdf';
+import FootnotesConfig from './footnotes';
+import FormatpainterConfig from './formatpainter';
+import ImportwordConfig from './importword';
+import InlinecssConfig from './inlinecss';
+import LinkcheckerConfig from './linkchecker';
+import MarkdownConfig from './markdown';
+import MathConfig from './math';
+import MediaembedConfig from './mediaembed';
+import PageembedConfig from './pageembed';
+import PermanentpenConfig from './permanentpen';
+import PowerpasteConfig from './powerpaste';
+import RevisionhistoryConfig from './revisionhistory';
+import TableofcontentsConfig from './tableofcontents';
+import TinymceaiConfig from './tinymceai';
+import TinymcespellcheckerConfig from './tinymcespellchecker';
+import TypographyConfig from './typography';
+import UploadcareConfig from './uploadcare';
+import { Config, Options, Params, PluginConfig } from './types';
+
+const pluginConfigs: PluginConfig[] = [
+ AccordionConfig,
+ CodeSampleConfig,
+ AdvlistConfig,
+ AnchorConfig,
+ AutolinkConfig,
+ AutosaveConfig,
+ CharmapConfig,
+ CodeConfig,
+ EditimageConfig,
+ DirectionalityConfig,
+ EmoticonsConfig,
+ FullscreenConfig,
+ HelpConfig,
+ ImageConfig,
+ ImportcssConfig,
+ InsertDatetimeConfig,
+ LinkConfig,
+ ListsConfig,
+ MediaConfig,
+ NonbreakingConfig,
+ PagebreakConfig,
+ PreviewConfig,
+ QuickbarsConfig,
+ SaveConfig,
+ SearchreplaceConfig,
+ TableConfig,
+ VisualblocksConfig,
+ VisualcharsConfig,
+ WordcountConfig,
+ A11ycheckerConfig,
+ AdvcodeConfig,
+ AdvtableConfig,
+ AdvtemplateConfig,
+ AutocorrectConfig,
+ CasechangeConfig,
+ ChecklistConfig,
+ EditimageConfig2,
+ ExportpdfConfig,
+ ExportWordConfig,
+ FootnotesConfig,
+ FormatpainterConfig,
+ ImportwordConfig,
+ InlinecssConfig,
+ LinkcheckerConfig,
+ MarkdownConfig,
+ MathConfig,
+ MediaembedConfig,
+ MentionsConfig,
+ MergetagsConfig,
+ PageembedConfig,
+ PermanentpenConfig,
+ PowerpasteConfig,
+ RevisionhistoryConfig,
+ SuggestedEditConfig,
+ TableofcontentsConfig,
+ TinyCommentsConfig,
+ TinymcespellcheckerConfig,
+ TypographyConfig,
+ UploadcareConfig,
+ TinymceaiConfig,
+];
+
+const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';
+
+const user_id = 'james-wilson';
+const basicConfig = {
+ height: 600,
+ mobile: {
+ theme: "silver",
+ contextmenu: "link image table preview",
+ },
+ pad_empty_with_br: true,
+ help_accessibility: true,
+ // TODO: Target for tinymce 8
+ user_id,
+ fetch_users: (userIds: string[]) =>
+ Promise.all(userIds.map((userId) => fetch(`${API_URL}/${userId}`)
.then((response) => response.json())
- .then((userInfo) => {
- const card = createCard({
- id: userInfo.id,
- name: userInfo.name,
- image: userInfo.avatar,
- description: userInfo.custom.role
- });
- success(card);
- })
- .catch((error) => console.error(error));
- };
-
- const ai_request = (request: any, respondWith: any) => {
- respondWith.stream((signal: any, streamMessage: any) => {
- // Adds each previous query and response as individual messages
- const conversation = request.thread.flatMap((event: any) => {
- if (event.response) {
- return [
- { role: 'user', content: event.request.query },
- { role: 'assistant', content: event.response.data }
- ];
- } else {
- return [];
- }
- });
-
- // System messages provided by the plugin to format the output as HTML content.
- const systemMessages = request.system.map((content: string) => ({
- role: 'system',
- content
- }));
-
- // Forms the new query sent to the API
- const content = request.context.length === 0 || conversation.length > 0
- ? request.query
- : `Question: ${request.query} Context: """${request.context}"""`;
-
- const messages = [
- ...conversation,
- ...systemMessages,
- { role: 'user', content }
- ];
-
- let hasHead = false;
- let markdownHead = "";
-
- const hasMarkdown = (message: string) => {
- if (message.includes("`") && markdownHead !== "```") {
- const numBackticks = message.split("`").length - 1;
- markdownHead += "`".repeat(numBackticks);
- if (hasHead && markdownHead === "```") {
- markdownHead = "";
- hasHead = false;
- }
- return true;
- } else if (message.includes("html") && markdownHead === "```") {
- markdownHead = "";
- hasHead = true;
- return true;
- }
- return false;
- };
-
- const requestBody = {
- model: 'gpt-4o',
- temperature: 0.7,
- max_tokens: 4000,
- messages,
- stream: true
- };
-
- const openAiOptions = {
- signal,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(requestBody)
- };
-
- const onopen = async (response: any) => {
- if (response) {
- const contentType = response.headers.get('content-type');
- if (response.ok && contentType?.includes('text/event-stream')) {
- return;
- } else if (contentType?.includes('application/json')) {
- const data = await response.json();
- if (data.error) {
- throw new Error(`${data.error.type}: ${data.error.message}`);
- }
- }
- } else {
- throw new Error('Failed to communicate with the ChatGPT API');
- }
- };
-
- // This function passes each new message into the plugin via the `streamMessage` callback.
- const onmessage = (ev: any) => {
- const data = ev.data;
- if (data !== '[DONE]') {
- const parsedData = JSON.parse(data);
- const firstChoice = parsedData?.choices[0];
- const message = firstChoice?.delta?.content;
- if (message && message !== "") {
- if (!hasMarkdown(message)) {
- streamMessage(message);
- }
- }
- }
- };
-
- const onerror = (error: any) => {
- // Stop operation and do not retry by the fetch-event-source
- console.log('an error', { error })
- console.trace();
- throw error;
- };
-
- return fetchEventSource('https://openai-dev-proxy.tiny.work/v1/chat/completions', {
- ...openAiOptions,
- openWhenHidden: true,
- onopen,
- onmessage,
- onerror,
- }
- ).catch(onerror);
- });
- };
-
- const user_id: string = 'james-wilson';
- const collaborator_id: string = 'mia-andersson';
-
- const tinycomments_create = (req: any, done: Function, fail: Function) => {
- if (req.content === 'fail') {
- fail(new Error('Something has gone wrong...'));
- } else {
- const uid = 'annotation-' + randomString();
- conversationDb[uid] = {
- uid,
- comments: [{
- uid,
- author: user_id,
- authorName: 'James Wilson',
- authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
- content: req.content,
- createdAt: req.createdAt,
- modifiedAt: req.createdAt
- }]
- };
- setTimeout(() => done({ conversationUid: uid }), fakeDelay);
- }
- };
-
- const fakeDelay = 200;
-
- const randomString = () => {
- return crypto.getRandomValues(new Uint32Array(1))[0].toString(36).substring(2, 14);
- };
-
-
- const conversationDb: Record = {}
-
- const tinycomments_reply = (req: any, done: Function) => {
- const replyUid = 'annotation-' + randomString();
- conversationDb[req.conversationUid].comments.push({
- uid: replyUid,
- author: user_id,
- authorName: 'James Wilson',
- authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
- content: req.content,
- createdAt: req.createdAt,
- modifiedAt: req.createdAt
- });
- setTimeout(() => done({ commentUid: replyUid }), fakeDelay);
- };
-
- const tinycomments_delete = (req: any, done: Function) => {
- if (user_id === collaborator_id) { // Replace wth your own logic, e.g. check if user created the conversation
- delete conversationDb[req.conversationUid];
- setTimeout(() => done({ canDelete: true }), fakeDelay);
- } else {
- setTimeout(() => done({ canDelete: false, reason: 'Must be admin user' }), fakeDelay);
- }
- };
-
- const tinycomments_resolve = (req: any, done: Function) => {
- const conversation = conversationDb[req.conversationUid];
- if (user_id === conversation.comments[0].author) { // Replace wth your own logic, e.g. check if user has admin priveleges
- delete conversationDb[req.conversationUid];
- setTimeout(() => done({ canResolve: true }), fakeDelay);
- } else {
- setTimeout(() => done({ canResolve: false, reason: 'Must be conversation author' }), fakeDelay);
- }
- };
-
- const tinycomments_delete_comment = (req: any, done: Function) => {
- const oldcomments = conversationDb[req.conversationUid].comments;
- let reason = 'Comment not found';
-
- const newcomments = oldcomments.filter((comment: any) => {
- if (comment.uid === req.commentUid) { // Found the comment to delete
- if (user_id === comment.author) { // Replace with your own logic, e.g. check if user has admin privileges
- return false; // Remove the comment
- } else {
- reason = 'Not authorised to delete this comment'; // Update reason
- }
- }
- return true; // Keep the comment
- });
-
- if (newcomments.length === oldcomments.length) {
- setTimeout(() => done({ canDelete: false, reason }), fakeDelay);
- } else {
- conversationDb[req.conversationUid].comments = newcomments;
- setTimeout(() => done({ canDelete: true }), fakeDelay);
- }
- };
-
- const tinycomments_edit_comment = (req: any, done: Function) => {
- const oldcomments = conversationDb[req.conversationUid].comments;
- let reason = 'Comment not found';
- let canEdit = false;
-
- const newcomments = oldcomments.map((comment: any) => {
- if (comment.uid === req.commentUid) { // Found the comment to delete
- if (user_id === comment.author) { // Replace with your own logic, e.g. check if user has admin privileges
- canEdit = true; // User can edit the comment
- return { ...comment, content: req.content, modifiedAt: new Date().toISOString() }; // Update the comment
- } else {
- reason = 'Not authorised to edit this comment'; // Update reason
- }
- }
- return comment; // Keep the comment
- });
-
- if (canEdit) {
- conversationDb[req.conversationUid].comments = newcomments;
- setTimeout(() => done({ canEdit }), fakeDelay);
- } else {
- setTimeout(() => done({ canEdit, reason }), fakeDelay);
- }
- };
-
- const tinycomments_delete_all = (req: any, done: Function) => {
- const conversation = conversationDb[req.conversationUid];
- if (user_id === conversation.comments[0].author) { // Replace wth your own logic, e.g. check if user has admin priveleges
- delete conversationDb[req.conversationUid];
- setTimeout(() => done({ canDelete: true }), fakeDelay);
- } else {
- setTimeout(() => done({ canDelete: false, reason: 'Must be conversation author' }), fakeDelay);
- }
- };
+ .catch(() => ({ id: userId }))))
+};
- const tinycomments_lookup = (req: any, done: Function) => {
- setTimeout(() => {
- done({
- conversation: {
- uid: conversationDb[req.conversationUid].uid,
- comments: [...conversationDb[req.conversationUid].comments]
- }
- });
- }, fakeDelay);
+const generateConfig = (params: Params, {excludePlugins, overrideConfig}: Options): any => {
+ const normalizedPluginConfigs: Config[] = pluginConfigs.reduce((acc, plugin) => {
+ const pConfig = typeof plugin === 'function' ? plugin(params) : plugin;
+ return acc.concat(pConfig);
+ }, [] as Config[]);
+ const toolbarConfig = normalizedPluginConfigs.map((plugin) => plugin?.toolbar).filter(Boolean).join(' | ');
+ const plugins = normalizedPluginConfigs.map((p: PluginConfig) => p.name).filter((name) => !excludePlugins?.includes(name as never));
+ const allConfigFromPlugins = normalizedPluginConfigs.reduce((acc, cur) => ({ ...acc, ...cur.config }), {});
+ const finalConfig = {
+ ...basicConfig,
+ ...allConfigFromPlugins,
+ ...overrideConfig
};
- const tinycomments_fetch = (conversationUids: string[], done: Function) => {
- const fetchedConversations: Record = {};
- conversationUids.forEach((uid: string) => {
- const conversation = conversationDb[uid];
- if (conversation) {
- fetchedConversations[uid] = {...conversation};
- }
- });
- setTimeout(() => done({ conversations: fetchedConversations }), fakeDelay);
+ return {
+ ...finalConfig,
+ plugins: plugins,
+ toolbar: toolbarConfig,
+ height: 500
};
+}
- const revisions = [
- {
- 'revisionId': '1',
- 'createdAt': '2023-11-25T03:30:46.171Z',
- 'content': 'Rev 1
'
- },
- {
- 'revisionId': '2',
- 'createdAt': '2023-11-25T12:06:09.675Z',
- 'content': 'Rev 2
'
- },
- {
- 'revisionId': '3',
- 'createdAt': '2023-11-27T03:23:32.351Z',
- 'content': 'Rev 3
'
- },
- {
- 'revisionId': '4',
- 'createdAt': '2023-11-29T12:35:16.203Z',
- 'content': 'Rev 4
'
- },
- {
- 'revisionId': '5',
- 'createdAt': '2023-11-28T08:01:56.100Z',
- 'content': 'Rev 5
'
- }
- ];
-
- const suggestededitsModel = {
- 'history': {
- '2': [
- {
- 'id': 1,
- 'uid': 'james-wilson',
- 'timestamp': 1752576936000,
- 'feedback': 'Nice improvement!'
- }
- ]
- },
- 'version': 1,
- 'contents': [
- {
- 'type': 'p',
- 'children': [
- {
- 'type': 'img',
- 'attrs': {
- 'style': 'display: block; margin-left: auto; margin-right: auto;',
- 'title': 'Tiny Logo',
- 'src': 'https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png',
- 'alt': 'TinyMCE Logo',
- 'width': '128',
- 'height': '128'
- }
- }
- ]
- },
- {
- 'type': 'h2',
- 'attrs': {
- 'style': 'text-align: center;'
- },
- 'children': [
- {
- 'text': 'Welcome to the TinyMCE Suggested Edits '
- },
- {
- 'text': 'interactive ',
- 'opData': {
- 'id': 1,
- 'type': 'insert',
- 'uid': 'alex-thompson',
- 'timestamp': 1752015064000
- }
- },
- {
- 'text': 'demo!'
- }
- ]
- },
- {
- 'type': 'p',
- 'attrs': {
- 'style': 'text-align: center;'
- },
- 'children': [
- {
- 'text': 'Try out the Suggested Edits feature'
- },
- {
- 'text': ': type in the editor, apply formatting or delete some content. T',
- 'opData': {
- 'id': 2,
- 'type': 'insert',
- 'uid': 'alex-thompson',
- 'timestamp': 1752415064000
- }
- },
- {
- 'text': ' by typing in the editor and t',
- 'opData': {
- 'id': 2,
- 'type': 'remove',
- 'uid': 'alex-thompson',
- 'timestamp': 1752415064000
- }
- },
- {
- 'text': 'hen'
- },
- {
- 'text': ',',
- 'opData': {
- 'id': 3,
- 'type': 'insert',
- 'uid': 'alex-thompson',
- 'timestamp': 1752515064000
- }
- },
- {
- 'text': ' click'
- },
- {
- 'text': 'ing',
- 'opData': {
- 'id': 4,
- 'type': 'remove',
- 'uid': 'alex-thompson',
- 'timestamp': 1752515064000
- }
- },
- {
- 'text': ' the Review Changes button in the toolbar'
- },
- {
- 'text': ' to see your changes',
- 'opData': {
- 'id': 5,
- 'type': 'insert',
- 'uid': 'kai-nakamura',
- 'timestamp': 1752615064000
- }
- },
- {
- 'text': '.'
- }
- ]
- },
- {
- 'type': 'p',
- 'attrs': {
- 'style': 'text-align: center;'
- },
- 'children': [
- {
- 'text': 'And visit the '
- },
- {
- 'text': 'pricing page',
- 'opData': {
- 'id': 6,
- 'type': 'modify',
- 'uid': 'kai-nakamura',
- 'timestamp': 1752615064000
- },
- 'format': [
- {
- 'type': 'a',
- 'attrs': {
- 'href': 'https://www.tiny.cloud/pricing'
- }
- }
- ],
- 'oldFormat': [
- {
- 'type': 'a',
- 'attrs': {
- 'href': 'https://www.tiny.cloud/pricing'
- }
- },
- 'em'
- ]
- },
- {
- 'text': ' to learn more about our Premium plugins.'
- }
- ]
- },
- {
- 'type': 'h2',
- 'children': [
- {
- 'text': 'A simple table to play with'
- }
- ]
- },
- {
- 'type': 'table',
- 'attrs': {
- 'style': 'border-collapse: collapse; width: 100%;',
- 'border': '1'
- },
- 'children': [
- {
- 'type': 'thead',
- 'children': [
- {
- 'type': 'tr',
- 'attrs': {
- 'style': 'text-align: left;'
- },
- 'children': [
- {
- 'type': 'th',
- 'children': [
- {
- 'text': 'Product'
- }
- ]
- },
- {
- 'type': 'th',
- 'children': [
- {
- 'text': 'Cost'
- }
- ]
- },
- {
- 'type': 'th',
- 'children': [
- {
- 'text': 'Really?'
- }
- ]
- }
- ]
- }
- ]
- },
- {
- 'type': 'tbody',
- 'children': [
- {
- 'type': 'tr',
- 'children': [
- {
- 'type': 'td',
- 'children': [
- {
- 'text': 'TinyMCE Cloud'
- }
- ]
- },
- {
- 'type': 'td',
- 'children': [
- {
- 'text': 'Get started for free'
- }
- ]
- },
- {
- 'type': 'td',
- 'children': [
- {
- 'text': 'Yes!',
- 'format': [
- 'strong'
- ]
- }
- ]
- }
- ]
- },
- {
- 'type': 'tr',
- 'children': [
- {
- 'type': 'td',
- 'children': [
- {
- 'text': 'Plupload'
- }
- ]
- },
- {
- 'type': 'td',
- 'children': [
- {
- 'text': 'Free'
- }
- ]
- },
- {
- 'type': 'td',
- 'children': [
- {
- 'text': 'Yes!',
- 'format': [
- 'strong'
- ]
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- },
- {
- 'type': 'h2',
- 'opData': {
- 'id': 7,
- 'type': 'insert',
- 'uid': 'mia-andersson',
- 'timestamp': 1752576331000
- },
- 'children': [
- {
- 'text': 'Found a bug?'
- }
- ]
- },
- {
- 'type': 'p',
- 'children': [
- {
- 'text': ' ',
- 'opData': {
- 'id': 7,
- 'type': 'remove',
- 'uid': 'mia-andersson',
- 'timestamp': 1752576331000
- }
- },
- {
- 'text': 'If you believe you have found a bug please create an issue on the ',
- 'opData': {
- 'id': 7,
- 'type': 'insert',
- 'uid': 'mia-andersson',
- 'timestamp': 1752576331000
- }
- },
- {
- 'text': 'GitHub repo',
- 'opData': {
- 'id': 7,
- 'type': 'insert',
- 'uid': 'mia-andersson',
- 'timestamp': 1752576331000
- },
- 'format': [
- {
- 'type': 'a',
- 'attrs': {
- 'href': 'https://github.com/tinymce/tinymce/issues'
- }
- }
- ]
- },
- {
- 'text': ' to report it to the developers.',
- 'opData': {
- 'id': 7,
- 'type': 'insert',
- 'uid': 'mia-andersson',
- 'timestamp': 1752576331000
- }
- }
- ]
- },
- {
- 'type': 'h2',
- 'children': [
- {
- 'text': 'Finally…'
- }
- ]
- },
- {
- 'type': 'p',
- 'children': [
- {
- 'text': 'Don’t forget to check out '
- },
- {
- 'text': 'Plupload',
- 'format': [
- {
- 'type': 'a',
- 'attrs': {
- 'href': 'http://www.plupload.com',
- 'target': '_blank',
- 'rel': 'noopener'
- }
- }
- ]
- },
- {
- 'text': ', the upload solution featuring HTML5 upload support.'
- }
- ]
- },
- {
- 'type': 'p',
- 'children': [
- {
- 'text': 'Thanks for supporting TinyMCE. We hope it helps you and your users create great content.'
- }
- ]
- },
- {
- 'type': 'p',
- 'children': [
- {
- 'text': 'All the best from the TinyMCE team.'
- }
- ]
- }
- ]
+export {
+ generateConfig
};
- const basicConfig = {
- height: 600,
- mobile: {
- theme: "silver",
- contextmenu: "link image table preview",
- },
- pad_empty_with_br: true,
- help_accessibility: true,
- // TODO: Target for tinymce 8
- user_id,
- fetch_users: (userIds: string[]) =>
- Promise.all(userIds.map((userId: string) => fetch(`${API_URL}/${userId}`)
- .then((response) => response.json())
- .catch(() => ({ id: userId })))),
-
- };
-
- const pluginsConfig = [
- {
- name: 'accordion',
- config: {}
- },
- {
- name: 'advlist',
- config: {
- advlist_number_styles: "default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman",
- advlist_bullet_styles: "default,circle,disc,square",
- }
- },
- {
- name: 'anchor',
- config: {}
- },
- {
- name: 'autolink',
- config: {}
- },
- // Intentional left out
- // {
- // name: 'autoresize',
- // config: {}
- // },
- {
- name: 'autosave',
- config: {}
- },
- {
- name: 'charmap',
- config: {}
- },
- {
- name: 'code',
- config: {}
- },
- {
- name: 'codesample',
- config: {
- codesample_languages: [
- { text: 'HTML/XML', value: 'markup' },
- { text: 'JavaScript', value: 'javascript' },
- { text: 'CSS', value: 'css' },
- { text: 'PHP', value: 'php' },
- { text: 'Ruby', value: 'ruby' },
- { text: 'Python', value: 'python' },
- { text: 'Java', value: 'java' },
- { text: 'C', value: 'c' },
- { text: 'C#####', value: 'csharpppppp' },
- { text: 'C++', value: 'cpp' }
- ],
- }
- },
- {
- name: 'directionality',
- config: {}
- },
- {
- name: 'emoticons',
- config: {}
- },
- // fullscreen is only available in TinyMCE < v7
- {
- name: 'fullscreen',
- config: {}
- },
- {
- name: 'help',
- config: {}
- },
- {
- name: 'image',
- config: {
- image_advtab: true,
- image_description: true,
- image_dimensions: true,
- image_title: true,
- image_caption: true,
- }
- },
- {
- name: 'importcss',
- config: {}
- },
- {
- name: 'insertdatetime',
- config: {
- // override the default formatting rule for date formats inserted by the mceInsertDate command
- insertdatetime_dateformat: "%Y/%m/%d",
-
- // override the default formatting rule for times inserted by the mceInsertTime command
- insertdatetime_timeformat: "%H%M%S",
-
- // specify a list of date/time formats to be used in the date menu or date select box
- insertdatetime_formats: ["%H:%M:%S", "%Y-%m-%d", "%I:%M:%S %p", "%D", "%H%M%S", "%Y/%m/%d"],
- }
- },
- {
- name: 'link',
- config: {}
- },
- {
- name: 'lists',
- config: {}
- },
- {
- name: 'media',
- config: {}
- },
- {
- name: 'nonbreaking',
- config: {}
- },
- {
- name: 'pagebreak',
- config: {}
- },
- {
- name: 'preview',
- config: {}
- },
- {
- name: 'quickbars',
- config: {
- quickbars_image_toolbar: 'alignleft aligncenter alignright', //This option allows you to specify or disable the image quickbars toolbar
- quickbars_selection_toolbar: 'undo redo | copy cut paste | quicklink align', //This option option configures the Quick Selection toolbar provided by the quickbars plugin.
- quickbars_insert_toolbar: 'quickimage quicktable | hr pagebreak'
- }
- },
- {
- name: 'save',
- config: {}
- },
- {
- name: 'searchreplace',
- config: {
- save_onsavecallback: function () { alert("Saved"); },
- save_oncancelcallback: function () { alert("Save Cancelled"); },
- }
- },
- {
- name: 'table',
- config: {}
- },
- {
- name: 'visualblocks',
- config: {}
- },
- {
- name: 'visualchars',
- config: {}
- },
- {
- name: 'wordcount',
- config: {}
- },
- // Premium plugins
- {
- name: 'a11ychecker',
- config: {},
- },
- {
- name: 'advcode',
- config: {
- advcode_inline: true
- },
- },
- {
- name: 'advtable',
- config: {},
- },
- {
- name: 'advtemplate',
- config: {
- advtemplate_templates
- },
- },
- {
- name: 'ai',
- config: {
- ai_request
- },
- },
- {
- name: 'autocorrect',
- config: {
- autocorrect_autocorrect: true,
- autocorrect_capitalize: true,
- },
- },
- {
- name: 'casechange',
- config: {},
- },
- {
- name: 'checklist',
- config: {},
- },
- {
- name: 'editimage',
- config: {
- editimage_toolbar: "rotateleft rotateright flipv fliph editimage imageoptions",
- // TODO: re-check DOD
- editimage_proxy_service_url: 'https://imageproxy.tiny.cloud',
-
- },
- },
- {
- name: 'exportpdf',
- config: {
- exportpdf_service_url: "https://exportpdf.converter.tiny.cloud",
- exportpdf_converter_options: {
- // HTML content to be used as the header in each page of the PDF
- header_html: 'Document Title
Date:
Page of
',
-
- // HTML content to be used as the footer in each page of the PDF
- footer_html: 'Confidential
',
-
- // CSS styles specifically for the header and footer
- header_and_footer_css: 'div { color: blue; font-family: Arial, sans-serif; font-size: 10pt; }',
-
- margin_top: '2cm',
- margin_bottom: '2cm',
- margin_left: '20mm',
- margin_right: '20mm',
-
- // 'Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6'
- format: 'A4',
- // 'portrait' or 'landscape'
- page_orientation: 'portrait',
- },
- exportpdf_converter_style: 'body { color: black !important; font-family: Helvetica, Arial, sans-serif; } p { color: cyan !important; }',
- },
- },
- {
- name: 'exportword',
- config: {
- exportword_service_url: "https://exportdocx.converter.tiny.cloud",
- exportword_converter_options: {
- // 'Letter', 'Legal', 'Tabloid', 'Statement', 'Executive', 'A3', 'A4', 'A5', 'A6', 'B4', 'B5'
- format: 'A4',
-
- margin_top: '1in',
- margin_bottom: '1in',
- margin_right: '1in',
- margin_left: '1in',
- header: [
- {
- html: 'First page header.
',
- css: 'h1 { font-size: 30px; }',
-
- //'default', 'even', 'odd', 'first'
- type: 'first'
- }
- ],
-
- footer: [
- {
- html: 'Page footer
',
- css: 'p { font-size: 12px; }',
- // 'default', 'even', 'odd', 'first'
- type: 'default'
- }
- ],
-
- // 'portrait' or 'landscape'
- orientation: 'portrait',
- auto_pagination: true,
-
- base_url: 'https://example.com',
- timezone: 'UTC'
- },
- exportword_converter_style: 'p { color: cyan !important }',
- },
- },
- {
- name: 'footnotes',
- config: {},
- },
- {
- name: 'formatpainter',
- config: {},
- },
- {
- name: 'importword',
- config: {
- importword_service_url: "https://importdocx.converter.tiny.cloud",
- importword_converter_options: {
- formatting: {
- styles: 'inline',
- resets: 'inline',
- defaults: 'inline',
- }
- }
- }
- },
- {
- name: 'inlinecss',
- config: {},
- },
- {
- name: 'linkchecker',
- config: {},
- },
- {
- name: 'markdown',
- config: {},
- },
- {
- name: 'math',
- config: {},
- },
- {
- name: 'mediaembed',
- config: {},
- },
- {
- name: 'mentions',
- config: {
- mentions_fetch,
- mentions_menu_complete,
- mentions_menu_hover,
- mentions_select,
- mentions_selector: '.mymention',
- mentions_item_type: 'profile',
- mentions_min_chars: 0,
- },
- },
- {
- name: 'mergetags',
- config: {
- mergetags_prefix: '${',
- mergetags_suffix: '}',
- mergetags_list,
- },
- },
- {
- name: 'pageembed',
- config: {},
- },
- {
- name: 'permanentpen',
- config: {
- permanentpen_properties: {
- fontname: 'impact,sans-serif',
- forecolor: '#E74C3C',
- fontsize: '12px',
- bold: true,
- italic: false,
- strikethrough: false,
- underline: false
- },
- },
- },
- {
- name: 'powerpaste',
- config: {},
- },
- // TODO: Validate content with data
- {
- name: 'revisionhistory',
- config: {
- revisionhistory_fetch: () => Promise.resolve(revisions),
- },
- },
- // TODO: Validate content with model
- {
- name: 'suggestededits',
- config: {
- suggestededits_model: suggestededitsModel,
- suggestededits_access: 'full',
- suggestededits_content: 'html',
- },
- },
- {
- name: 'tableofcontents',
- config: {},
- },
- {
- name: 'tinycomments',
- config: {
- tinycomments_mode: 'callback',
- tinycomments_mentions_enabled: true,
- tinycomments_create,
- tinycomments_reply,
- tinycomments_delete,
- tinycomments_resolve,
- tinycomments_delete_all,
- tinycomments_lookup,
- tinycomments_delete_comment,
- tinycomments_edit_comment,
- tinycomments_fetch,
- // Fallback TinyMCE < 7.8
- tinycomments_author: user_id,
- tinycomments_author_name: 'James Wilson',
- tinycomments_avatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
- // Fallback for tinymce >= 7.8
- tinycomments_fetch_author_info: (done: Function) => {
- setTimeout(() => done({
- author: user_id,
- authorName: 'James Wilson',
- authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
- }), fakeDelay);
- },
- }
- },
- // Intentionally excluded
- // {
- // name: 'tinydrive',
- // config: {},
- // },
- {
- name: 'tinymcespellchecker',
- config: {},
- },
- {
- name: 'typography',
- config: {},
- },
- {
- name: 'uploadcare',
- config: {
- uploadcare_public_key: '6ff5776be9bb64e10023',
- },
- },
- ];
-
-
- const basicToolbar = "bold italic underline strikethrough subscript superscript | math uploadcare | exportpdf exportword importword | aidialog aishortcuts | accordion addtemplate inserttemplate| fontfamily fontsize fontsizeinput | numlist bullist checklist mergetags footnotes footnotesupdate| typography permanentpen formatpainter removeformat forecolor backcolor | blockquote nonbreaking hr pagebreak | casechange styles blocks lineheight | ltr rtl outdent indent | align alignleft aligncenter alignright alignjustify alignnone | h1 h2 h3 h4 h5 h6 h7 | wordcount searchreplace | undo redo | revisionhistory save cancel restoredraft | fullscreen print preview code help | insertdatetime codesample emoticons charmap | anchor link unlink image media pageembed insertfile | visualblocks visualchars | suggestededits";
-
-
- interface Config {
- excludePlugins?: string[];
- overrides?: {}
- }
-
- export const generateConfig = ({ excludePlugins = [], overrides = {} }: Config): any => {
- const plugins = pluginsConfig.map((p) => p.name).filter((name: string) => !(excludePlugins as string[]).includes(name));
- const extractedPluginsConfig = pluginsConfig.reduce((acc, cur) => ({ ...acc, ...cur.config }), {});
- const finalConfig = { ...basicConfig, ...extractedPluginsConfig, ...overrides };
-
- return {
- ...finalConfig,
- plugins: plugins,
- toolbar: basicToolbar,
- height: 500
- };
- }
+
diff --git a/src/app/configs/directionality.ts b/src/app/configs/directionality.ts
new file mode 100644
index 0000000..5468ee6
--- /dev/null
+++ b/src/app/configs/directionality.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'directionality',
+ toolbar: 'ltr rtl',
+ config: {}
+};
diff --git a/src/app/configs/editimage.ts b/src/app/configs/editimage.ts
new file mode 100644
index 0000000..b4146fb
--- /dev/null
+++ b/src/app/configs/editimage.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'editimage',
+ config: {}
+};
diff --git a/src/app/configs/editimage2.ts b/src/app/configs/editimage2.ts
new file mode 100644
index 0000000..5a59491
--- /dev/null
+++ b/src/app/configs/editimage2.ts
@@ -0,0 +1,8 @@
+export default {
+ name: 'editimage',
+ toolbar: 'rotateleft rotateright flipv fliph editimage imageoptions',
+ config: {
+ editimage_toolbar: "rotateleft rotateright flipv fliph editimage imageoptions",
+ editimage_proxy_service_url: 'https://imageproxy.tiny.cloud',
+ }
+};
diff --git a/src/app/configs/emoticons.ts b/src/app/configs/emoticons.ts
new file mode 100644
index 0000000..996ab12
--- /dev/null
+++ b/src/app/configs/emoticons.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'emoticons',
+ toolbar: 'emoticons',
+ config: {}
+};
diff --git a/src/app/configs/exportpdf.ts b/src/app/configs/exportpdf.ts
new file mode 100644
index 0000000..297e24c
--- /dev/null
+++ b/src/app/configs/exportpdf.ts
@@ -0,0 +1,19 @@
+export default {
+ name: 'exportpdf',
+ toolbar: 'exportpdf',
+ config: {
+ exportpdf_service_url: "https://exportpdf.converter.tiny.cloud",
+ exportpdf_converter_options: {
+ header_html: 'Document Title
Date:
Page of
',
+ footer_html: 'Confidential
',
+ header_and_footer_css: 'div { color: blue; font-family: Arial, sans-serif; font-size: 10pt; }',
+ margin_top: '2cm',
+ margin_bottom: '2cm',
+ margin_left: '20mm',
+ margin_right: '20mm',
+ format: 'A4',
+ page_orientation: 'portrait',
+ },
+ exportpdf_converter_style: 'body { color: black !important; font-family: Helvetica, Arial, sans-serif; } p { color: cyan !important; }',
+ }
+};
diff --git a/src/app/configs/exportword.ts b/src/app/configs/exportword.ts
new file mode 100644
index 0000000..0d357dc
--- /dev/null
+++ b/src/app/configs/exportword.ts
@@ -0,0 +1,42 @@
+export default {
+ toolbar: 'exportword',
+ name: 'exportword',
+ config: {
+ exportword_service_url: "https://exportdocx.converter.tiny.cloud",
+ exportword_converter_options: {
+ // 'Letter', 'Legal', 'Tabloid', 'Statement', 'Executive', 'A3', 'A4', 'A5', 'A6', 'B4', 'B5'
+ format: 'A4',
+
+ margin_top: '1in',
+ margin_bottom: '1in',
+ margin_right: '1in',
+ margin_left: '1in',
+ header: [
+ {
+ html: 'First page header.
',
+ css: 'h1 { font-size: 30px; }',
+
+ //'default', 'even', 'odd', 'first'
+ type: 'first'
+ }
+ ],
+
+ footer: [
+ {
+ html: 'Page footer
',
+ css: 'p { font-size: 12px; }',
+ // 'default', 'even', 'odd', 'first'
+ type: 'default'
+ }
+ ],
+
+ // 'portrait' or 'landscape'
+ orientation: 'portrait',
+ auto_pagination: true,
+
+ base_url: 'https://example.com',
+ timezone: 'UTC'
+ },
+ exportword_converter_style: 'p { color: cyan !important }',
+ },
+};
\ No newline at end of file
diff --git a/src/app/configs/footnotes.ts b/src/app/configs/footnotes.ts
new file mode 100644
index 0000000..d1f7412
--- /dev/null
+++ b/src/app/configs/footnotes.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'footnotes',
+ toolbar: 'footnotes footnotesupdate',
+ config: {}
+};
diff --git a/src/app/configs/formatpainter.ts b/src/app/configs/formatpainter.ts
new file mode 100644
index 0000000..ae46554
--- /dev/null
+++ b/src/app/configs/formatpainter.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'formatpainter',
+ toolbar: 'formatpainter',
+ config: {}
+};
diff --git a/src/app/configs/fullscreen.ts b/src/app/configs/fullscreen.ts
new file mode 100644
index 0000000..b2c7e98
--- /dev/null
+++ b/src/app/configs/fullscreen.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'fullscreen',
+ toolbar: 'fullscreen',
+ config: {}
+};
diff --git a/src/app/configs/help.ts b/src/app/configs/help.ts
new file mode 100644
index 0000000..aa5ace0
--- /dev/null
+++ b/src/app/configs/help.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'help',
+ toolbar: 'help',
+ config: {}
+};
diff --git a/src/app/configs/image.ts b/src/app/configs/image.ts
new file mode 100644
index 0000000..ca8234d
--- /dev/null
+++ b/src/app/configs/image.ts
@@ -0,0 +1,11 @@
+export default {
+ name: 'image',
+ toolbar: 'image',
+ config: {
+ image_advtab: true,
+ image_description: true,
+ image_dimensions: true,
+ image_title: true,
+ image_caption: true,
+ }
+};
diff --git a/src/app/configs/importcss.ts b/src/app/configs/importcss.ts
new file mode 100644
index 0000000..14fbe18
--- /dev/null
+++ b/src/app/configs/importcss.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'importcss',
+ config: {}
+};
diff --git a/src/app/configs/importword.ts b/src/app/configs/importword.ts
new file mode 100644
index 0000000..0859913
--- /dev/null
+++ b/src/app/configs/importword.ts
@@ -0,0 +1,14 @@
+export default {
+ name: 'importword',
+ toolbar: 'importword',
+ config: {
+ importword_service_url: "https://importdocx.converter.tiny.cloud",
+ importword_converter_options: {
+ formatting: {
+ styles: 'inline',
+ resets: 'inline',
+ defaults: 'inline',
+ }
+ }
+ }
+};
diff --git a/src/app/configs/inlinecss.ts b/src/app/configs/inlinecss.ts
new file mode 100644
index 0000000..e180616
--- /dev/null
+++ b/src/app/configs/inlinecss.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'inlinecss',
+ config: {}
+};
diff --git a/src/app/configs/insertdatetime.ts b/src/app/configs/insertdatetime.ts
new file mode 100644
index 0000000..dc53424
--- /dev/null
+++ b/src/app/configs/insertdatetime.ts
@@ -0,0 +1,14 @@
+export default {
+ name: 'insertdatetime',
+ toolbar: 'insertdatetime',
+ config: {
+ // override the default formatting rule for date formats inserted by the mceInsertDate command
+ insertdatetime_dateformat: "%Y/%m/%d",
+
+ // override the default formatting rule for times inserted by the mceInsertTime command
+ insertdatetime_timeformat: "%H%M%S",
+
+ // specify a list of date/time formats to be used in the date menu or date select box
+ insertdatetime_formats: ["%H:%M:%S", "%Y-%m-%d", "%I:%M:%S %p", "%D", "%H%M%S", "%Y/%m/%d"],
+ }
+};
diff --git a/src/app/configs/link.ts b/src/app/configs/link.ts
new file mode 100644
index 0000000..f1fd5cc
--- /dev/null
+++ b/src/app/configs/link.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'link',
+ toolbar: 'link unlink',
+ config: {}
+};
diff --git a/src/app/configs/linkchecker.ts b/src/app/configs/linkchecker.ts
new file mode 100644
index 0000000..2804aa8
--- /dev/null
+++ b/src/app/configs/linkchecker.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'linkchecker',
+ config: {}
+};
diff --git a/src/app/configs/lists.ts b/src/app/configs/lists.ts
new file mode 100644
index 0000000..9db9673
--- /dev/null
+++ b/src/app/configs/lists.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'lists',
+ toolbar: 'numlist bullist',
+ config: {}
+};
diff --git a/src/app/configs/markdown.ts b/src/app/configs/markdown.ts
new file mode 100644
index 0000000..aeed70d
--- /dev/null
+++ b/src/app/configs/markdown.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'markdown',
+ config: {}
+};
diff --git a/src/app/configs/math.ts b/src/app/configs/math.ts
new file mode 100644
index 0000000..f6651b7
--- /dev/null
+++ b/src/app/configs/math.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'math',
+ toolbar: 'math',
+ config: {}
+};
diff --git a/src/app/configs/media.ts b/src/app/configs/media.ts
new file mode 100644
index 0000000..49ce63f
--- /dev/null
+++ b/src/app/configs/media.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'media',
+ toolbar: 'media',
+ config: {}
+};
diff --git a/src/app/configs/mediaembed.ts b/src/app/configs/mediaembed.ts
new file mode 100644
index 0000000..77f1d32
--- /dev/null
+++ b/src/app/configs/mediaembed.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'mediaembed',
+ config: {}
+};
diff --git a/src/app/configs/mentions.ts b/src/app/configs/mentions.ts
new file mode 100644
index 0000000..c9c33eb
--- /dev/null
+++ b/src/app/configs/mentions.ts
@@ -0,0 +1,78 @@
+import type { Editor } from "tinymce";
+
+const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';
+
+interface UserInfo {
+ id: string;
+ name: string;
+ avatar?: string;
+ custom?: any;
+}
+
+const mentions_fetch = async (query: any, success: any) => {
+ const searchPhrase = query.term.toLowerCase();
+ await fetch(`${API_URL}?q=${encodeURIComponent(searchPhrase)}`)
+ .then((response) => response.json())
+ .then((users) => success(users.data.map((userInfo: UserInfo) => ({
+ id: userInfo.id,
+ name: userInfo.name,
+ image: userInfo.avatar,
+ description: userInfo.custom.role
+ }))))
+ .catch((error) => console.log(error));
+};
+
+const mentions_menu_complete = (editor: Editor, userInfo: UserInfo) => {
+ const span = editor.getDoc().createElement('span');
+ span.className = 'mymention';
+ span.setAttribute('data-mention-id', userInfo.id);
+ span.appendChild(editor.getDoc().createTextNode('@' + userInfo.name));
+ return span;
+};
+
+const createCard = (userInfo: any) => {
+ const div = document.createElement('div');
+ div.innerHTML = (
+ '' +
+ '

' +
+ '
' + userInfo.name + '
' +
+ '
' + userInfo.description + '
' +
+ '
'
+ );
+ return div;
+};
+
+const mentions_menu_hover = async (userInfo: UserInfo, success: any) => {
+ const card = createCard(userInfo);
+ success(card);
+};
+
+const mentions_select = async (mention: any, success: any) => {
+ const id = mention.getAttribute('data-mention-id');
+ await fetch(`${API_URL}/${id}`)
+ .then((response) => response.json())
+ .then((userInfo) => {
+ const card = createCard({
+ id: userInfo.id,
+ name: userInfo.name,
+ image: userInfo.avatar,
+ description: userInfo.custom.role
+ });
+ success(card);
+ })
+ .catch((error) => console.error(error));
+};
+
+export default {
+ toolbar: 'mentions',
+ name: 'mentions',
+ config: {
+ mentions_fetch,
+ mentions_menu_complete,
+ mentions_menu_hover,
+ mentions_select,
+ mentions_selector: '.mymention',
+ mentions_item_type: 'profile',
+ mentions_min_chars: 0,
+ }
+};
\ No newline at end of file
diff --git a/src/app/configs/mergetags.ts b/src/app/configs/mergetags.ts
new file mode 100644
index 0000000..d839fa7
--- /dev/null
+++ b/src/app/configs/mergetags.ts
@@ -0,0 +1,59 @@
+const mergetags_list = [
+ {
+ value: 'Current.Date',
+ title: 'Current date in DD/MM/YYYY format'
+ },
+ {
+ value: 'Campaign.Toc',
+ title: 'Linked table of contents in your campaign'
+ },
+ {
+ title: 'Phone',
+ menu: [
+ {
+ value: 'Phone.Home'
+ },
+ {
+ value: 'Phone.work'
+ }
+ ]
+ },
+ {
+ title: 'Person',
+ menu: [
+ {
+ value: 'Person.Name'
+ },
+ {
+ value: 'Person.Name.First'
+ },
+ {
+ value: 'Person.Name.Last'
+ },
+ {
+ value: 'Person.Name.Full'
+ },
+ {
+ title: 'Email',
+ menu: [
+ {
+ value: 'Person.Email.Work'
+ },
+ {
+ value: 'Person.Email.Home'
+ }
+ ]
+ }
+ ]
+ }
+ ];
+
+ export default {
+ toolbar: 'mergetags',
+ name: 'mergetags',
+ config: {
+ mergetags_prefix: '${',
+ mergetags_suffix: '}',
+ mergetags_list,
+ }
+ };
\ No newline at end of file
diff --git a/src/app/configs/nonbreaking.ts b/src/app/configs/nonbreaking.ts
new file mode 100644
index 0000000..493c470
--- /dev/null
+++ b/src/app/configs/nonbreaking.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'nonbreaking',
+ toolbar: 'nonbreaking',
+ config: {}
+};
diff --git a/src/app/configs/pagebreak.ts b/src/app/configs/pagebreak.ts
new file mode 100644
index 0000000..12d4e0c
--- /dev/null
+++ b/src/app/configs/pagebreak.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'pagebreak',
+ toolbar: 'pagebreak',
+ config: {}
+};
diff --git a/src/app/configs/pageembed.ts b/src/app/configs/pageembed.ts
new file mode 100644
index 0000000..3d89903
--- /dev/null
+++ b/src/app/configs/pageembed.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'pageembed',
+ toolbar: 'pageembed',
+ config: {}
+};
diff --git a/src/app/configs/permanentpen.ts b/src/app/configs/permanentpen.ts
new file mode 100644
index 0000000..304232e
--- /dev/null
+++ b/src/app/configs/permanentpen.ts
@@ -0,0 +1,15 @@
+export default {
+ name: 'permanentpen',
+ toolbar: 'permanentpen',
+ config: {
+ permanentpen_properties: {
+ fontname: 'impact,sans-serif',
+ forecolor: '#E74C3C',
+ fontsize: '12px',
+ bold: true,
+ italic: false,
+ strikethrough: false,
+ underline: false
+ },
+ }
+};
diff --git a/src/app/configs/powerpaste.ts b/src/app/configs/powerpaste.ts
new file mode 100644
index 0000000..6873936
--- /dev/null
+++ b/src/app/configs/powerpaste.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'powerpaste',
+ config: {}
+};
diff --git a/src/app/configs/preview.ts b/src/app/configs/preview.ts
new file mode 100644
index 0000000..e958596
--- /dev/null
+++ b/src/app/configs/preview.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'preview',
+ toolbar: 'preview',
+ config: {}
+};
diff --git a/src/app/configs/quickbars.ts b/src/app/configs/quickbars.ts
new file mode 100644
index 0000000..891d728
--- /dev/null
+++ b/src/app/configs/quickbars.ts
@@ -0,0 +1,8 @@
+export default {
+ name: 'quickbars',
+ config: {
+ quickbars_image_toolbar: 'alignleft aligncenter alignright',
+ quickbars_selection_toolbar: 'undo redo | copy cut paste | quicklink align',
+ quickbars_insert_toolbar: 'quickimage quicktable | hr pagebreak'
+ }
+};
diff --git a/src/app/configs/revisionhistory.ts b/src/app/configs/revisionhistory.ts
new file mode 100644
index 0000000..acee513
--- /dev/null
+++ b/src/app/configs/revisionhistory.ts
@@ -0,0 +1,35 @@
+const revisions = [
+ {
+ 'revisionId': '1',
+ 'createdAt': '2023-11-25T03:30:46.171Z',
+ 'content': 'Rev 1
'
+ },
+ {
+ 'revisionId': '2',
+ 'createdAt': '2023-11-25T12:06:09.675Z',
+ 'content': 'Rev 2
'
+ },
+ {
+ 'revisionId': '3',
+ 'createdAt': '2023-11-27T03:23:32.351Z',
+ 'content': 'Rev 3
'
+ },
+ {
+ 'revisionId': '4',
+ 'createdAt': '2023-11-29T12:35:16.203Z',
+ 'content': 'Rev 4
'
+ },
+ {
+ 'revisionId': '5',
+ 'createdAt': '2023-11-28T08:01:56.100Z',
+ 'content': 'Rev 5
'
+ }
+];
+
+export default {
+ toolbar: 'revisionhistory',
+ name: 'revisionhistory',
+ config: {
+ revisionhistory_fetch: () => Promise.resolve(revisions),
+ }
+};
diff --git a/src/app/configs/save.ts b/src/app/configs/save.ts
new file mode 100644
index 0000000..84f3ca6
--- /dev/null
+++ b/src/app/configs/save.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'save',
+ toolbar: 'save cancel',
+ config: {}
+};
diff --git a/src/app/configs/searchreplace.ts b/src/app/configs/searchreplace.ts
new file mode 100644
index 0000000..06be404
--- /dev/null
+++ b/src/app/configs/searchreplace.ts
@@ -0,0 +1,8 @@
+export default {
+ name: 'searchreplace',
+ toolbar: 'searchreplace',
+ config: {
+ save_onsavecallback: function () { alert("Saved"); },
+ save_oncancelcallback: function () { alert("Save Cancelled"); },
+ }
+};
diff --git a/src/app/configs/suggestededits.ts b/src/app/configs/suggestededits.ts
new file mode 100644
index 0000000..a2b44a6
--- /dev/null
+++ b/src/app/configs/suggestededits.ts
@@ -0,0 +1,407 @@
+const suggestededitsModel = {
+ 'history': {
+ '2': [
+ {
+ 'id': 1,
+ 'uid': 'james-wilson',
+ 'timestamp': 1752576936000,
+ 'feedback': 'Nice improvement!'
+ }
+ ]
+ },
+ 'version': 1,
+ 'contents': [
+ {
+ 'type': 'p',
+ 'children': [
+ {
+ 'type': 'img',
+ 'attrs': {
+ 'style': 'display: block; margin-left: auto; margin-right: auto;',
+ 'title': 'Tiny Logo',
+ 'src': 'https://www.tiny.cloud/docs/images/logos/android-chrome-256x256.png',
+ 'alt': 'TinyMCE Logo',
+ 'width': '128',
+ 'height': '128'
+ }
+ }
+ ]
+ },
+ {
+ 'type': 'h2',
+ 'attrs': {
+ 'style': 'text-align: center;'
+ },
+ 'children': [
+ {
+ 'text': 'Welcome to the TinyMCE Suggested Edits '
+ },
+ {
+ 'text': 'interactive ',
+ 'opData': {
+ 'id': 1,
+ 'type': 'insert',
+ 'uid': 'alex-thompson',
+ 'timestamp': 1752015064000
+ }
+ },
+ {
+ 'text': 'demo!'
+ }
+ ]
+ },
+ {
+ 'type': 'p',
+ 'attrs': {
+ 'style': 'text-align: center;'
+ },
+ 'children': [
+ {
+ 'text': 'Try out the Suggested Edits feature'
+ },
+ {
+ 'text': ': type in the editor, apply formatting or delete some content. T',
+ 'opData': {
+ 'id': 2,
+ 'type': 'insert',
+ 'uid': 'alex-thompson',
+ 'timestamp': 1752415064000
+ }
+ },
+ {
+ 'text': ' by typing in the editor and t',
+ 'opData': {
+ 'id': 2,
+ 'type': 'remove',
+ 'uid': 'alex-thompson',
+ 'timestamp': 1752415064000
+ }
+ },
+ {
+ 'text': 'hen'
+ },
+ {
+ 'text': ',',
+ 'opData': {
+ 'id': 3,
+ 'type': 'insert',
+ 'uid': 'alex-thompson',
+ 'timestamp': 1752515064000
+ }
+ },
+ {
+ 'text': ' click'
+ },
+ {
+ 'text': 'ing',
+ 'opData': {
+ 'id': 4,
+ 'type': 'remove',
+ 'uid': 'alex-thompson',
+ 'timestamp': 1752515064000
+ }
+ },
+ {
+ 'text': ' the Review Changes button in the toolbar'
+ },
+ {
+ 'text': ' to see your changes',
+ 'opData': {
+ 'id': 5,
+ 'type': 'insert',
+ 'uid': 'kai-nakamura',
+ 'timestamp': 1752615064000
+ }
+ },
+ {
+ 'text': '.'
+ }
+ ]
+ },
+ {
+ 'type': 'p',
+ 'attrs': {
+ 'style': 'text-align: center;'
+ },
+ 'children': [
+ {
+ 'text': 'And visit the '
+ },
+ {
+ 'text': 'pricing page',
+ 'opData': {
+ 'id': 6,
+ 'type': 'modify',
+ 'uid': 'kai-nakamura',
+ 'timestamp': 1752615064000
+ },
+ 'format': [
+ {
+ 'type': 'a',
+ 'attrs': {
+ 'href': 'https://www.tiny.cloud/pricing'
+ }
+ }
+ ],
+ 'oldFormat': [
+ {
+ 'type': 'a',
+ 'attrs': {
+ 'href': 'https://www.tiny.cloud/pricing'
+ }
+ },
+ 'em'
+ ]
+ },
+ {
+ 'text': ' to learn more about our Premium plugins.'
+ }
+ ]
+ },
+ {
+ 'type': 'h2',
+ 'children': [
+ {
+ 'text': 'A simple table to play with'
+ }
+ ]
+ },
+ {
+ 'type': 'table',
+ 'attrs': {
+ 'style': 'border-collapse: collapse; width: 100%;',
+ 'border': '1'
+ },
+ 'children': [
+ {
+ 'type': 'thead',
+ 'children': [
+ {
+ 'type': 'tr',
+ 'attrs': {
+ 'style': 'text-align: left;'
+ },
+ 'children': [
+ {
+ 'type': 'th',
+ 'children': [
+ {
+ 'text': 'Product'
+ }
+ ]
+ },
+ {
+ 'type': 'th',
+ 'children': [
+ {
+ 'text': 'Cost'
+ }
+ ]
+ },
+ {
+ 'type': 'th',
+ 'children': [
+ {
+ 'text': 'Really?'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'type': 'tbody',
+ 'children': [
+ {
+ 'type': 'tr',
+ 'children': [
+ {
+ 'type': 'td',
+ 'children': [
+ {
+ 'text': 'TinyMCE Cloud'
+ }
+ ]
+ },
+ {
+ 'type': 'td',
+ 'children': [
+ {
+ 'text': 'Get started for free'
+ }
+ ]
+ },
+ {
+ 'type': 'td',
+ 'children': [
+ {
+ 'text': 'Yes!',
+ 'format': [
+ 'strong'
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'type': 'tr',
+ 'children': [
+ {
+ 'type': 'td',
+ 'children': [
+ {
+ 'text': 'Plupload'
+ }
+ ]
+ },
+ {
+ 'type': 'td',
+ 'children': [
+ {
+ 'text': 'Free'
+ }
+ ]
+ },
+ {
+ 'type': 'td',
+ 'children': [
+ {
+ 'text': 'Yes!',
+ 'format': [
+ 'strong'
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ 'type': 'h2',
+ 'opData': {
+ 'id': 7,
+ 'type': 'insert',
+ 'uid': 'mia-andersson',
+ 'timestamp': 1752576331000
+ },
+ 'children': [
+ {
+ 'text': 'Found a bug?'
+ }
+ ]
+ },
+ {
+ 'type': 'p',
+ 'children': [
+ {
+ 'text': ' ',
+ 'opData': {
+ 'id': 7,
+ 'type': 'remove',
+ 'uid': 'mia-andersson',
+ 'timestamp': 1752576331000
+ }
+ },
+ {
+ 'text': 'If you believe you have found a bug please create an issue on the ',
+ 'opData': {
+ 'id': 7,
+ 'type': 'insert',
+ 'uid': 'mia-andersson',
+ 'timestamp': 1752576331000
+ }
+ },
+ {
+ 'text': 'GitHub repo',
+ 'opData': {
+ 'id': 7,
+ 'type': 'insert',
+ 'uid': 'mia-andersson',
+ 'timestamp': 1752576331000
+ },
+ 'format': [
+ {
+ 'type': 'a',
+ 'attrs': {
+ 'href': 'https://github.com/tinymce/tinymce/issues'
+ }
+ }
+ ]
+ },
+ {
+ 'text': ' to report it to the developers.',
+ 'opData': {
+ 'id': 7,
+ 'type': 'insert',
+ 'uid': 'mia-andersson',
+ 'timestamp': 1752576331000
+ }
+ }
+ ]
+ },
+ {
+ 'type': 'h2',
+ 'children': [
+ {
+ 'text': 'Finally…'
+ }
+ ]
+ },
+ {
+ 'type': 'p',
+ 'children': [
+ {
+ 'text': 'Don’t forget to check out '
+ },
+ {
+ 'text': 'Plupload',
+ 'format': [
+ {
+ 'type': 'a',
+ 'attrs': {
+ 'href': 'http://www.plupload.com',
+ 'target': '_blank',
+ 'rel': 'noopener'
+ }
+ }
+ ]
+ },
+ {
+ 'text': ', the upload solution featuring HTML5 upload support.'
+ }
+ ]
+ },
+ {
+ 'type': 'p',
+ 'children': [
+ {
+ 'text': 'Thanks for supporting TinyMCE. We hope it helps you and your users create great content.'
+ }
+ ]
+ },
+ {
+ 'type': 'p',
+ 'children': [
+ {
+ 'text': 'All the best from the TinyMCE team.'
+ }
+ ]
+ }
+ ]
+};
+
+export default {
+ toolbar: 'suggestededits',
+ name: 'suggestededits',
+ config: {
+ suggestededits_model: suggestededitsModel,
+ suggestededits_access: 'full',
+ suggestededits_content: 'html',
+ },
+};
+
diff --git a/src/app/configs/table.ts b/src/app/configs/table.ts
new file mode 100644
index 0000000..e29e779
--- /dev/null
+++ b/src/app/configs/table.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'table',
+ toolbar: 'table',
+ config: {}
+};
diff --git a/src/app/configs/tableofcontents.ts b/src/app/configs/tableofcontents.ts
new file mode 100644
index 0000000..05ece39
--- /dev/null
+++ b/src/app/configs/tableofcontents.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'tableofcontents',
+ toolbar: 'tableofcontents',
+ config: {}
+};
diff --git a/src/app/configs/tinycomments.ts b/src/app/configs/tinycomments.ts
new file mode 100644
index 0000000..d6a7a89
--- /dev/null
+++ b/src/app/configs/tinycomments.ts
@@ -0,0 +1,175 @@
+const user_id: string = 'james-wilson';
+const collaborator_id: string = 'mia-andersson';
+
+const conversationDb: Record = {}
+
+
+const tinycomments_create = (req: any, done: any, fail: any) => {
+ if (req.content === 'fail') {
+ fail(new Error('Something has gone wrong...'));
+ } else {
+ const uid = 'annotation-' + randomString();
+ conversationDb[uid] = {
+ uid,
+ comments: [{
+ uid,
+ author: user_id,
+ authorName: 'James Wilson',
+ authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
+ content: req.content,
+ createdAt: req.createdAt,
+ modifiedAt: req.createdAt
+ }]
+ };
+ setTimeout(() => done({ conversationUid: uid }), fakeDelay);
+ }
+};
+
+const fakeDelay = 200;
+
+const randomString = () => {
+ return crypto.getRandomValues(new Uint32Array(1))[0].toString(36).substring(2, 14);
+};
+
+const tinycomments_reply = (req: any, done: any) => {
+ const replyUid = 'annotation-' + randomString();
+ conversationDb[req.conversationUid].comments.push({
+ uid: replyUid,
+ author: user_id,
+ authorName: 'James Wilson',
+ authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
+ content: req.content,
+ createdAt: req.createdAt,
+ modifiedAt: req.createdAt
+ });
+ setTimeout(() => done({ commentUid: replyUid }), fakeDelay);
+};
+
+const tinycomments_delete = (req: any, done: any) => {
+ if (user_id === collaborator_id) { // Replace wth your own logic, e.g. check if user created the conversation
+ delete conversationDb[req.conversationUid];
+ setTimeout(() => done({ canDelete: true }), fakeDelay);
+ } else {
+ setTimeout(() => done({ canDelete: false, reason: 'Must be admin user' }), fakeDelay);
+ }
+};
+
+const tinycomments_resolve = (req: any, done: any) => {
+ const conversation = conversationDb[req.conversationUid];
+ if (user_id === conversation.comments[0].author) { // Replace wth your own logic, e.g. check if user has admin priveleges
+ delete conversationDb[req.conversationUid];
+ setTimeout(() => done({ canResolve: true }), fakeDelay);
+ } else {
+ setTimeout(() => done({ canResolve: false, reason: 'Must be conversation author' }), fakeDelay);
+ }
+};
+
+const tinycomments_delete_comment = (req: any, done: any) => {
+ const oldcomments = conversationDb[req.conversationUid].comments;
+ let reason = 'Comment not found';
+
+ const newcomments = oldcomments.filter((comment: any) => {
+ if (comment.uid === req.commentUid) { // Found the comment to delete
+ if (user_id === comment.author) { // Replace with your own logic, e.g. check if user has admin privileges
+ return false; // Remove the comment
+ } else {
+ reason = 'Not authorised to delete this comment'; // Update reason
+ }
+ }
+ return true; // Keep the comment
+ });
+
+ if (newcomments.length === oldcomments.length) {
+ setTimeout(() => done({ canDelete: false, reason }), fakeDelay);
+ } else {
+ conversationDb[req.conversationUid].comments = newcomments;
+ setTimeout(() => done({ canDelete: true }), fakeDelay);
+ }
+};
+
+const tinycomments_edit_comment = (req: any, done: any) => {
+ const oldcomments = conversationDb[req.conversationUid].comments;
+ let reason = 'Comment not found';
+ let canEdit = false;
+
+ const newcomments = oldcomments.map((comment: any) => {
+ if (comment.uid === req.commentUid) { // Found the comment to delete
+ if (user_id === comment.author) { // Replace with your own logic, e.g. check if user has admin privileges
+ canEdit = true; // User can edit the comment
+ return { ...comment, content: req.content, modifiedAt: new Date().toISOString() }; // Update the comment
+ } else {
+ reason = 'Not authorised to edit this comment'; // Update reason
+ }
+ }
+ return comment; // Keep the comment
+ });
+
+ if (canEdit) {
+ conversationDb[req.conversationUid].comments = newcomments;
+ setTimeout(() => done({ canEdit }), fakeDelay);
+ } else {
+ setTimeout(() => done({ canEdit, reason }), fakeDelay);
+ }
+};
+
+const tinycomments_delete_all = (req: any, done: any) => {
+ const conversation = conversationDb[req.conversationUid];
+ if (user_id === conversation.comments[0].author) { // Replace wth your own logic, e.g. check if user has admin priveleges
+ delete conversationDb[req.conversationUid];
+ setTimeout(() => done({ canDelete: true }), fakeDelay);
+ } else {
+ setTimeout(() => done({ canDelete: false, reason: 'Must be conversation author' }), fakeDelay);
+ }
+};
+
+const tinycomments_lookup = (req: any, done: any) => {
+ setTimeout(() => {
+ done({
+ conversation: {
+ uid: conversationDb[req.conversationUid].uid,
+ comments: [...conversationDb[req.conversationUid].comments]
+ }
+ });
+ }, fakeDelay);
+};
+
+const tinycomments_fetch = (conversationUids: any[], done: any) => {
+ const fetchedConversations: Record = {};
+ conversationUids.forEach((uid) => {
+ const conversation = conversationDb[uid];
+ if (conversation) {
+ fetchedConversations[uid] = {...conversation};
+ }
+ });
+ setTimeout(() => done({ conversations: fetchedConversations }), fakeDelay);
+};
+
+export default {
+ toolbar: 'comments',
+ name: 'tinycomments',
+ config: {
+ tinycomments_mode: 'callback',
+ tinycomments_mentions_enabled: true,
+ tinycomments_create,
+ tinycomments_reply,
+ tinycomments_delete,
+ tinycomments_resolve,
+ tinycomments_delete_all,
+ tinycomments_lookup,
+ tinycomments_delete_comment,
+ tinycomments_edit_comment,
+ tinycomments_fetch,
+ // Fallback TinyMCE < 7.8
+ tinycomments_author: user_id,
+ tinycomments_author_name: 'James Wilson',
+ tinycomments_avatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
+ // Fallback for tinymce >= 7.8
+ tinycomments_fetch_author_info: (done: any) => {
+ setTimeout(() => done({
+ author: user_id,
+ authorName: 'James Wilson',
+ authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
+ }), fakeDelay);
+ },
+ }
+};
\ No newline at end of file
diff --git a/src/app/configs/tinymceai.ts b/src/app/configs/tinymceai.ts
new file mode 100644
index 0000000..80be2dc
--- /dev/null
+++ b/src/app/configs/tinymceai.ts
@@ -0,0 +1,144 @@
+import { TinymceAIParams } from "./types";
+
+export default (params: TinymceAIParams) => ({
+ config: {
+ // tinymceai_api_url: 'https://tinymceai.api.staging.tiny.cloud/',
+
+ // REQUIRED: tinymceai_service_url — Base URL of the AI backend service
+ // tinymceai_service_url: 'https://tinymceai.api.staging.tiny.cloud/',
+ tinymceai_service_url: 'https://tinymceai.api.tiny.cloud/',
+ tinymceai_token_provider: async () => {
+ // Create a session and fetch a random user
+ await fetch(`${params.jwtServerURL}/1/${params.apiKey}/auth/random`, { method: "POST", credentials: "include" });
+
+ const response = await fetch(`${params.jwtServerURL}/1/${params.apiKey}/jwt/tinymceai`, {
+ credentials: 'include'
+ });
+ const token = await response.text();
+ return { token };
+ },
+
+
+ // content_id — Groups conversations by document
+ content_id: 'render_doc_1',
+
+ // tinymceai_sidebar_type — 'static' | 'floating' (default: 'static')
+ tinymceai_sidebar_type: 'static',
+
+ // Model configuration
+ // Available Models: auto (agent-1), gpt-5.2, gpt-5.1, gpt-5, gpt-5-mini,
+ // claude-4-5-haiku, claude-4-5-sonnet, gemini-3-pro, gemini-3-flash,
+ // gemini-2-5-flash, gpt-4.1, gpt-4.1-mini
+
+ // tinymceai_default_model — Model ID to select by default
+ tinymceai_default_model: 'assistant-1',
+
+ // tinymceai_allow_model_selection — Show model selection dropdown (default: true)
+ tinymceai_allow_model_selection: true,
+
+ // tinymceai_quickactions_menu — Items in the Quick Actions menu
+ // Correct control IDs: ai-quickactions-chat-prompts, ai-quickactions-improve-writing,
+ // ai-quickactions-continue-writing, ai-quickactions-check-grammar,
+ // ai-quickactions-change-length, ai-quickactions-change-tone,
+ // ai-quickactions-translate, ai-quickactions-custom
+ tinymceai_quickactions_menu: [
+ 'ai-quickactions-chat-prompts',
+ 'ai-quickactions-improve-writing',
+ 'ai-quickactions-continue-writing',
+ 'ai-quickactions-check-grammar',
+ 'ai-quickactions-change-length',
+ 'ai-quickactions-change-tone',
+ 'ai-quickactions-translate',
+ 'ai-quickactions-custom'
+ ],
+
+ // tinymceai_quickactions_chat_prompts — Items in the "Chat commands" sub-menu
+ // Correct control IDs: ai-chat-explain, ai-chat-summarize, ai-chat-highlight-key-points
+ tinymceai_quickactions_chat_prompts: [],
+
+ // tinymceai_quickactions_change_tone_menu — Items in the "Change tone" sub-menu
+ // Correct control IDs: ai-quickactions-tone-casual, ai-quickactions-tone-direct,
+ // ai-quickactions-tone-friendly, ai-quickactions-tone-confident, ai-quickactions-tone-professional
+ tinymceai_quickactions_change_tone_menu: [
+ 'ai-quickactions-tone-casual',
+ 'ai-quickactions-tone-direct',
+ 'ai-quickactions-tone-friendly',
+ 'ai-quickactions-tone-confident',
+ 'ai-quickactions-tone-professional'
+ ],
+
+ // tinymceai_languages — Languages in the "Translate" sub-menu
+ // Each item: { title: string, language: string }
+ tinymceai_languages: [
+ { title: 'English', language: 'english' },
+ { title: 'Chinese (Simplified)', language: 'chinese' },
+ { title: 'Spanish', language: 'spanish' },
+ { title: 'German', language: 'german' },
+ { title: 'Japanese', language: 'japanese' },
+ { title: 'Portuguese', language: 'portuguese' },
+ { title: 'Korean', language: 'korean' },
+ { title: 'Italian', language: 'italian' },
+ { title: 'Russian', language: 'russian' },
+ { title: 'French', language: 'french' }
+ ],
+
+ // tinymceai_quickactions_custom — Custom commands in the "Other" sub-menu
+ // Chat type: { type: 'chat', title: string, prompt: string }
+ // Action type: { type: 'action', title: string, prompt: string, model: string }
+ tinymceai_quickactions_custom: [
+ {
+ type: 'chat',
+ title: 'Generate Outline',
+ prompt: 'Create a detailed outline for the selected content'
+ },
+ {
+ type: 'action',
+ title: 'Convert to Table',
+ prompt: 'Convert this data into an HTML table',
+ model: 'gpt-4.1'
+ },
+ {
+ type: 'chat',
+ title: 'Explain Like I\'m 5',
+ prompt: 'Explain the selected content as if I am a 5 year old child'
+ }
+ ],
+
+ // tinymceai_chat_welcome_message — Custom HTML welcome message in chat sidebar
+ tinymceai_chat_welcome_message: 'Welcome to AI Chat!
I can help you write, edit, review, and brainstorm. Ask me anything or use the quick actions above.
',
+
+ // tinymceai_chat_fetch_sources — Provides a list of external sources users can attach
+ // Returns Promise
+ tinymceai_chat_fetch_sources: () => {
+ console.log('[tinymceai] tinymceai_chat_fetch_sources called');
+ return Promise.resolve([
+ {
+ label: 'Sample Documents',
+ icon: 'document-properties',
+ sources: [
+ { label: 'Company Guidelines', id: 'doc-guidelines', type: 'web-resource' },
+ { label: 'Product Roadmap', id: 'doc-roadmap', type: 'file' },
+ { label: 'FAQ Document', id: 'doc-faq', type: 'web-resource' }
+ ]
+ }
+ ]);
+ },
+
+ // tinymceai_chat_fetch_source — Fetches a specific external source by ID
+ // Returns Promise — { type: 'file', file: File } or { type: 'web-resource', url: string }
+ tinymceai_chat_fetch_source: (id: string) => {
+ console.log('[tinymceai] tinymceai_chat_fetch_source called with id:', id);
+ if (id === 'doc-guidelines') {
+ return Promise.resolve({ type: 'web-resource', url: 'https://www.tiny.cloud' });
+ } else if (id === 'doc-roadmap') {
+ const blob = new Blob(['Sample roadmap content for testing'], { type: 'text/plain' });
+ const file = new File([blob], 'roadmap.txt', { type: 'text/plain' });
+ return Promise.resolve({ type: 'file', file: file });
+ } else {
+ return Promise.resolve({ type: 'web-resource', url: 'https://www.tiny.cloud/docs' });
+ }
+ },
+ },
+ toolbar: 'tinymceai-chat tinymceai-review tinymceai-quickactions',
+ name: 'tinymceai',
+});
diff --git a/src/app/configs/tinymcespellchecker.ts b/src/app/configs/tinymcespellchecker.ts
new file mode 100644
index 0000000..189f2d7
--- /dev/null
+++ b/src/app/configs/tinymcespellchecker.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'tinymcespellchecker',
+ config: {}
+};
diff --git a/src/app/configs/types.ts b/src/app/configs/types.ts
new file mode 100644
index 0000000..2f10ef4
--- /dev/null
+++ b/src/app/configs/types.ts
@@ -0,0 +1,15 @@
+export interface TinymceAIParams {
+ jwtServerURL: string;
+ apiKey: string;
+};
+export type Config = {
+ name: string;
+ toolbar?: string;
+ config: any;
+};
+export type PluginConfig = Config | ((params: Params) => Config);
+export interface Options {
+ excludePlugins?: string[];
+ overrideConfig?: {};
+}
+export interface Params extends TinymceAIParams {}
\ No newline at end of file
diff --git a/src/app/configs/typography.ts b/src/app/configs/typography.ts
new file mode 100644
index 0000000..c4daa17
--- /dev/null
+++ b/src/app/configs/typography.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'typography',
+ toolbar: 'typography',
+ config: {}
+};
diff --git a/src/app/configs/uploadcare.ts b/src/app/configs/uploadcare.ts
new file mode 100644
index 0000000..32ff8b5
--- /dev/null
+++ b/src/app/configs/uploadcare.ts
@@ -0,0 +1,7 @@
+export default {
+ name: 'uploadcare',
+ toolbar: 'uploadcare',
+ config: {
+ uploadcare_public_key: '6ff5776be9bb64e10023',
+ }
+};
diff --git a/src/app/configs/visualblocks.ts b/src/app/configs/visualblocks.ts
new file mode 100644
index 0000000..2370da9
--- /dev/null
+++ b/src/app/configs/visualblocks.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'visualblocks',
+ toolbar: 'visualblocks',
+ config: {}
+};
diff --git a/src/app/configs/visualchars.ts b/src/app/configs/visualchars.ts
new file mode 100644
index 0000000..4b818ec
--- /dev/null
+++ b/src/app/configs/visualchars.ts
@@ -0,0 +1,5 @@
+export default {
+ name: 'visualchars',
+ toolbar: 'visualchars',
+ config: {}
+};
diff --git a/src/app/configs/wordcount.ts b/src/app/configs/wordcount.ts
new file mode 100644
index 0000000..df05069
--- /dev/null
+++ b/src/app/configs/wordcount.ts
@@ -0,0 +1,4 @@
+export default {
+ name: 'wordcount',
+ config: {}
+};
diff --git a/tsconfig.json b/tsconfig.json
index 8326b70..9731ffa 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,9 +1,7 @@
-/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
- "outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
@@ -18,6 +16,8 @@
"importHelpers": true,
"target": "es2022",
"module": "es2022",
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
"lib": [
"es2022",
"dom"
diff --git a/yarn.lock b/yarn.lock
index 2c2b880..e4cf63a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2770,10 +2770,10 @@
resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==
-"@tinymce/tinymce-angular@9.1.2-rc.20260115041456592.sha0aa5d93":
- version "9.1.2-rc.20260115041456592.sha0aa5d93"
- resolved "https://registry.yarnpkg.com/@tinymce/tinymce-angular/-/tinymce-angular-9.1.2-rc.20260115041456592.sha0aa5d93.tgz#fffd10e8c2e63025f76041fff163c089bc8af68d"
- integrity sha512-DjfDZ5aRvawLY1JqJAhKgCFdilEI/oDkOqIkGouOR9LHT6TM424sj4K+utAmwzDnIdYf9SrkX53qgFOluJdFuw==
+"@tinymce/tinymce-angular@^9.1.1":
+ version "9.1.1"
+ resolved "https://registry.yarnpkg.com/@tinymce/tinymce-angular/-/tinymce-angular-9.1.1.tgz#8a3a986af109e8466a728449962c99879533a1f2"
+ integrity sha512-4W15Ruheqg9hnCUhtNoYX/SnvytT9N0ugv1Tk/aXXXVQMUCQwL3MQBNn8SNer8iZvkDbP+11au0zWW9dWVGSVA==
dependencies:
tslib "^2.3.0"