mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
tgui paper bandaid fix (#91098)
## About The Pull Request For the record, this is a bandaid fix. The inputs are losing focus with each update (aka EVERY backend update). This sets them to only update when their editability changes, letting you type again. Caveat: The UI, or at least how it's done with dangerous HTML, doesn't seem to be smart enough to detect when the UI turns back to being non editable, nor am I smart enough to fix this behavior after several attempts. The result: When you drop the pen, the fields look editable but lose focus when clicked just like the bug originally. It's not great UX. Paperwork needs to just be wholesale redone. Bumped marked.js versions a HUGE amount which should come with better performance, typings, etc. This seemingly resolves a few issues with markdown otherwise (like typing _a)
This commit is contained in:
@@ -29,7 +29,7 @@ type MarkdownRendererProps = {
|
||||
export const MarkdownRenderer = (props: MarkdownRendererProps) => {
|
||||
let { content, sanitize } = props;
|
||||
|
||||
content = marked(content);
|
||||
content = marked(content, { async: false });
|
||||
if (sanitize) {
|
||||
content = sanitizeText(content, /* advHtml = */ false);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { marked } from 'marked';
|
||||
import { baseUrl } from 'marked-base-url';
|
||||
import { markedSmartypants } from 'marked-smartypants';
|
||||
import { Component, RefObject } from 'react';
|
||||
import { Box, Section } from 'tgui-core/components';
|
||||
|
||||
import { useBackend, useLocalState } from '../../backend';
|
||||
import { sanitizeText } from '../../sanitize';
|
||||
import { canEdit, tokenizer, walkTokens } from './helpers';
|
||||
import { tokenizer, walkTokens } from './helpers';
|
||||
import { StampView } from './StampView';
|
||||
import { FieldInput, InteractionType, PaperContext } from './types';
|
||||
|
||||
@@ -12,6 +14,7 @@ type PreviewViewProps = {
|
||||
scrollableRef: RefObject<HTMLDivElement | null>;
|
||||
handleOnScroll: (this: GlobalEventHandlers, ev: Event) => any;
|
||||
textArea: string;
|
||||
canEdit: boolean;
|
||||
};
|
||||
|
||||
type FieldCreationReturn = {
|
||||
@@ -78,15 +81,17 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
walkTokens,
|
||||
};
|
||||
|
||||
marked.use({
|
||||
extensions: [inputField],
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
smartypants: true,
|
||||
walkTokens: walkTokens,
|
||||
marked.use(
|
||||
{
|
||||
extensions: [inputField],
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
walkTokens: walkTokens,
|
||||
},
|
||||
markedSmartypants(),
|
||||
// Once assets are fixed might need to change this for them
|
||||
baseUrl: 'thisshouldbreakhttp',
|
||||
});
|
||||
baseUrl('thisshouldbreakhttp'),
|
||||
);
|
||||
};
|
||||
|
||||
// Extracts the paper field "counter" from a full ID.
|
||||
@@ -145,6 +150,12 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
document.removeEventListener('input', this.onInputHandler);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Readonly<PreviewViewProps>): boolean {
|
||||
if (!this.props.canEdit) return true;
|
||||
|
||||
return this.props.canEdit !== nextProps.canEdit;
|
||||
}
|
||||
|
||||
// Creates the partial inline HTML for previewing or reading the paper from
|
||||
// only static_ui_data from DM.
|
||||
createPreviewFromDM = (): { text: string; newFieldCount: number } => {
|
||||
@@ -155,13 +166,12 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
default_pen_font,
|
||||
default_pen_color,
|
||||
paper_color,
|
||||
held_item_details,
|
||||
} = data;
|
||||
|
||||
let output = '';
|
||||
let fieldCount = 0;
|
||||
|
||||
const readOnly = !canEdit(held_item_details);
|
||||
const readOnly = !this.props.canEdit;
|
||||
|
||||
// If readonly is the same (input field writiability state hasn't changed)
|
||||
// And the input stats are the same (no new text inputs since last time)
|
||||
@@ -254,7 +264,7 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
text: string,
|
||||
font: string,
|
||||
color: string,
|
||||
bold: boolean = false,
|
||||
bold = false,
|
||||
): string => {
|
||||
return `<span style="color:${color};font-family:${font};${
|
||||
bold ? 'font-weight: bold;' : ''
|
||||
@@ -289,7 +299,7 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
},
|
||||
};
|
||||
|
||||
return marked.parse(rawText);
|
||||
return marked.parse(rawText, { async: false });
|
||||
};
|
||||
|
||||
// Fully formats, sanitises and parses the provided raw text and wraps it
|
||||
@@ -300,12 +310,11 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
color: string,
|
||||
paperColor: string,
|
||||
bold: boolean,
|
||||
fieldCounter: number = 0,
|
||||
fieldCounter = 0,
|
||||
forceReadonlyFields: boolean = false,
|
||||
advanced_html: boolean = false,
|
||||
advanced_html = false,
|
||||
): FieldCreationReturn => {
|
||||
// First lets make sure it ends in a new line
|
||||
const { data } = useBackend<PaperContext>();
|
||||
rawText += rawText[rawText.length] === '\n' ? '\n' : '\n\n';
|
||||
|
||||
// Second, parse the text using markup
|
||||
@@ -358,41 +367,38 @@ export class PreviewView extends Component<PreviewViewProps> {
|
||||
color: string,
|
||||
paperColor: string,
|
||||
forceReadonlyFields: boolean,
|
||||
counter: number = 0,
|
||||
counter = 0,
|
||||
): FieldCreationReturn => {
|
||||
const { data } = useBackend<PaperContext>();
|
||||
const { raw_field_input } = data;
|
||||
|
||||
const ret_text = rawText.replace(
|
||||
fieldRegex,
|
||||
(match, p1, offset, string) => {
|
||||
const width = this.textWidth(match, font, fontSize);
|
||||
const matchingData = raw_field_input?.find(
|
||||
(e) => e.field_index === `${counter}`,
|
||||
);
|
||||
if (matchingData) {
|
||||
return this.createFilledInputField(
|
||||
matchingData,
|
||||
p1.length,
|
||||
width,
|
||||
font,
|
||||
fontSize,
|
||||
color,
|
||||
paperColor,
|
||||
this.createIDHeader(counter++),
|
||||
);
|
||||
}
|
||||
return this.createInputField(
|
||||
const ret_text = rawText.replace(fieldRegex, (match, p1) => {
|
||||
const width = this.textWidth(match, font, fontSize);
|
||||
const matchingData = raw_field_input?.find(
|
||||
(e) => e.field_index === `${counter}`,
|
||||
);
|
||||
if (matchingData) {
|
||||
return this.createFilledInputField(
|
||||
matchingData,
|
||||
p1.length,
|
||||
width,
|
||||
font,
|
||||
fontSize,
|
||||
color,
|
||||
paperColor,
|
||||
this.createIDHeader(counter++),
|
||||
forceReadonlyFields,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return this.createInputField(
|
||||
p1.length,
|
||||
width,
|
||||
font,
|
||||
fontSize,
|
||||
color,
|
||||
this.createIDHeader(counter++),
|
||||
forceReadonlyFields,
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
nextCounter: counter,
|
||||
|
||||
@@ -74,6 +74,8 @@ export class PrimaryView extends Component {
|
||||
|
||||
const tooManyCharacters = usedCharacters > max_length;
|
||||
|
||||
const canEdit = interactMode === InteractionType.writing;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PaperSheetStamper scrollableRef={this.scrollableRef} />
|
||||
@@ -86,9 +88,10 @@ export class PrimaryView extends Component {
|
||||
scrollableRef={this.scrollableRef}
|
||||
handleOnScroll={this.onScrollHandler}
|
||||
textArea={textAreaText}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
</Flex.Item>
|
||||
{interactMode === InteractionType.writing && (
|
||||
{canEdit && (
|
||||
<Flex.Item shrink={1} height={TEXTAREA_INPUT_HEIGHT + 'px'}>
|
||||
<Section
|
||||
title="Insert Text"
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
"highlight.js": "^11.11.1",
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"marked": "^4.3.0",
|
||||
"marked": "^15.0.11",
|
||||
"marked-base-url": "^1.1.6",
|
||||
"marked-smartypants": "^1.1.9",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"tgui-core": "^3.1.4",
|
||||
"tgui-dev-server": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/marked": "4.3.2",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"vitest": "^3.1.1"
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import { marked } from 'marked';
|
||||
import { baseUrl } from 'marked-base-url';
|
||||
import { markedSmartypants } from 'marked-smartypants';
|
||||
|
||||
import { sanitizeText } from './sanitize';
|
||||
|
||||
export const processedText = (value) => {
|
||||
const textHtml = {
|
||||
__html: sanitizeText(
|
||||
marked(value, {
|
||||
type ProcessedText = {
|
||||
__html: string;
|
||||
};
|
||||
|
||||
export function processedText(value: string | null): ProcessedText {
|
||||
if (!value) {
|
||||
return { __html: '' };
|
||||
}
|
||||
|
||||
const parsed = marked
|
||||
.use(
|
||||
{
|
||||
breaks: true,
|
||||
smartypants: true,
|
||||
smartLists: true,
|
||||
baseUrl: 'thisshouldbreakhttp',
|
||||
}),
|
||||
),
|
||||
},
|
||||
markedSmartypants(),
|
||||
baseUrl('thisshouldbreakhttp'),
|
||||
)
|
||||
.parse(value, { async: false });
|
||||
|
||||
const textHtml = {
|
||||
__html: sanitizeText(parsed),
|
||||
};
|
||||
return textHtml;
|
||||
};
|
||||
}
|
||||
|
||||
+37
-13
@@ -3918,13 +3918,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/marked@npm:4.3.2":
|
||||
version: 4.3.2
|
||||
resolution: "@types/marked@npm:4.3.2"
|
||||
checksum: 10c0/6f44d28da5c940a719d6c6aca41e33f49c5fe957f7972939cc1cbb47d045951f4d969f382d655345d0463e47db1994635d2862018716fcef2fa85ceeceb116e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/mime@npm:^1":
|
||||
version: 1.3.5
|
||||
resolution: "@types/mime@npm:1.3.5"
|
||||
@@ -13824,12 +13817,32 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked@npm:^4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "marked@npm:4.3.0"
|
||||
"marked-base-url@npm:^1.1.6":
|
||||
version: 1.1.6
|
||||
resolution: "marked-base-url@npm:1.1.6"
|
||||
peerDependencies:
|
||||
marked: ">= 4 < 16"
|
||||
checksum: 10c0/346b8f3a912ab5192caec896d8e923b83113f81312eb4838ec9773aca620088cd3ed4d9fce773f3251d71c72f271c3df8554f025c1146cea3e76845b29e5be42
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked-smartypants@npm:^1.1.9":
|
||||
version: 1.1.9
|
||||
resolution: "marked-smartypants@npm:1.1.9"
|
||||
dependencies:
|
||||
smartypants: "npm:^0.2.2"
|
||||
peerDependencies:
|
||||
marked: ">=4 <16"
|
||||
checksum: 10c0/50a9f9ddeb67045933c26abdd8acf997368d96920990048b6d432c4d2374a513152c639b46c16c74934483b7a1df6093f8f26461f2409b9bf1e58f8ac759391f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked@npm:^15.0.11":
|
||||
version: 15.0.11
|
||||
resolution: "marked@npm:15.0.11"
|
||||
bin:
|
||||
marked: bin/marked.js
|
||||
checksum: 10c0/0013463855e31b9c88d8bb2891a611d10ef1dc79f2e3cbff1bf71ba389e04c5971298c886af0be799d7fa9aa4593b086a136062d59f1210b0480b026a8c5dc47
|
||||
checksum: 10c0/d532db4955c1f2ac6efc65a644725e9e12e7944cb6af40c7148baecfd3b3c2f3564229b3daf12d2125635466448fb9b367ce52357be3aea0273e3d152efdbdcf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -18699,6 +18712,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"smartypants@npm:^0.2.2":
|
||||
version: 0.2.2
|
||||
resolution: "smartypants@npm:0.2.2"
|
||||
bin:
|
||||
smartypants: bin/smartypants.js
|
||||
smartypantsu: bin/smartypantsu.js
|
||||
checksum: 10c0/29b46df6acaed9ad6271624e26e055d6360c9da7f79433b7a83ebd40b50bd6b554d8f3793932f42be07e5badce889f547c89378fda77cb1f935b09a902c4527b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"snake-case@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "snake-case@npm:3.0.4"
|
||||
@@ -19885,7 +19908,6 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "tgui@workspace:packages/tgui"
|
||||
dependencies:
|
||||
"@types/marked": "npm:4.3.2"
|
||||
"@types/react": "npm:^19.1.0"
|
||||
"@types/react-dom": "npm:^19.1.2"
|
||||
common: "workspace:*"
|
||||
@@ -19895,7 +19917,9 @@ __metadata:
|
||||
highlight.js: "npm:^11.11.1"
|
||||
jest: "npm:^29.7.0"
|
||||
js-yaml: "npm:^4.1.0"
|
||||
marked: "npm:^4.3.0"
|
||||
marked: "npm:^15.0.11"
|
||||
marked-base-url: "npm:^1.1.6"
|
||||
marked-smartypants: "npm:^1.1.9"
|
||||
react: "npm:^19.1.0"
|
||||
react-dom: "npm:^19.1.0"
|
||||
tgui-core: "npm:^3.1.4"
|
||||
|
||||
Reference in New Issue
Block a user