Enhance email domain management and switch to Monaco Editor

Updated EmailDomainsController to support password retrieval.
Modified repository and service interfaces to include a new
optional `returnPassword` parameter. Transitioned from Tiptap
to Monaco Editor for rich text editing. Improved UI for
EmailDomainEdit and TemplateEdit components, added password
visibility toggle in EmailDomains, and refined data grid
layout in Templates.
This commit is contained in:
David Headrick 2025-03-04 18:54:57 -06:00
parent 2bdb1a8de6
commit d7b00cf335
11 changed files with 386 additions and 928 deletions

View File

@ -17,16 +17,19 @@ namespace Surge365.MassEmailReact.Server.Controllers
}
[HttpGet("GetAll")]
public async Task<IActionResult> GetAll([FromQuery] bool? activeOnly)
public async Task<IActionResult> GetAll([FromQuery] bool? activeOnly, bool? returnPassword = null)
{
var emailDomains = await _emailDomainService.GetAllAsync(activeOnly == null || activeOnly.Value ? true : false);
bool activeOnlyValue = activeOnly == null || activeOnly.Value ? true : false;
bool returnPasswordValue = returnPassword != null && returnPassword.Value ? true : false;
var emailDomains = await _emailDomainService.GetAllAsync(activeOnlyValue, returnPasswordValue);
return Ok(emailDomains);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetByKey(int id)
public async Task<IActionResult> GetByKey(int id, bool? returnPassword = null)
{
var emailDomain = await _emailDomainService.GetByIdAsync(id);
bool returnPasswordValue = returnPassword != null && returnPassword.Value ? true : false;
var emailDomain = await _emailDomainService.GetByIdAsync(id, returnPasswordValue);
return emailDomain is not null ? Ok(emailDomain) : NotFound($"EmailDomain with key '{id}' not found.");
}

View File

@ -6,8 +6,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces
{
public interface IEmailDomainRepository
{
Task<EmailDomain?> GetByIdAsync(int id);
Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true);
Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false);
Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false);
Task<int?> CreateAsync(EmailDomain emailDomain);
Task<bool> UpdateAsync(EmailDomain emailDomain);
}

View File

@ -6,8 +6,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces
{
public interface IEmailDomainService
{
Task<EmailDomain?> GetByIdAsync(int id);
Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true);
Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false);
Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false);
Task<int?> CreateAsync(EmailDomainUpdateDto emailDomainDto);
Task<bool> UpdateAsync(EmailDomainUpdateDto emailDomainDto);
}

View File

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.Intrinsics.Arm;
using System.Threading.Tasks;
namespace Surge365.MassEmailReact.Infrastructure.Repositories
@ -35,22 +36,35 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
#endif
}
public async Task<EmailDomain?> GetByIdAsync(int id)
public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
{
ArgumentNullException.ThrowIfNull(_config);
ArgumentNullException.ThrowIfNull(_connectionStringName);
using SqlConnection conn = new SqlConnection(ConnectionString);
return (await conn.QueryAsync<EmailDomain>("mem_get_domain_by_id", new { domain_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
EmailDomain? domain = (await conn.QueryAsync<EmailDomain>("mem_get_domain_by_id", new { domain_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
if (domain != null && !returnPassword)
{
domain.Password = "";
}
return domain;
}
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true)
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
{
ArgumentNullException.ThrowIfNull(_config);
ArgumentNullException.ThrowIfNull(_connectionStringName);
using SqlConnection conn = new SqlConnection(ConnectionString);
return (await conn.QueryAsync<EmailDomain>("mem_get_domain_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
List<EmailDomain> domains = (await conn.QueryAsync<EmailDomain>("mem_get_domain_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
if(!returnPassword)
{
foreach (var domain in domains)
{
domain.Password = "";
}
}
return domains;
}
public async Task<int?> CreateAsync(EmailDomain emailDomain)

View File

@ -18,14 +18,14 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
_config = config;
}
public async Task<EmailDomain?> GetByIdAsync(int id)
public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
{
return await _emailDomainRepository.GetByIdAsync(id);
return await _emailDomainRepository.GetByIdAsync(id, returnPassword);
}
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true)
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
{
return await _emailDomainRepository.GetAllAsync(activeOnly);
return await _emailDomainRepository.GetAllAsync(activeOnly, returnPassword);
}
public async Task<int?> CreateAsync(EmailDomainUpdateDto emailDomainDto)

View File

@ -12,13 +12,11 @@
"@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.1.1",
"@hookform/resolvers": "^4.1.2",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5",
"@mui/x-charts": "^7.27.1",
"@mui/x-data-grid": "^7.27.1",
"@tiptap/pm": "^2.11.5",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"admin-lte": "4.0.0-beta3",
"bootstrap": "^5.3.3",
"dayjs": "^1.11.13",
@ -1195,6 +1193,29 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@monaco-editor/loader": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
"integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
"license": "MIT",
"dependencies": {
"state-local": "^1.0.6"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.5.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz",
@ -1688,12 +1709,6 @@
"integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==",
"license": "MIT"
},
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@restart/hooks": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
@ -2042,391 +2057,6 @@
"tslib": "^2.8.0"
}
},
"node_modules/@tiptap/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz",
"integrity": "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.5.tgz",
"integrity": "sha512-MZfcRIzKRD8/J1hkt/eYv49060GTL6qGR3NY/oTDuw2wYzbQXXLEbjk8hxAtjwNn7G+pWQv3L+PKFzZDxibLuA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.5.tgz",
"integrity": "sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.5.tgz",
"integrity": "sha512-rx+rMd7EEdht5EHLWldpkzJ56SWYA9799b33ustePqhXd6linnokJCzBqY13AfZ9+xp3RsR6C0ZHI9GGea0tIA==",
"license": "MIT",
"dependencies": {
"tippy.js": "^6.3.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.5.tgz",
"integrity": "sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-code": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.5.tgz",
"integrity": "sha512-xOvHevNIQIcCCVn9tpvXa1wBp0wHN/2umbAZGTVzS+AQtM7BTo0tz8IyzwxkcZJaImONcUVYLOLzt2AgW1LltA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.5.tgz",
"integrity": "sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-document": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.5.tgz",
"integrity": "sha512-7I4BRTpIux2a0O2qS3BDmyZ5LGp3pszKbix32CmeVh7lN9dV7W5reDqtJJ9FCZEEF+pZ6e1/DQA362dflwZw2g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.5.tgz",
"integrity": "sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.5.tgz",
"integrity": "sha512-HsMI0hV5Lwzm530Z5tBeyNCBNG38eJ3qjfdV2OHlfSf3+KOEfn6a5AUdoNaZO02LF79/8+7BaYU2drafag9cxQ==",
"license": "MIT",
"dependencies": {
"tippy.js": "^6.3.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.5.tgz",
"integrity": "sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz",
"integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.5.tgz",
"integrity": "sha512-x/MV53psJ9baRcZ4k4WjnCUBMt8zCX7mPlKVT+9C/o+DEs/j/qxPLs95nHeQv70chZpSwCQCt93xMmuF0kPoAg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-history": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz",
"integrity": "sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.5.tgz",
"integrity": "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.5.tgz",
"integrity": "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.5.tgz",
"integrity": "sha512-Mp5RD/pbkfW1vdc6xMVxXYcta73FOwLmblQlFNn/l/E5/X1DUSA4iGhgDDH4EWO3swbs03x2f7Zka/Xoj3+WLg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.5.tgz",
"integrity": "sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.5.tgz",
"integrity": "sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.5.tgz",
"integrity": "sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-text": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.5.tgz",
"integrity": "sha512-Gq1WwyhFpCbEDrLPIHt5A8aLSlf8bfz4jm417c8F/JyU0J5dtYdmx0RAxjnLw1i7ZHE7LRyqqAoS0sl7JHDNSQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-text-style": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.5.tgz",
"integrity": "sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/pm": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.5.tgz",
"integrity": "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.2.1",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.23.0",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.4.1",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.3",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.37.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/react": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.11.5.tgz",
"integrity": "sha512-Dp8eHL1G+R/C4+QzAczyb3t1ovexEIZx9ln7SGEM+cT1KHKAw9XGPRgsp92+NQaYI+EdEb/YqoBOSzQcd18/OQ==",
"license": "MIT",
"dependencies": {
"@tiptap/extension-bubble-menu": "^2.11.5",
"@tiptap/extension-floating-menu": "^2.11.5",
"@types/use-sync-external-store": "^0.0.6",
"fast-deep-equal": "^3",
"use-sync-external-store": "^1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^2.7.0",
"@tiptap/pm": "^2.7.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.5.tgz",
"integrity": "sha512-SLI7Aj2ruU1t//6Mk8f+fqW+18uTqpdfLUJYgwu0CkqBckrkRZYZh6GVLk/02k3H2ki7QkFxiFbZrdbZdng0JA==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^2.11.5",
"@tiptap/extension-blockquote": "^2.11.5",
"@tiptap/extension-bold": "^2.11.5",
"@tiptap/extension-bullet-list": "^2.11.5",
"@tiptap/extension-code": "^2.11.5",
"@tiptap/extension-code-block": "^2.11.5",
"@tiptap/extension-document": "^2.11.5",
"@tiptap/extension-dropcursor": "^2.11.5",
"@tiptap/extension-gapcursor": "^2.11.5",
"@tiptap/extension-hard-break": "^2.11.5",
"@tiptap/extension-heading": "^2.11.5",
"@tiptap/extension-history": "^2.11.5",
"@tiptap/extension-horizontal-rule": "^2.11.5",
"@tiptap/extension-italic": "^2.11.5",
"@tiptap/extension-list-item": "^2.11.5",
"@tiptap/extension-ordered-list": "^2.11.5",
"@tiptap/extension-paragraph": "^2.11.5",
"@tiptap/extension-strike": "^2.11.5",
"@tiptap/extension-text": "^2.11.5",
"@tiptap/extension-text-style": "^2.11.5",
"@tiptap/pm": "^2.11.5"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@ -2543,28 +2173,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
@ -2614,12 +2222,6 @@
"@types/react": "*"
}
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@types/warning": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
@ -2931,6 +2533,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/babel-plugin-macros": {
@ -3161,12 +2764,6 @@
"node": ">= 6"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -3368,18 +2965,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -3633,6 +3218,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
@ -4091,15 +3677,6 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -4145,29 +3722,6 @@
"yallist": "^3.0.2"
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -4205,6 +3759,13 @@
"node": "*"
}
},
"node_modules/monaco-editor": {
"version": "0.52.2",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
"license": "MIT",
"peer": true
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -4271,12 +3832,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -4462,201 +4017,6 @@
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
"license": "MIT"
},
"node_modules/prosemirror-changeset": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz",
"integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.0.tgz",
"integrity": "sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz",
"integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz",
"integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz",
"integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
"integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz",
"integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz",
"integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.20.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
"integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.1.tgz",
"integrity": "sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz",
"integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.19.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.0.tgz",
"integrity": "sha512-gg1tAfH1sqpECdhIHOA/aLg2VH3ROKBWQ4m8Qp9mBKrOxQRW61zc+gMCI8nh22gnBzd1t2u1/NPLmO3nAa3ssg==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
"integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.4.tgz",
"integrity": "sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.2",
"prosemirror-model": "^1.24.1",
"prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.37.2"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz",
"integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.38.0.tgz",
"integrity": "sha512-O45kxXQTaP9wPdXhp8TKqCR+/unS/gnfg9Q93svQcB3j0mlp2XSPAmsPefxHADwzC+fbNS404jqRxm3UQaGvgw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -4667,15 +4027,6 @@
"node": ">=6"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4949,12 +4300,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@ -5043,6 +4388,12 @@
"node": ">=0.10.0"
}
},
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"license": "MIT"
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@ -5093,15 +4444,6 @@
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
"license": "MIT"
},
"node_modules/tippy.js": {
"version": "6.3.7",
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
"integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.9.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -5208,12 +4550,6 @@
"typescript": ">=4.8.4 <5.8.0"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/uncontrollable": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
@ -5358,12 +4694,6 @@
}
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",

View File

@ -15,13 +15,11 @@
"@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.1.1",
"@hookform/resolvers": "^4.1.2",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5",
"@mui/x-charts": "^7.27.1",
"@mui/x-data-grid": "^7.27.1",
"@tiptap/pm": "^2.11.5",
"@tiptap/react": "^2.11.5",
"@tiptap/starter-kit": "^2.11.5",
"admin-lte": "4.0.0-beta3",
"bootstrap": "^5.3.3",
"dayjs": "^1.11.13",

View File

@ -19,7 +19,7 @@ type EmailDomainEditProps = {
open: boolean;
emailDomain: EmailDomain | null;
onClose: () => void;
onSave: (updatedEmailDomain: EmailDomain) => void;
onSave: (updatedEmailDomain: EmailDomain, password: string) => void;
};
const schema = yup.object().shape({
@ -34,9 +34,13 @@ const schema = yup.object().shape({
(d) => d.name.toLowerCase() === value?.toLowerCase() && (d.id === 0 || d.id !== this.parent.id)
);
}),
emailAddress: yup.string().required("Email address is required"),
emailAddress: yup.string().email("Invalid email").required("Email address is required"),
username: yup.string().required("Username is required"),
password: yup.string().required("Password is required"),
password: yup.string().default("")
.test("required-if-new", "NamePassword is required", function (value) {
if (this.parent.id > 0) return true;
else return value.length > 0;
}),
isActive: yup.boolean().default(true),
displayOrder: yup.number().required("Display order is required"),
});
@ -54,6 +58,7 @@ const defaultEmailDomain: EmailDomain = {
const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEditProps) => {
const isNew = !emailDomain || emailDomain.id === 0;
const setupData: SetupData = useSetupData();
const originalEmailDomain: EmailDomain | null = emailDomain ? { ...emailDomain } : null;
const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<EmailDomain>({
mode: "onBlur",
@ -84,7 +89,7 @@ const EmailDomainEdit = ({ open, emailDomain, onClose, onSave }: EmailDomainEdit
if (!response.ok) throw new Error(isNew ? "Failed to create" : "Failed to update");
const updatedEmailDomain = await response.json();
onSave(updatedEmailDomain);
onSave(updatedEmailDomain, isNew || formData.password ? formData.password : originalEmailDomain?.password);
onClose();
} catch (error) {
console.error("Update error:", error);

View File

@ -9,12 +9,14 @@ import {
Button,
Switch,
FormControlLabel,
Grid,
Box,
Paper,
} from "@mui/material";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { EditorContent, useEditor, Editor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Editor from "@monaco-editor/react";
// Assuming these types and context are defined elsewhere
import Template from "@/types/template";
@ -31,13 +33,13 @@ const schema = yup.object().shape({
id: yup.number().default(0),
name: yup.string().required("Name is required"),
domainId: yup.number().required("Domain is required").moreThan(0, "Domain is required"),
description: yup.string().required("Description is required"),
description: yup.string().default(""),
htmlBody: yup.string().required("HTML Body is required"),
subject: yup.string().required("Subject is required"),
toName: yup.string().default(""),
fromName: yup.string().required("From Name is required"),
fromEmail: yup.string().email("Invalid email").required("From Email is required"),
replyToEmail: yup.string().email("Invalid email").required("Reply To Email is required"),
fromEmail: yup.string().default(""),
replyToEmail: yup.string().default(""),
clickTracking: yup.boolean().default(false),
openTracking: yup.boolean().default(false),
categoryXml: yup.string().default(""),
@ -71,6 +73,7 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
handleSubmit,
reset,
setValue,
watch,
formState: { errors },
} = useForm<Template>({
mode: "onBlur",
@ -80,23 +83,19 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
});
const [loading, setLoading] = useState(false);
const editor = useEditor({
extensions: [StarterKit],
content: template?.htmlBody || '',
onUpdate: ({ editor }) => {
const html = editor.getHTML();
setValue('htmlBody', html, { shouldValidate: true });
},
});
const [isExpanded, setIsExpanded] = useState(false);
const [isPreviewMode, setIsPreviewMode] = useState(false);
useEffect(() => {
if (open && editor) {
editor.commands.setContent(template?.htmlBody || '');
if (open) {
reset(template || defaultTemplate, { keepDefaultValues: true });
}
}, [open, template, editor]);
}, [open, template, reset]);
const handleSave = async (formData: Template) => {
const domain = setupData.emailDomains.find(el => el.id === formData.domainId);
formData.fromEmail = domain.emailAddress;
formData.replyToEmail = domain.emailAddress;
const apiUrl = isNew ? "/api/templates" : `/api/templates/${formData.id}`;
const method = isNew ? "POST" : "PUT";
setLoading(true);
@ -119,154 +118,189 @@ const TemplateEdit = ({ open, template, onClose, onSave }: TemplateEditProps) =>
}
};
type MenuProps = {
editor: Editor | null;
};
const MenuBar = ({ editor }: MenuProps) => {
if (!editor) return null;
return (
<div>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={!editor.can().toggleBold()}
>
Bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
disabled={!editor.can().toggleItalic()}
>
Italic
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
>
Bullet List
</button>
<button
onClick={() => editor.chain().focus().toggleOrderedList().run()}
>
Ordered List
</button>
</div>
);
};
const renderEditorOrPreview = (height: string) => (
<Box sx={{ flexGrow: 1, overflow: "hidden" }}>
{isPreviewMode ? (
<iframe
srcDoc={watch("htmlBody")}
sandbox="allow-same-origin"
style={{ width: "100%", height, border: "none" }}
/>
) : (
<Editor
height={height}
defaultLanguage="html"
value={watch("htmlBody")}
onChange={(value) => setValue("htmlBody", value || "", { shouldValidate: true })}
options={{ wordWrap: "on" }}
/>
)}
{!isExpanded && errors.htmlBody && (
<p style={{ color: "red", marginTop: "8px" }}>{errors.htmlBody.message}</p>
)}
</Box>
);
return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle>{isNew ? "Add Template" : "Edit Template id=" + template?.id}</DialogTitle>
<DialogContent>
<Controller
name="domainId"
control={control}
render={({ field }) => (
<Autocomplete
options={setupData.emailDomains}
getOptionLabel={(option) => option.name}
value={setupData.emailDomains.find((d) => d.id === field.value) || null}
onChange={(_, newValue) => field.onChange(newValue ? newValue.id : null)}
renderInput={(params) => (
<TextField
{...params}
label="Domain"
margin="dense"
error={!!errors.domainId}
helperText={errors.domainId?.message}
/>
)}
/>
)}
/>
<TextField
{...register("name")}
label="Name"
fullWidth
margin="dense"
error={!!errors.name}
helperText={errors.name?.message}
/>
<TextField
{...register("description")}
label="Description"
fullWidth
margin="dense"
error={!!errors.description}
helperText={errors.description?.message}
/>
<Controller
name="htmlBody"
control={control}
render={({ field }) => (
<div>
<MenuBar editor={editor} />
<EditorContent editor={editor} />
{errors.htmlBody && (
<p style={{ color: 'red' }}>{errors.htmlBody.message}</p>
)}
</div>
)}
/>
<TextField
{...register("subject")}
label="Subject"
fullWidth
margin="dense"
error={!!errors.subject}
helperText={errors.subject?.message}
/>
<TextField
{...register("fromName")}
label="From Name"
fullWidth
margin="dense"
error={!!errors.fromName}
helperText={errors.fromName?.message}
/>
<TextField
{...register("fromEmail")}
label="From Email"
fullWidth
margin="dense"
error={!!errors.fromEmail}
helperText={errors.fromEmail?.message}
/>
<TextField
{...register("replyToEmail")}
label="Reply To Email"
fullWidth
margin="dense"
error={!!errors.replyToEmail}
helperText={errors.replyToEmail?.message}
/>
<FormControlLabel
control={<Switch {...register("clickTracking")} />}
label="Click Tracking"
/>
<FormControlLabel
control={<Switch {...register("openTracking")} />}
label="Open Tracking"
/>
<TextField
{...register("categoryXml")}
label="Category XML"
fullWidth
margin="dense"
multiline
rows={4}
error={!!errors.categoryXml}
helperText={errors.categoryXml?.message}
/>
<Controller
name="isActive"
control={control}
render={({ field }) => (
<FormControlLabel
control={<Switch checked={field.value} onChange={(e) => field.onChange(e.target.checked)} />}
label="Active"
/>
)}
/>
<Dialog
open={open}
onClose={onClose}
maxWidth={isExpanded ? false : "lg"}
fullScreen={isExpanded}
sx={{ "& .MuiDialog-paper": { width: isExpanded ? "100%" : "auto" } }}
>
<DialogTitle>
{isExpanded
? "Editing HTML Body"
: isNew
? "Add Template"
: `Edit Template id=${template?.id}`}
</DialogTitle>
<DialogContent sx={{ p: isExpanded ? 0 : 2, display: "flex", flexDirection: "column" }}>
{isExpanded ? (
<Box sx={{ height: "100%", display: "flex", flexDirection: "column" }}>
<Box
sx={{
p: 1,
backgroundColor: "#f5f5f5",
display: "flex",
justifyContent: "space-between",
}}
>
<Button onClick={() => setIsExpanded(false)}>Collapse</Button>
<Button onClick={() => setIsPreviewMode(!isPreviewMode)}>
{isPreviewMode ? "Edit" : "Preview"}
</Button>
</Box>
{renderEditorOrPreview("calc(100vh - 120px)")}
</Box>
) : (
<Grid container spacing={2}>
{/* Top Section: Two Columns for All Fields Except HTML */}
<Grid item xs={12}>
<Grid container spacing={2} sx={{ marginTop: 0 }}>
<Grid item xs={6} >
<TextField
{...register("name")}
label="Name"
fullWidth
error={!!errors.name}
helperText={errors.name?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("description")}
label="Description"
fullWidth
error={!!errors.description}
helperText={errors.description?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register("subject")}
label="Subject"
fullWidth
error={!!errors.subject}
helperText={errors.subject?.message}
/>
</Grid>
<Grid item xs={6}>
<TextField
{...register("fromName")}
label="From Name"
fullWidth
error={!!errors.fromName}
helperText={errors.fromName?.message}
/>
</Grid>
<Grid item xs={6}>
<Controller
name="domainId"
control={control}
render={({ field }) => (
<Autocomplete
options={setupData.emailDomains}
getOptionLabel={(option) => option.name}
value={setupData.emailDomains.find((d) => d.id === field.value) || null}
onChange={(_, newValue) => field.onChange(newValue ? newValue.id : null)}
renderInput={(params) => (
<TextField
{...params}
label="Domain"
error={!!errors.domainId}
helperText={errors.domainId?.message}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={12}>
<Controller
name="clickTracking"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
}
label="Click Tracking"
/>
)}
/>
<Controller
name="openTracking"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
}
label="Open Tracking"
/>
)}
/>
<Controller
name="isActive"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
/>
}
label="Active"
/>
)}
/>
</Grid>
</Grid>
</Grid>
{/* Bottom Section: Single Column for HTML Control */}
<Grid item xs={12}>
<Box sx={{ display: "flex", justifyContent: "space-between", my: 1 }}>
<Button onClick={() => setIsExpanded(true)}>Expand Editor</Button>
<Button onClick={() => setIsPreviewMode(!isPreviewMode)}>
{isPreviewMode ? "Edit" : "Preview"}
</Button>
</Box>
<Paper variant="outlined" sx={{ p: 2 }}>
{renderEditorOrPreview("300px")}
</Paper>
</Grid>
</Grid>
)}
</DialogContent>
<DialogActions>
<Button onClick={onClose} disabled={loading}>

View File

@ -1,6 +1,7 @@
import { useState, useRef } from 'react';
import { useSetupData, SetupData } from "@/context/SetupDataContext";
import EditIcon from '@mui/icons-material/Edit';
import { Lock, LockOpen } from "@mui/icons-material";
import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, Button, CircularProgress, IconButton } from '@mui/material';
import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
import EmailDomain from '@/types/emailDomain';
@ -11,6 +12,8 @@ function EmailDomains() {
const setupData: SetupData = useSetupData();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [emailDomainsWithPasswords, setEmailDomainsWithPasswords] = useState<EmailDomain[] | null>(null);
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const gridContainerRef = useRef<HTMLDivElement | null>(null);
const [selectedRow, setSelectedRow] = useState<EmailDomain | null>(null);
const [open, setOpen] = useState<boolean>(false);
@ -30,11 +33,43 @@ function EmailDomains() {
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
{ field: "emailAddress", headerName: "Email Address", flex: 1, minWidth: 200 },
{ field: "username", headerName: "Username", flex: 1, minWidth: 150 },
{ field: "password", headerName: "Password", flex: 1, minWidth: 150 },
{
field: "password",
headerName: "Password",
width: 175,
renderHeader: () => (
<div style={{ display: "flex", alignItems: "center" }}>
Password
<IconButton size="small" onClick={(e) => { e.stopPropagation(); togglePasswordVisibility(); }} sx={{ marginLeft: 1 }}>
{isPasswordVisible ? <LockOpen /> : <Lock />}
</IconButton>
</div>
),
renderCell: (params: GridRenderCellParams<EmailDomain>) =>
isPasswordVisible ? params.value : "••••••••",
},
{ field: "displayOrder", headerName: "Display Order", width: 120 },
{ field: "isActive", headerName: "Active", width: 75 },
];
const togglePasswordVisibility = async () => {
if (isPasswordVisible) {
setIsPasswordVisible(false);
setEmailDomainsWithPasswords(null); // Revert to setupData.emailDomains (masked)
} else {
try {
setIsPasswordVisible(true);
await loadEmailDomainsWithPasswords();
} catch (error) {
console.error("Error fetching email domains:", error);
}
}
};
const loadEmailDomainsWithPasswords = async () => {
const response = await fetch("/api/emailDomains/GetAll?activeOnly=false&returnPassword=true");
const data = await response.json();
setEmailDomainsWithPasswords(data);
}
const handleNew = () => {
setSelectedRow(null);
setOpen(true);
@ -45,9 +80,27 @@ function EmailDomains() {
setOpen(true);
};
const handleUpdateRow = (updatedRow: EmailDomain) => {
setupData.setEmailDomains(updatedRow);
const handleSaveRow = (savedRow: EmailDomain, password: string) => {
setupData.setEmailDomains(savedRow);
if (isPasswordVisible) {
if (password)
updateEmailDomains({ ...savedRow, password: password }); // Update local state
else
updateEmailDomains({ ...savedRow });
}
};
const updateEmailDomains = (updatedEmailDomain: EmailDomain) => {
setEmailDomainsWithPasswords((prev) => {
if (prev == null) return null;
const exists = prev.some((e) => e.id === updatedEmailDomain.id);
return exists
? prev.map((domain) => (domain.id === updatedEmailDomain.id ? updatedEmailDomain : domain))
: [...prev, updatedEmailDomain];
});
};
const displayRows = isPasswordVisible ? (emailDomainsWithPasswords ?? setupData.emailDomains) : setupData.emailDomains;
return (
<Box ref={gridContainerRef} sx={{
@ -59,29 +112,38 @@ function EmailDomains() {
}}>
<Box sx={{ position: 'absolute', inset: 0 }}>
{isMobile ? (
<List>
{setupData.emailDomains.map((row) => (
<Card key={row.id} sx={{ marginBottom: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<CardContent>
<Typography variant="h6">{row.name}</Typography>
<Typography variant="body2">ID: {row.id}</Typography>
<Typography variant="body2">Email: {row.emailAddress}</Typography>
<Typography variant="body2">Username: {row.username}</Typography>
<Typography variant="body2">Password: {row.password}</Typography>
<Typography variant="body2">Display Order: {row.displayOrder}</Typography>
<Typography variant="body2">Active: {row.isActive ? "Yes" : "No"}</Typography>
</CardContent>
<IconButton onClick={(e) => { e.stopPropagation(); handleEdit(row); }}>
<EditIcon />
</IconButton>
</Box>
</Card>
))}
</List>
<><Box sx={{ display: "flex", justifyContent: "flex-end", marginBottom: 2 }}>
<Button
onClick={togglePasswordVisibility}
startIcon={isPasswordVisible ? <LockOpen /> : <Lock />}
>
{isPasswordVisible ? "Hide Passwords" : "Show Passwords"}
</Button>
</Box>
<List>
{displayRows.map((row) => (
<Card key={row.id} sx={{ marginBottom: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<CardContent>
<Typography variant="h6">{row.name}</Typography>
<Typography variant="body2">ID: {row.id}</Typography>
<Typography variant="body2">Email: {row.emailAddress}</Typography>
<Typography variant="body2">Username: {row.username}</Typography>
<Typography variant="body2">Password: {row.password}</Typography>
<Typography variant="body2">Display Order: {row.displayOrder}</Typography>
<Typography variant="body2">Active: {row.isActive ? "Yes" : "No"}</Typography>
</CardContent>
<IconButton onClick={(e) => { e.stopPropagation(); handleEdit(row); }}>
<EditIcon />
</IconButton>
</Box>
</Card>
))}
</List>
</>
) : (
<DataGrid
rows={setupData.emailDomains}
rows={displayRows}
columns={columns}
autoPageSize
sx={{ minWidth: "600px" }}
@ -94,12 +156,16 @@ function EmailDomains() {
onClick={() => handleNew()}
sx={{ marginRight: 2 }}
>
{setupData.emailDomainsLoading ? <CircularProgress size={24} color="inherit" /> : "Add New"}
Add New
</Button>
<Button
variant="contained"
color="primary"
onClick={() => setupData.reloadEmailDomains()}
onClick={() => {
if (isPasswordVisible)
loadEmailDomainsWithPasswords();
setupData.reloadEmailDomains();
}}
sx={{ marginRight: 2 }}
>
{setupData.emailDomainsLoading ? <CircularProgress size={24} color="inherit" /> : "Refresh"}
@ -133,7 +199,7 @@ function EmailDomains() {
open={open}
emailDomain={selectedRow}
onClose={() => setOpen(false)}
onSave={handleUpdateRow}
onSave={handleSaveRow}
/>
)}
</Box>

View File

@ -27,12 +27,19 @@ function Templates() {
),
},
{ field: "id", headerName: "ID", width: 60 },
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
{ field: "domainId", headerName: "Domain ID", width: 100 },
{ field: "description", headerName: "Description", flex: 1, minWidth: 200 },
{ field: "domainId", headerName: "Domain", maxWidth: 200 },
{ field: "name", headerName: "Name", flex: 1, minWidth: 160, maxWidth:600 },
{ field: "description", headerName: "Description", flex: 1, minWidth: 200, maxWidth: 600 },
{ field: "isActive", headerName: "Active", width: 75 },
];
const getMaxWidth = () => {
let maxWidth = 0;
for (const column of columns) {
maxWidth += column.width ?? column.maxWidth ?? 0;
}
return maxWidth;
}
const handleNew = () => {
setSelectedRow(null);
setOpen(true);
@ -80,7 +87,8 @@ function Templates() {
rows={setupData.templates}
columns={columns}
autoPageSize
sx={{ minWidth: "600px" }}
sx={{
minWidth: "600px", maxWidth: getMaxWidth() }}
slots={{
toolbar: () => (
<GridToolbarContainer sx={{ display: "flex", alignItems: "center" }}>
@ -90,7 +98,7 @@ function Templates() {
onClick={() => handleNew()}
sx={{ marginRight: 2 }}
>
{setupData.templatesLoading ? <CircularProgress size={24} color="inherit" /> : "Add New"}
{"Add New"}
</Button>
<Button
variant="contained"