further through .tsx conversion
This commit is contained in:
parent
01da06b57b
commit
6979c450e6
@ -1,3 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper.js';
|
||||||
import ElementsUtil from './require/elements-util.js';
|
import ElementsUtil from './require/elements-util.js';
|
||||||
import BaseElements from './require/base-elements.js';
|
import BaseElements from './require/base-elements.js';
|
||||||
|
|
||||||
@ -9,20 +11,20 @@ import CombinedGuild from '../guild-combined.js';
|
|||||||
export default function createConnectionContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild) {
|
export default function createConnectionContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild) {
|
||||||
const statuses = [ 'online', 'away', 'busy', 'invisible' ];
|
const statuses = [ 'online', 'away', 'busy', 'invisible' ];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let content: any[] = [
|
let content: JSX.Element[] = [
|
||||||
{ class: 'item personalize', content: [
|
<div key="personalize" className="item personalize">
|
||||||
{ class: 'icon', content: { tag: 'img', src: './img/pencil-icon.png' } },
|
<div className="icon"><img src="./img/pencil-icon.png"></img></div>
|
||||||
{ content: 'Personalize' }
|
<div>Personalize</div>
|
||||||
] },
|
</div>,
|
||||||
{ class: 'item-spacer' }
|
<div key="spacer-1" className="item-spacer"></div>
|
||||||
];
|
];
|
||||||
content = content.concat(statuses.map(status => {
|
content = content.concat(statuses.map(status => (
|
||||||
return { class: 'item ' + status, content: [
|
<div key={status} className={'item ' + status}>
|
||||||
{ class: 'status-circle' },
|
<div className="status-circle"></div>
|
||||||
{ class: 'status-text', content: status }
|
<div className="status-text">{status}</div>
|
||||||
] };
|
</div>
|
||||||
}));
|
)));
|
||||||
const element = BaseElements.createContextMenu(document, { class: 'member-context', content: content });
|
const element = BaseElements.createContextMenu(document, <div className="member-context">{content}</div>);
|
||||||
|
|
||||||
q.$$$(element, '.personalize').addEventListener('click', async () => {
|
q.$$$(element, '.personalize').addEventListener('click', async () => {
|
||||||
element.removeSelf();
|
element.removeSelf();
|
@ -16,30 +16,37 @@ import createCreateChannelOverlay from './overlay-create-channel';
|
|||||||
import createTokenLogOverlay from './overlay-token-log';
|
import createTokenLogOverlay from './overlay-token-log';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): HTMLElement {
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): Element {
|
||||||
if (ui.activeConnection === null) {
|
if (ui.activeConnection === null) {
|
||||||
LOG.warn('no active connection when creating guild title context menu');
|
LOG.warn('no active connection when creating guild title context menu');
|
||||||
return q.create({}) as HTMLElement;
|
return ReactHelper.createElementFromJSX(<div></div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const menuItems: any[] = [];
|
const menuItems: JSX.Element[] = [];
|
||||||
|
|
||||||
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
||||||
menuItems.push({ class: 'item guild-settings', content: [
|
menuItems.push(
|
||||||
{ class: 'icon', content: BaseElements.Q_COG },
|
<div key="guild-settings" className="item guild-settings">
|
||||||
'Guild Settings'
|
<div className="icon">{BaseElements.COG}</div>
|
||||||
] });
|
<div>Guild Settings</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ui.activeConnection.privileges.includes('modify_channels')) {
|
if (ui.activeConnection.privileges.includes('modify_channels')) {
|
||||||
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
||||||
menuItems.push({ class: 'item-spacer' });
|
menuItems.push(<div key="spacer-1" className="item-spacer"></div>);
|
||||||
}
|
}
|
||||||
menuItems.push({ class: 'item create-channel', content: [
|
menuItems.push(
|
||||||
{ class: 'icon', content: BaseElements.Q_CREATE },
|
<div key="create-channel" className="item create-channel">
|
||||||
'Create Channel'
|
<div className="icon">{BaseElements.CREATE}</div>
|
||||||
] });
|
<div>Create Channel</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ui.activeConnection.privileges.includes('modify_members')) {
|
if (ui.activeConnection.privileges.includes('modify_members')) {
|
||||||
@ -47,21 +54,25 @@ export default function createGuildTitleContextMenu(document: Document, q: Q, ui
|
|||||||
ui.activeConnection.privileges.includes('modify_profile') ||
|
ui.activeConnection.privileges.includes('modify_profile') ||
|
||||||
ui.activeConnection.privileges.includes('modify_channels')
|
ui.activeConnection.privileges.includes('modify_channels')
|
||||||
) {
|
) {
|
||||||
menuItems.push({ class: 'item-spacer' });
|
menuItems.push(<div key="spacer-2" className="item-spacer"></div>);
|
||||||
}
|
}
|
||||||
menuItems.push({ class: 'item create-invite-token', content: [
|
menuItems.push(
|
||||||
{ class: 'icon', content: BaseElements.Q_TOKEN },
|
<div key="create-invite-token" className="item create-invite-token">
|
||||||
'Create Invite Token'
|
<div className="icon">{BaseElements.TOKEN}</div>
|
||||||
] });
|
<div>Create Invite Token</div>
|
||||||
menuItems.push({ class: 'item token-log', content: [
|
</div>
|
||||||
{ class: 'icon', content: BaseElements.Q_TOKEN },
|
);
|
||||||
'Token Log'
|
menuItems.push(
|
||||||
] });
|
<div key="token-log" className="item token-log">
|
||||||
|
<div className="icon">{BaseElements.TOKEN}</div>
|
||||||
|
<div>Token Log</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = BaseElements.createContextMenu(document, {
|
const element = BaseElements.createContextMenu(document, (
|
||||||
class: 'guild-title-context', content: menuItems
|
<div className="guild-title-context">{menuItems}</div>
|
||||||
});
|
));
|
||||||
|
|
||||||
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
||||||
q.$$$(element, '.item.guild-settings').addEventListener('click', async () => {
|
q.$$$(element, '.item.guild-settings').addEventListener('click', async () => {
|
@ -10,12 +10,14 @@ import UI from '../ui';
|
|||||||
import GuildsManager from '../guilds-manager';
|
import GuildsManager from '../guilds-manager';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default function createGuildContextMenu(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) {
|
export default function createGuildContextMenu(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) {
|
||||||
const element = BaseElements.createContextMenu(document, {
|
const element = BaseElements.createContextMenu(document, (
|
||||||
class: 'guild-context', content: [
|
<div className="guild-context">
|
||||||
{ class: 'item red leave-guild', content: 'Leave Guild' }
|
<div className="item red leave-guild">Leave Guild</div>
|
||||||
]
|
</div>
|
||||||
});
|
));
|
||||||
|
|
||||||
q.$$$(element, '.leave-guild').addEventListener('click', async () => {
|
q.$$$(element, '.leave-guild').addEventListener('click', async () => {
|
||||||
element.removeSelf();
|
element.removeSelf();
|
@ -10,6 +10,8 @@ import ElementsUtil from './require/elements-util';
|
|||||||
import Q from '../q-module';
|
import Q from '../q-module';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default function createImageContextMenu(
|
export default function createImageContextMenu(
|
||||||
document: Document,
|
document: Document,
|
||||||
q: Q,
|
q: Q,
|
||||||
@ -21,10 +23,12 @@ export default function createImageContextMenu(
|
|||||||
isPreview: boolean
|
isPreview: boolean
|
||||||
): HTMLElement {
|
): HTMLElement {
|
||||||
// TODO: try/catch around sharp?
|
// TODO: try/catch around sharp?
|
||||||
const contextMenu = BaseElements.createContextMenu(document, { class: 'image', content: [
|
const contextMenu = BaseElements.createContextMenu(document, (
|
||||||
{ class: 'item copy-image', content: 'Copy Image' + (isPreview ? ' Preview' : '') },
|
<div className="image">
|
||||||
{ class: 'item save-image', content: 'Save Image' + (isPreview ? ' Preview' : '') },
|
<div className="item copy-image">Copy Image{isPreview ? ' Preview' : ''}</div>
|
||||||
] });
|
<div className="item save-image">Save Image{isPreview ? ' Preview' : ''}</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
q.$$$(contextMenu, '.copy-image').addEventListener('click', async () => {
|
q.$$$(contextMenu, '.copy-image').addEventListener('click', async () => {
|
||||||
q.$$$(contextMenu, '.copy-image').innerText = 'Copying...';
|
q.$$$(contextMenu, '.copy-image').innerText = 'Copying...';
|
||||||
let nativeImage: electron.NativeImage;
|
let nativeImage: electron.NativeImage;
|
@ -5,6 +5,8 @@ import Q from '../q-module';
|
|||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
import ElementsUtil from './require/elements-util';
|
import ElementsUtil from './require/elements-util';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
export interface CreateErrorIndicatorProps {
|
export interface CreateErrorIndicatorProps {
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
@ -16,19 +18,19 @@ export interface CreateErrorIndicatorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolveFunc and rejectFunc should be the resolve/reject functions from the withPotentialError promise
|
// resolveFunc and rejectFunc should be the resolve/reject functions from the withPotentialError promise
|
||||||
export default function createErrorIndicator(q: Q, props: CreateErrorIndicatorProps): HTMLElement {
|
export default function createErrorIndicator(q: Q, props: CreateErrorIndicatorProps): Element {
|
||||||
props.classes = props.classes ?? [];
|
props.classes = props.classes ?? [];
|
||||||
const { container, classes, message, taskFunc, resolveFunc, rejectFunc } = props;
|
const { container, classes, message, taskFunc, resolveFunc, rejectFunc } = props;
|
||||||
|
|
||||||
const element = q.create({
|
const element = ReactHelper.createElementFromJSX(
|
||||||
class: [ 'error-indicator', ...classes ], content: [
|
<div className={['error-indicator', ...classes].join(' ')}>
|
||||||
{ tag: 'img', src: './img/error.png', alt: 'error' },
|
<img src="./img/error.png" alt="error"></img>
|
||||||
{ content: [
|
<div>
|
||||||
{ content: message },
|
<div>{message}</div>
|
||||||
{ class: 'retry-button', content: 'Try Again' }
|
<div className="retry-button">Try Again</div>
|
||||||
] }
|
</div>
|
||||||
]
|
</div>
|
||||||
}) as HTMLElement;
|
);
|
||||||
|
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
if (element.parentElement == null) {
|
if (element.parentElement == null) {
|
@ -13,7 +13,7 @@ export default function bindAddGuildTitleEvents(document: Document, q: Q, ui: UI
|
|||||||
!ui.activeConnection.privileges.includes('modify_members')
|
!ui.activeConnection.privileges.includes('modify_members')
|
||||||
) return;
|
) return;
|
||||||
|
|
||||||
const contextMenu = createGuildTitleContextMenu(document, q, ui, ui.activeGuild);
|
const contextMenu = createGuildTitleContextMenu(document, q, ui, ui.activeGuild) as HTMLElement;
|
||||||
document.body.appendChild(contextMenu);
|
document.body.appendChild(contextMenu);
|
||||||
ElementsUtil.alignContextElement(contextMenu, q.$('#guild-name-container'), { top: 'bottom', centerX: 'centerX' });
|
ElementsUtil.alignContextElement(contextMenu, q.$('#guild-name-container'), { top: 'bottom', centerX: 'centerX' });
|
||||||
});
|
});
|
||||||
|
@ -14,11 +14,16 @@ import createGuildContextMenu from './context-menu-guild';
|
|||||||
import GuildsManager from '../guilds-manager';
|
import GuildsManager from '../guilds-manager';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
export default function createGuildListGuild(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) {
|
import React from 'react';
|
||||||
const element = q.create({ class: 'guild', 'data-id': guild.id, 'meta-name': guild.id, content: [
|
import ReactHelper from './require/react-helper';
|
||||||
{ class: 'pill' },
|
|
||||||
{ tag: 'img', src: './img/loading.svg', alt: 'guild' }, // src is set later by script.js
|
export default function createGuildListGuild(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild): Element {
|
||||||
] }) as HTMLElement;
|
const element = ReactHelper.createElementFromJSX(
|
||||||
|
<div className="guild" data-id={guild.id}>
|
||||||
|
<div className="pill"></div>
|
||||||
|
<img src="./img/loading.svg" alt="guild"></img>
|
||||||
|
</div>
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
// Hover over for name + connection info
|
// Hover over for name + connection info
|
||||||
(async () => {
|
(async () => {
|
@ -8,8 +8,9 @@ import createResourceMessageContinued from './msg-res-cont';
|
|||||||
import createTextMessage from './msg-txt';
|
import createTextMessage from './msg-txt';
|
||||||
import createTextMessageContinued from './msg-txt-cont';
|
import createTextMessageContinued from './msg-txt-cont';
|
||||||
|
|
||||||
export default function createMessage(document: Document, q: Q, guild: CombinedGuild, message: Message, lastMessage: Message | null): HTMLElement {
|
// TODO: This is probably best as a react class
|
||||||
let element: HTMLElement;
|
export default function createMessage(document: Document, q: Q, guild: CombinedGuild, message: Message, lastMessage: Message | null): Element {
|
||||||
|
let element: Element;
|
||||||
if (message.hasResource()) {
|
if (message.hasResource()) {
|
||||||
if (message.isImageResource()) {
|
if (message.isImageResource()) {
|
||||||
if (message.isContinued(lastMessage)) {
|
if (message.isContinued(lastMessage)) {
|
||||||
|
@ -14,19 +14,26 @@ import createImageOverlay from './overlay-image';
|
|||||||
import createImageContextMenu from './context-menu-img';
|
import createImageContextMenu from './context-menu-img';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
export default function createImageResourceMessageContinued(document: Document, q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createImageResourceMessageContinued(document: Document, q: Q, guild: CombinedGuild, message: Message): Element {
|
||||||
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
||||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = q.create({ class: 'message continued', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
const element = ReactHelper.createElementFromJSX(
|
||||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
<div className="message continued" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||||
{ class: 'right', content: [
|
<div className="timestamp">{moment(message.sent).format('HH:mm')}</div>
|
||||||
{ class: 'content image', style: `width: ${message.previewWidth}px; height: ${message.previewHeight}px;`, content:
|
<div className="right">
|
||||||
{ tag: 'img', src: './img/loading.svg', alt: message.resourceName } }, // src will be replaced later
|
<div className="content image" style={{width: message.previewWidth + 'px', height: message.previewHeight + 'px'}}>
|
||||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
<img src="./img/loading.svg" alt={message.resourceName}></img>
|
||||||
] }
|
</div>
|
||||||
] }) as HTMLElement;
|
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
q.$$$(element, '.content.image').addEventListener('click', () => {
|
q.$$$(element, '.content.image').addEventListener('click', () => {
|
||||||
document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
|
document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
|
||||||
});
|
});
|
||||||
@ -51,5 +58,6 @@ export default function createImageResourceMessageContinued(document: Document,
|
|||||||
(q.$$$(element, '.content.image img') as HTMLImageElement).src = './img/error.png';
|
(q.$$$(element, '.content.image img') as HTMLImageElement).src = './img/error.png';
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
@ -14,7 +14,10 @@ import createImageOverlay from './overlay-image';
|
|||||||
import createImageContextMenu from './context-menu-img';
|
import createImageContextMenu from './context-menu-img';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
export default function createImageResourceMessage(document: Document, q: Q, guild: CombinedGuild, message: Message) {
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createImageResourceMessage(document: Document, q: Q, guild: CombinedGuild, message: Message): Element {
|
||||||
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
||||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||||
}
|
}
|
||||||
@ -38,19 +41,25 @@ export default function createImageResourceMessage(document: Document, q: Q, gui
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameStyle = memberInfo.roleColor != null ? 'color: ' + memberInfo.roleColor : '';
|
const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
|
||||||
const element = q.create({ class: 'message', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
const element = ReactHelper.createElementFromJSX(
|
||||||
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: memberInfo.displayName } },
|
<div className="message" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||||
{ class: 'right', content: [
|
<div className="member-avatar">
|
||||||
{ class: 'header', content: [
|
<img src="./img/loading.svg" alt={memberInfo.displayName}></img>
|
||||||
{ class: 'member-name', style: nameStyle, content: memberInfo.displayName },
|
</div>
|
||||||
{ class: 'timestamp', content: moment(message.sent).calendar(ElementsUtil.calendarFormats) }
|
<div className="right">
|
||||||
] },
|
<div className="header">
|
||||||
{ class: 'content image', style: `width: ${message.previewWidth}px; height: ${message.previewHeight}px;`, content:
|
<div className="member-name" style={nameStyle}>{memberInfo.displayName}</div>
|
||||||
{ tag: 'img', src: './img/loading.svg', alt: message.resourceName } }, // src will be replaced later
|
<div className="timestamp">{moment(message.sent).calendar(ElementsUtil.calendarFormats)}</div>
|
||||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
</div>
|
||||||
] }
|
<div className="content image" style={{width: message.previewWidth + 'px', height: message.previewHeight + 'px'}}>
|
||||||
] }) as HTMLElement;
|
<img src="./img/loading.svg" alt={message.resourceName}></img>
|
||||||
|
</div>
|
||||||
|
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
q.$$$(element, '.content.image').addEventListener('click', (e) => {
|
q.$$$(element, '.content.image').addEventListener('click', (e) => {
|
||||||
document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
|
document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
|
||||||
});
|
});
|
@ -5,24 +5,30 @@ import Q from "../q-module";
|
|||||||
|
|
||||||
import ElementsUtil from "./require/elements-util";
|
import ElementsUtil from "./require/elements-util";
|
||||||
|
|
||||||
export default function createResourceMessageContinued(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createResourceMessageContinued(q: Q, guild: CombinedGuild, message: Message): Element {
|
||||||
if (!message.resourceId || !message.resourceName) {
|
if (!message.resourceId || !message.resourceName) {
|
||||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = q.create({ class: 'message continued', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
const element = ReactHelper.createElementFromJSX(
|
||||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
<div className="message continued" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||||
{ class: 'right', content: [
|
<div className="timestamp">{moment(message.sent).format('HH:mm')}</div>
|
||||||
{ class: 'content resource', content: [
|
<div className="right">
|
||||||
{ tag: 'img', class: 'icon', src: './img/file-icon.png' }, // TODO: SVG based on content-type
|
<div className="content resource">
|
||||||
{ class: 'text', content: [
|
<img className="icon" src="./img/file-icon.png" alt="file"></img>
|
||||||
{ class: 'filename', content: message.resourceName },
|
<div className="text">
|
||||||
{ class: 'download-status', content: 'Click to Download' },
|
<div className="filename">{message.resourceName}</div>
|
||||||
] }
|
<div className="download-status">Click to Download</div>
|
||||||
] },
|
</div>
|
||||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
</div>
|
||||||
] }
|
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||||
] }) as HTMLElement;
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
|
q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
|
||||||
guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
|
guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
|
||||||
downloadStartFunc: () => {
|
downloadStartFunc: () => {
|
@ -5,7 +5,10 @@ import Q from '../q-module';
|
|||||||
|
|
||||||
import ElementsUtil from './require/elements-util';
|
import ElementsUtil from './require/elements-util';
|
||||||
|
|
||||||
export default function createResourceMessage(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createResourceMessage(q: Q, guild: CombinedGuild, message: Message): Element {
|
||||||
if (!message.resourceId || !message.resourceName) {
|
if (!message.resourceId || !message.resourceName) {
|
||||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||||
}
|
}
|
||||||
@ -29,24 +32,29 @@ export default function createResourceMessage(q: Q, guild: CombinedGuild, messag
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameStyle = memberInfo.roleColor ? 'color: ' + memberInfo.roleColor : '';
|
const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
|
||||||
const element = q.create({ class: 'message', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
const element = ReactHelper.createElementFromJSX(
|
||||||
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: memberInfo.displayName } },
|
<div className="message" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||||
{ class: 'right', content: [
|
<div className="member-avatar">
|
||||||
{ class: 'header', content: [
|
<img src="./img/loading.svg" alt={memberInfo.displayName}></img>
|
||||||
{ class: 'member-name', style: nameStyle, content: memberInfo.displayName },
|
</div>
|
||||||
{ class: 'timestamp', content: moment(message.sent).calendar(ElementsUtil.calendarFormats) }
|
<div className="right">
|
||||||
] },
|
<div className="header">
|
||||||
{ class: 'content resource', content: [
|
<div className="member-name" style={nameStyle}>{memberInfo.displayName}</div>
|
||||||
{ tag: 'img', class: 'icon', src: './img/file-icon.png' }, // TODO: SVG based on content-type
|
<div className="timestamp">{moment(message.sent).calendar(ElementsUtil.calendarFormats)}</div>
|
||||||
{ class: 'text', content: [
|
</div>
|
||||||
{ class: 'filename', content: message.resourceName },
|
<div className="content resource">
|
||||||
{ class: 'download-status', content: 'Click to Download' },
|
<img className="icon" src="./img/file-icon.png" alt="file"></img>
|
||||||
] }
|
<div className="text">
|
||||||
] },
|
<div className="filename">{message.resourceName}</div>
|
||||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
<div className="download-status">Click to Download</div>
|
||||||
] }
|
</div>
|
||||||
] }) as HTMLElement;
|
</div>
|
||||||
|
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
|
q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
|
||||||
guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
|
guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
|
||||||
downloadStartFunc: () => {
|
downloadStartFunc: () => {
|
@ -1,15 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import { Message } from '../data-types';
|
|
||||||
import CombinedGuild from '../guild-combined';
|
|
||||||
import Q from '../q-module.js';
|
|
||||||
|
|
||||||
import ElementsUtil from './require/elements-util.js';
|
|
||||||
|
|
||||||
export default function createTextMessageContinued(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
|
||||||
return q.create({ class: 'message continued', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
|
||||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
|
||||||
{ class: 'right', content: [
|
|
||||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
|
||||||
] }
|
|
||||||
] }) as HTMLElement;
|
|
||||||
}
|
|
20
src/client/webapp/elements/msg-txt-cont.tsx
Normal file
20
src/client/webapp/elements/msg-txt-cont.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { Message } from '../data-types';
|
||||||
|
import CombinedGuild from '../guild-combined';
|
||||||
|
import Q from '../q-module.js';
|
||||||
|
|
||||||
|
import ElementsUtil from './require/elements-util.js';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createTextMessageContinued(q: Q, guild: CombinedGuild, message: Message): Element {
|
||||||
|
return ReactHelper.createElementFromJSX(
|
||||||
|
<div className="message continued" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||||
|
<div className="timestamp">{moment(message.sent).format('HH:mm')}</div>
|
||||||
|
<div className="right">
|
||||||
|
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -6,7 +6,10 @@ import { Message, Member, IDummyTextMessage } from '../data-types';
|
|||||||
import Q from '../q-module';
|
import Q from '../q-module';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
|
|
||||||
export default function createTextMessage(q: Q, guild: CombinedGuild, message: Message | IDummyTextMessage): HTMLElement {
|
import React from 'react';
|
||||||
|
import ReactHelper from './require/react-helper';
|
||||||
|
|
||||||
|
export default function createTextMessage(q: Q, guild: CombinedGuild, message: Message | IDummyTextMessage): Element {
|
||||||
let memberInfo: {
|
let memberInfo: {
|
||||||
roleColor: string | null,
|
roleColor: string | null,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
@ -34,17 +37,21 @@ export default function createTextMessage(q: Q, guild: CombinedGuild, message: M
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameStyle = memberInfo.roleColor ? 'color: ' + memberInfo.roleColor : '';
|
const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
|
||||||
const element = q.create({ class: 'message', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
const element = ReactHelper.createElementFromJSX(
|
||||||
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: memberInfo.displayName } },
|
<div className="message" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||||
{ class: 'right', content: [
|
<div className="member-avatar">
|
||||||
{ class: 'header', content: [
|
<img src="./img/loading.svg" alt={memberInfo.displayName}></img>
|
||||||
{ class: 'member-name', style: nameStyle, content: memberInfo.displayName },
|
</div>
|
||||||
{ class: 'timestamp', content: moment(message.sent).calendar(ElementsUtil.calendarFormats) }
|
<div className="right">
|
||||||
] },
|
<div className="header">
|
||||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text || '') }
|
<div className="member-name" style={nameStyle}>{memberInfo.displayName}</div>
|
||||||
] }
|
<div className="timestamp">{moment(message.sent).calendar(ElementsUtil.calendarFormats)}</div>
|
||||||
] }) as HTMLElement;
|
</div>
|
||||||
|
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
(async () => {
|
(async () => {
|
||||||
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
|
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
|
||||||
await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, memberInfo.avatarResourceId);
|
await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, memberInfo.avatarResourceId);
|
@ -13,8 +13,9 @@ import ElementsUtil from './elements-util';
|
|||||||
import { Channel } from '../../data-types';
|
import { Channel } from '../../data-types';
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import Q from '../../q-module';
|
import Q from '../../q-module';
|
||||||
|
import ReactHelper from './react-helper';
|
||||||
|
|
||||||
interface HTMLElementWithRemoveSelf extends HTMLElement {
|
export interface HTMLElementWithRemoveSelf extends HTMLElement {
|
||||||
removeSelf: (() => void);
|
removeSelf: (() => void);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,12 +159,26 @@ export default class BaseElements {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TOKEN = (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd"
|
||||||
|
d="M 7.9160156 0 A 8.0000004 8.0000004 0 0 0 1.8710938 2.8574219 A 8.0000004 8.0000004 0 0 0 2.8574219 14.128906 A 8.0000004 8.0000004 0 0 0 14.128906 13.142578 A 8.0000004 8.0000004 0 0 0 13.142578 1.8710938 A 8.0000004 8.0000004 0 0 0 7.9160156 0 z M 5.2324219 3.2851562 A 2.4000002 2.4000002 0 0 1 6.7851562 3.8476562 A 2.4000002 2.4000002 0 0 1 7.6054688 6.1015625 L 13.367188 10.9375 L 12.595703 11.857422 L 10.90625 12.003906 L 10.445312 11.619141 L 10.373047 10.773438 L 9.9121094 10.388672 L 9.0664062 10.462891 L 8.6074219 10.076172 L 8.5332031 9.2304688 L 8.0742188 8.8457031 L 7.2285156 8.9199219 L 6.0625 7.9414062 A 2.4000002 2.4000002 0 0 1 3.6992188 7.5253906 A 2.4000002 2.4000002 0 0 1 3.4042969 4.1425781 A 2.4000002 2.4000002 0 0 1 5.2324219 3.2851562 z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
static Q_TOKEN = {
|
static Q_TOKEN = {
|
||||||
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 16, height: 16, viewBox: '0 0 16 16', content:
|
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 16, height: 16, viewBox: '0 0 16 16', content:
|
||||||
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', 'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', 'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
||||||
d: 'M 7.9160156 0 A 8.0000004 8.0000004 0 0 0 1.8710938 2.8574219 A 8.0000004 8.0000004 0 0 0 2.8574219 14.128906 A 8.0000004 8.0000004 0 0 0 14.128906 13.142578 A 8.0000004 8.0000004 0 0 0 13.142578 1.8710938 A 8.0000004 8.0000004 0 0 0 7.9160156 0 z M 5.2324219 3.2851562 A 2.4000002 2.4000002 0 0 1 6.7851562 3.8476562 A 2.4000002 2.4000002 0 0 1 7.6054688 6.1015625 L 13.367188 10.9375 L 12.595703 11.857422 L 10.90625 12.003906 L 10.445312 11.619141 L 10.373047 10.773438 L 9.9121094 10.388672 L 9.0664062 10.462891 L 8.6074219 10.076172 L 8.5332031 9.2304688 L 8.0742188 8.8457031 L 7.2285156 8.9199219 L 6.0625 7.9414062 A 2.4000002 2.4000002 0 0 1 3.6992188 7.5253906 A 2.4000002 2.4000002 0 0 1 3.4042969 4.1425781 A 2.4000002 2.4000002 0 0 1 5.2324219 3.2851562 z' }
|
d: 'M 7.9160156 0 A 8.0000004 8.0000004 0 0 0 1.8710938 2.8574219 A 8.0000004 8.0000004 0 0 0 2.8574219 14.128906 A 8.0000004 8.0000004 0 0 0 14.128906 13.142578 A 8.0000004 8.0000004 0 0 0 13.142578 1.8710938 A 8.0000004 8.0000004 0 0 0 7.9160156 0 z M 5.2324219 3.2851562 A 2.4000002 2.4000002 0 0 1 6.7851562 3.8476562 A 2.4000002 2.4000002 0 0 1 7.6054688 6.1015625 L 13.367188 10.9375 L 12.595703 11.857422 L 10.90625 12.003906 L 10.445312 11.619141 L 10.373047 10.773438 L 9.9121094 10.388672 L 9.0664062 10.462891 L 8.6074219 10.076172 L 8.5332031 9.2304688 L 8.0742188 8.8457031 L 7.2285156 8.9199219 L 6.0625 7.9414062 A 2.4000002 2.4000002 0 0 1 3.6992188 7.5253906 A 2.4000002 2.4000002 0 0 1 3.4042969 4.1425781 A 2.4000002 2.4000002 0 0 1 5.2324219 3.2851562 z' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CREATE = (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd"
|
||||||
|
d="M 8 0 A 8 8 0 0 0 0 8 A 8 8 0 0 0 8 16 A 8 8 0 0 0 16 8 A 8 8 0 0 0 8 0 z M 7 4 L 9 4 L 9 7 L 12 7 L 12 9 L 9 9 L 9 12 L 7 12 L 7 9 L 4 9 L 4 7 L 7 7 L 7 4 z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
static Q_CREATE = {
|
static Q_CREATE = {
|
||||||
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 16, height: 16, viewBox: '0 0 16 16', content:
|
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 16, height: 16, viewBox: '0 0 16 16', content:
|
||||||
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', 'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', 'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
||||||
@ -210,20 +225,26 @@ export default class BaseElements {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
static createContextMenu(document: Document, content: any): HTMLElementWithRemoveSelf {
|
static createContextMenu(document: Document, content: JSX.Element): HTMLElementWithRemoveSelf {
|
||||||
const q = new Q(document);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const element = q.create({ class: 'context', content: { class: 'menu', content: content }}) as any;
|
const element = ReactHelper.createElementFromJSX(
|
||||||
|
<div className="context">
|
||||||
|
<div className="menu">{content}</div>
|
||||||
|
</div>
|
||||||
|
) as HTMLElementWithRemoveSelf;
|
||||||
|
|
||||||
element.addEventListener('mousedown', (e: Event) => {
|
element.addEventListener('mousedown', (e: Event) => {
|
||||||
e.stopPropagation(); // stop the bubble
|
e.stopPropagation(); // stop the bubble
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
element.removeSelf = () => {
|
element.removeSelf = () => {
|
||||||
if (element.parentElement) {
|
if (element.parentElement) {
|
||||||
element.parentElement.removeChild(element);
|
element.parentElement.removeChild(element);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
document.body.removeEventListener('mousedown', element.removeSelf);
|
document.body.removeEventListener('mousedown', element.removeSelf);
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
document.body.addEventListener('mousedown', element.removeSelf);
|
document.body.addEventListener('mousedown', element.removeSelf);
|
||||||
return element as HTMLElementWithRemoveSelf;
|
return element as HTMLElementWithRemoveSelf;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import * as electron from 'electron';
|
|||||||
|
|
||||||
import * as electronRemote from '@electron/remote';
|
import * as electronRemote from '@electron/remote';
|
||||||
const electronConsole = electronRemote.getGlobal('console') as Console;
|
const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||||
|
|
||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
@ -16,6 +15,8 @@ import Globals from '../../globals';
|
|||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import { ShouldNeverHappenError } from '../../data-types';
|
import { ShouldNeverHappenError } from '../../data-types';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
// TODO: pass-through Globals in init function
|
// TODO: pass-through Globals in init function
|
||||||
// alignment: {
|
// alignment: {
|
||||||
// centerY: 'top'
|
// centerY: 'top'
|
||||||
@ -34,6 +35,13 @@ interface IHTMLElementWithRemovalType extends HTMLElement {
|
|||||||
manualRemoval?: boolean;
|
manualRemoval?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SimpleQElement {
|
||||||
|
tag: 'span',
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
content: (SimpleQElement | string)[],
|
||||||
|
class: string | null
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateDownloadListenerProps {
|
interface CreateDownloadListenerProps {
|
||||||
downloadBuff?: Buffer;
|
downloadBuff?: Buffer;
|
||||||
guild?: CombinedGuild;
|
guild?: CombinedGuild;
|
||||||
@ -115,19 +123,19 @@ export default class ElementsUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates <span class="bold"/"italic"/"bold italic"/"underline"> spans to format the text (all in q.js element markup)
|
// creates <span class="bold"/"italic"/"bold italic"/"underline"> spans to format the text (all in q.js element markup that gets converted to jsx [aka hypergaming])
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
static parseMessageText(text: string): any {
|
static parseMessageText(text: string): JSX.Element {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const obj = { tag: 'span', content: [] as any[], class: null };
|
const obj: SimpleQElement = { tag: 'span', content: [] as (SimpleQElement | string)[], class: null };
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const stack: any[] = [ obj ];
|
const stack: SimpleQElement[] = [ obj ];
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
function makeEscape(regex: RegExp, len: number, str: string): { matcher: RegExp, response: ((i: number) => void)} { // function for readability
|
function makeEscape(regex: RegExp, len: number, str: string): { matcher: RegExp, response: ((i: number) => void)} { // function for readability
|
||||||
return {
|
return {
|
||||||
matcher: regex,
|
matcher: regex,
|
||||||
response: (i: number) => {
|
response: (i: number) => {
|
||||||
const top = stack[stack.length - 1];
|
const top = stack[stack.length - 1] as SimpleQElement;
|
||||||
if (idx != i) top.content.push(text.substring(idx, i));
|
if (idx != i) top.content.push(text.substring(idx, i));
|
||||||
top.content.push(str);
|
top.content.push(str);
|
||||||
idx = i + len;
|
idx = i + len;
|
||||||
@ -138,13 +146,14 @@ export default class ElementsUtil {
|
|||||||
return {
|
return {
|
||||||
matcher: regex,
|
matcher: regex,
|
||||||
response: (i: number) => {
|
response: (i: number) => {
|
||||||
const top = stack[stack.length - 1];
|
const top = stack[stack.length - 1] as SimpleQElement;
|
||||||
if (idx != i) top.content.push(text.substring(idx, i));
|
if (idx != i) top.content.push(text.substring(idx, i));
|
||||||
if (top.class == cls) { // italic ends
|
if (top.class == cls) { // italic ends
|
||||||
// TODO: optimise out empty elements
|
// TODO: optimise out empty elements
|
||||||
stack.pop();
|
stack.pop();
|
||||||
} else { // italic begins
|
} else { // italic begins
|
||||||
const obj = { tag: 'span', class: cls, content: [] }
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const obj: SimpleQElement = { tag: 'span', class: cls, content: [] as any[] }
|
||||||
top.content.push(obj);
|
top.content.push(obj);
|
||||||
stack.push(obj);
|
stack.push(obj);
|
||||||
}
|
}
|
||||||
@ -175,13 +184,23 @@ export default class ElementsUtil {
|
|||||||
}
|
}
|
||||||
if (!matched && idx != text.length) {
|
if (!matched && idx != text.length) {
|
||||||
// Add any remaining content
|
// Add any remaining content
|
||||||
const top = stack[stack.length - 1];
|
const top = stack[stack.length - 1] as SimpleQElement;
|
||||||
top.content.push(text.substr(idx));
|
top.content.push(text.substr(idx));
|
||||||
idx = text.length;
|
idx = text.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
function createReactElement(qjsObj: SimpleQElement | string): JSX.Element | string {
|
||||||
|
if (typeof qjsObj === 'string') {
|
||||||
|
return qjsObj
|
||||||
|
} else {
|
||||||
|
const content = qjsObj.content.map(qjsElement => createReactElement(qjsElement));
|
||||||
|
return React.createElement(qjsObj.tag, { className: qjsObj.class }, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return createReactElement(obj) as JSX.Element; // since the first layer will always have a tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: both elements must be within the document or this function will not work
|
// NOTE: both elements must be within the document or this function will not work
|
@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
import { ShouldNeverHappenError } from "../../data-types";
|
import { ShouldNeverHappenError } from "../../data-types";
|
||||||
|
|
||||||
@ -6,7 +5,7 @@ import { ShouldNeverHappenError } from "../../data-types";
|
|||||||
|
|
||||||
export default class ReactHelper {
|
export default class ReactHelper {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
static createElementFromJSX(element: React.ReactElement<any, string | React.JSXElementConstructor<any>>): Element {
|
static createElementFromJSX(element: JSX.Element): Element {
|
||||||
// See also: https://www.codegrepper.com/code-examples/javascript/convert+a+string+to+html+element+in+js
|
// See also: https://www.codegrepper.com/code-examples/javascript/convert+a+string+to+html+element+in+js
|
||||||
const htmlString = ReactDOMServer.renderToStaticMarkup(element);
|
const htmlString = ReactDOMServer.renderToStaticMarkup(element);
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
@ -34,7 +34,7 @@ export default class UI {
|
|||||||
|
|
||||||
public messagePairsGuild: CombinedGuild | null = null;
|
public messagePairsGuild: CombinedGuild | null = null;
|
||||||
public messagePairsChannel: Channel | { id: string } | null = null;
|
public messagePairsChannel: Channel | { id: string } | null = null;
|
||||||
public messagePairs = new Map<string | null, { message: Message, element: HTMLElement }>(); // messageId -> { message: Message, element: HTMLElement }
|
public messagePairs = new Map<string | null, { message: Message, element: Element }>(); // messageId -> { message: Message, element: HTMLElement }
|
||||||
|
|
||||||
private document: Document;
|
private document: Document;
|
||||||
private q: Q;
|
private q: Q;
|
||||||
@ -311,7 +311,7 @@ export default class UI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setChannelsErrorIndicator(guild: CombinedGuild, errorIndicatorElement: HTMLElement): Promise<void> {
|
public async setChannelsErrorIndicator(guild: CombinedGuild, errorIndicatorElement: Element): Promise<void> {
|
||||||
await this.lockChannels(guild, () => {
|
await this.lockChannels(guild, () => {
|
||||||
Q.clearChildren(this.q.$('#channel-list'));
|
Q.clearChildren(this.q.$('#channel-list'));
|
||||||
this.q.$('#channel-list').appendChild(errorIndicatorElement);
|
this.q.$('#channel-list').appendChild(errorIndicatorElement);
|
||||||
@ -381,7 +381,7 @@ export default class UI {
|
|||||||
const newStyle = member.roleColor ? 'color: ' + member.roleColor : null;
|
const newStyle = member.roleColor ? 'color: ' + member.roleColor : null;
|
||||||
const newName = member.displayName;
|
const newName = member.displayName;
|
||||||
// the extra query selectors may be overkill
|
// the extra query selectors may be overkill
|
||||||
for (const messageElement of this.q.$$(`.message[meta-member-id="${member.id}"]`)) {
|
for (const messageElement of this.q.$$(`.message[data-member-id="${member.id}"]`)) {
|
||||||
const nameElement = this.q.$$$_(messageElement, '.member-name');
|
const nameElement = this.q.$$$_(messageElement, '.member-name');
|
||||||
if (nameElement) { // continued messages will still show up but need to be skipped
|
if (nameElement) { // continued messages will still show up but need to be skipped
|
||||||
if (newStyle) nameElement.setAttribute('style', newStyle);
|
if (newStyle) nameElement.setAttribute('style', newStyle);
|
||||||
@ -396,19 +396,19 @@ export default class UI {
|
|||||||
await this.addMembers(guild, members, { clear: true });
|
await this.addMembers(guild, members, { clear: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: HTMLElement): Promise<void> {
|
public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: Element): Promise<void> {
|
||||||
await this.lockMembers(guild, () => {
|
await this.lockMembers(guild, () => {
|
||||||
Q.clearChildren(this.q.$('#guild-members'));
|
Q.clearChildren(this.q.$('#guild-members'));
|
||||||
this.q.$('#guild-members').appendChild(errorIndicatorElement);
|
this.q.$('#guild-members').appendChild(errorIndicatorElement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTopMessagePair(): { message: Message, element: HTMLElement } | null {
|
public getTopMessagePair(): { message: Message, element: Element } | null {
|
||||||
const element = this.q.$$('#channel-feed .message')[0];
|
const element = this.q.$$('#channel-feed .message')[0];
|
||||||
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getBottomMessagePair(): { message: Message, element: HTMLElement } | null {
|
public getBottomMessagePair(): { message: Message, element: Element } | null {
|
||||||
const messageElements = this.q.$$('#channel-feed .message');
|
const messageElements = this.q.$$('#channel-feed .message');
|
||||||
const element = messageElements[messageElements.length - 1];
|
const element = messageElements[messageElements.length - 1];
|
||||||
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
||||||
@ -688,19 +688,19 @@ export default class UI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addMessagesErrorIndicatorBefore(guild: CombinedGuild, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
public async addMessagesErrorIndicatorBefore(guild: CombinedGuild, channel: Channel, errorIndicatorElement: Element): Promise<void> {
|
||||||
await this.lockMessages(guild, channel, () => {
|
await this.lockMessages(guild, channel, () => {
|
||||||
this.q.$('#channel-feed').prepend(errorIndicatorElement);
|
this.q.$('#channel-feed').prepend(errorIndicatorElement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addMessagesErrorIndicatorAfter(guild: CombinedGuild, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
public async addMessagesErrorIndicatorAfter(guild: CombinedGuild, channel: Channel, errorIndicatorElement: Element): Promise<void> {
|
||||||
await this.lockMessages(guild, channel, () => {
|
await this.lockMessages(guild, channel, () => {
|
||||||
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setMessagesErrorIndicator(guild: CombinedGuild, channel: Channel | { id: string }, errorIndicatorElement: HTMLElement): Promise<void> {
|
public async setMessagesErrorIndicator(guild: CombinedGuild, channel: Channel | { id: string }, errorIndicatorElement: Element): Promise<void> {
|
||||||
await this.lockMessages(guild, channel, () => {
|
await this.lockMessages(guild, channel, () => {
|
||||||
Q.clearChildren(this.q.$('#channel-feed'));
|
Q.clearChildren(this.q.$('#channel-feed'));
|
||||||
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
||||||
|
@ -14,7 +14,7 @@ import createErrorIndicator from './elements/error-indicator';
|
|||||||
|
|
||||||
interface WithPotentialErrorParams {
|
interface WithPotentialErrorParams {
|
||||||
taskFunc: (() => Promise<void>),
|
taskFunc: (() => Promise<void>),
|
||||||
errorIndicatorAddFunc: ((element: HTMLElement) => Promise<void>),
|
errorIndicatorAddFunc: ((element: Element) => Promise<void>),
|
||||||
errorContainer: HTMLElement,
|
errorContainer: HTMLElement,
|
||||||
errorClasses?: string[],
|
errorClasses?: string[],
|
||||||
errorMessage: string,
|
errorMessage: string,
|
||||||
|
Loading…
Reference in New Issue
Block a user