Files
_0Steven e34504c5e3 Fixes two books creation bugs: trimming and newlines. (#95704)
## About The Pull Request

### Trimming

So as per #89165, paper below 5000 characters was getting cut off
despite being below the cap.
This seems to have been happening because of the following code:

https://github.com/tgstation/tgstation/blob/d8774f4c4176f09463457f04905570923b27b3de/code/modules/library/book_info.dm#L44-L50
Paper uses the raw text for its count and handles pen
color/font/boldness/such tgui-side, but books first expand that html
dm-side AND then encode it, significantly increasing the amount of
characters before trimming occurs.
This, obviously, leads to characters getting cut off!

Buuuuuuuut not trimming it at all isn't perfect. We know paper *should*
trim it, but we also know that's not gonna be all paper. There's gonna
be paper where it's inexplicably past the 5000 characters cap.

So, instead we use a second more lenient book limit, right now defined
as 2x the paper limit, which should stop the worst cases without
breaking for the best cases.

### Newlines

Second bit! Paper stores each writing attempt as a separate thing, but
turning them into books collapses these:

https://github.com/tgstation/tgstation/blob/d8774f4c4176f09463457f04905570923b27b3de/code/modules/library/book_info.dm#L46-L48
This, however, doesn't account for the part where paper tgui inserts
newlines between these inputs:

https://github.com/tgstation/tgstation/blob/d8774f4c4176f09463457f04905570923b27b3de/tgui/packages/tgui/interfaces/PaperSheet/Preview.tsx#L317-L318
Which book tgui doesn't! Cause it's just one big glob of text, not
separate entries.
Which in turn means paper written with multiple entries just... has all
of these put on the same line, which sucks.

So we make the paper entries collapsing actually add newlines the same
way, fixing our issue.

### Nitpicks

...We also change the `to_raw_html(...)` proc to re-use the same
`<font>` tag. Regrettably, we can't use the exact same setup as
paperwork uses on its tgui side because it uses `style` which would get
sanitized out, but there's no reason for us to double the amount of
characters behind the scenes by using multiple layered `<font>` tags for
no reason.

***Ideally*** I feel books and paper should just use the same tgui
systems, but I sure as hell wouldn't know how to do that without
breaking everything including old books, with how fickle paperwork code
already is.
## Why It's Good For The Game

Fixes #89165.
fix jank 👍
It *really* sucks when you make a book below the 5000 character cap, but
it somehow hits the character cap and gets trimmed anyway. I feel the
books using static ui data should avoid this being horrible.
## Changelog
🆑
fix: Books no longer get trimmed below the paper length limit.
fix: Books made using paperwork created with multiple inputs no longer
put all that text on the same line.
/🆑
2026-04-17 13:04:32 +02:00

79 lines
2.8 KiB
Plaintext

//Some information about how html sanitization is handled
//All book info datums should store sanitized data. This cannot be worked around
//All inputs and outputs from the round (DB calls) need to use sanitized data
//All tgui menus should get unsanitized data, since jsx handles that on its own
//Everything else should use sanitized data. Yes including names, it's an xss vuln because of how chat works
///A datum which contains all the metadata of a book
/datum/book_info
///The title of the book
var/title
///The "author" of the book
var/author
///The info inside the book
var/content
/datum/book_info/New(_title, _author, _content)
title = _title
author = _author
content = _content
/datum/book_info/proc/set_title(_title, trusted = FALSE) //Trusted should only be used for books read from the db, or in cases that we can be sure the info has already been sanitized
if(trusted)
title = _title
return
title = reject_bad_text(trim(html_encode(_title), 30))
/datum/book_info/proc/get_title(default="N/A") //Loads in an html decoded version of the title. Only use this for tgui menus, absolutely nothing else.
return html_decode(title) || "N/A"
/datum/book_info/proc/set_author(_author, trusted = FALSE)
if(trusted)
author = _author
return
author = trim(html_encode(_author), MAX_NAME_LEN)
/datum/book_info/proc/get_author(default="N/A")
return html_decode(author) || "N/A"
/datum/book_info/proc/set_content(_content, trusted = FALSE)
if(trusted)
content = _content
return
content = trim(html_encode(trim(_content, MAX_PAPER_LENGTH)), MAX_BOOK_LENGTH)
/datum/book_info/proc/set_content_using_paper(obj/item/paper/paper)
// Just the paper's raw data.
var/raw_content = ""
for(var/datum/paper_input/text_input as anything in paper.raw_text_inputs)
raw_content += text_input.to_raw_html()
raw_content += (text_input.raw_text[length(text_input.raw_text)] == "\n") ? "\n" : "\n\n"
// Paper raw inputs should already be trimmed to maximum paper length,
// so we don't trim here again to avoid cutting into the expanded html.
// We still trim to a more lenient length to avoid abuse.
content = trim(html_encode(raw_content), MAX_BOOK_LENGTH)
/datum/book_info/proc/get_content(default="N/A")
return html_decode(content) || "N/A"
///Returns a copy of the book_info datum
/datum/book_info/proc/return_copy()
var/datum/book_info/copycat = new(title, author, content)
return copycat
///Modify an existing book_info datum to match your data
/datum/book_info/proc/copy_into(datum/book_info/copycat)
copycat.set_title(title, trusted = TRUE)
copycat.set_author(author, trusted = TRUE)
copycat.set_content(content, trusted = TRUE)
return copycat
/datum/book_info/proc/compare(datum/book_info/cmp_with)
if(author != cmp_with.author)
return FALSE
if(title != cmp_with.title)
return FALSE
if(content != cmp_with.content)
return FALSE
return TRUE