diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm
index 3551afde9ec..5813afa387a 100644
--- a/code/game/machinery/ai_slipper.dm
+++ b/code/game/machinery/ai_slipper.dm
@@ -83,7 +83,7 @@
t += "Dispenser [(src.disabled ? "deactivated" : "activated")] - [(src.disabled ? "Enable" : "Disable")]?
\n"
t += "Uses Left: [uses]. Activate the dispenser?
\n"
- user << browse(t, "window=computer;size=575x450")
+ user << browse(HTML_SKELETON(t), "window=computer;size=575x450")
onclose(user, "computer")
return
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index f60ffd9cb24..a1e9c1affdf 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -216,7 +216,7 @@
t += "(add new)
"
t+= "
"
- user << browse(t, "window=navbeacon")
+ user << browse(HTML_SKELETON(t), "window=navbeacon")
onclose(user, "navbeacon")
return
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index a9e30c95fb4..c67031a4e2f 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -438,7 +438,7 @@ GLOBAL_LIST_INIT_TYPED(allConsoles, /obj/machinery/requests_console, list())
dat += "[department] Department
"
dat += "[info]"
dat += ""
- usr << browse(dat, "window=Information;size=560x240")
+ usr << browse(HTML_SKELETON(dat), "window=Information;size=560x240")
// Toggle the paper bin lid.
if(href_list["setLid"])
diff --git a/code/game/machinery/telecomms/computers/logbrowser.dm b/code/game/machinery/telecomms/computers/logbrowser.dm
index 736f267b106..bb0348e2890 100644
--- a/code/game/machinery/telecomms/computers/logbrowser.dm
+++ b/code/game/machinery/telecomms/computers/logbrowser.dm
@@ -28,7 +28,7 @@
if(stat & (BROKEN|NOPOWER))
return
user.set_machine(src)
- var/dat = "Telecommunication Server MonitorTelecommunications Server Monitor"
+ var/dat = "Telecommunications Server Monitor"
switch(screen)
@@ -64,7 +64,7 @@
dat += log_entries_to_text(user, SelectedServer)
- user << browse(dat, "window=comm_monitor;size=575x400")
+ user << browse(HTML_SKELETON_TITLE("Telecommunications Server Monitor", dat), "window=comm_monitor;size=575x400")
onclose(user, "server_control")
temp = ""
diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm
index a571b1ad4c6..3ede3172170 100644
--- a/code/game/machinery/telecomms/computers/message.dm
+++ b/code/game/machinery/telecomms/computers/message.dm
@@ -98,7 +98,7 @@
//If the computer is being hacked or is emagged, display the reboot message.
if(hacking || emag)
message = rebootmsg
- var/dat = "Message Monitor Console"
+ var/dat = ""
dat += "Message Monitor Console
"
dat += ""
@@ -247,7 +247,7 @@
dat += ""
message = defaultmsg
- user << browse(dat, "window=message;size=700x700")
+ user << browse(HTML_SKELETON_TITLE("Message Monitor Console", dat), "window=message;size=700x700")
onclose(user, "message")
return
diff --git a/code/game/machinery/telecomms/computers/telemonitor.dm b/code/game/machinery/telecomms/computers/telemonitor.dm
index c1406dd55c0..dfb67a6ceef 100644
--- a/code/game/machinery/telecomms/computers/telemonitor.dm
+++ b/code/game/machinery/telecomms/computers/telemonitor.dm
@@ -24,7 +24,7 @@
if(stat & (BROKEN|NOPOWER))
return
user.set_machine(src)
- var/dat = "Telecommunications MonitorTelecommunications Monitor"
+ var/dat = "Telecommunications Monitor"
switch(screen)
@@ -59,7 +59,7 @@
- user << browse(dat, "window=comm_monitor;size=575x400")
+ user << browse(HTML_SKELETON_TITLE("Telecommunications Monitor", dat), "window=comm_monitor;size=575x400")
onclose(user, "server_control")
temp = ""
diff --git a/code/game/machinery/telecomms/computers/traffic_control.dm b/code/game/machinery/telecomms/computers/traffic_control.dm
index 9bcd103af33..2d0ec8acd30 100644
--- a/code/game/machinery/telecomms/computers/traffic_control.dm
+++ b/code/game/machinery/telecomms/computers/traffic_control.dm
@@ -69,7 +69,7 @@
if(stat & (BROKEN|NOPOWER))
return
user.set_machine(src)
- var/dat = "Telecommunication Traffic ControlTelecommunications Traffic Control"
+ var/dat = "Telecommunications Traffic Control"
switch(screen)
@@ -105,7 +105,7 @@
dat += "NEVER"
- user << browse(dat, "window=traffic_control;size=575x400")
+ user << browse(HTML_SKELETON_TITLE("Telecommunication Traffic Control", dat), "window=traffic_control;size=575x400")
onclose(user, "server_control")
temp = ""
diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm
index 85657cfa34a..c1d5b77c6bb 100644
--- a/code/game/objects/items/devices/radio/electropack.dm
+++ b/code/game/objects/items/devices/radio/electropack.dm
@@ -107,6 +107,6 @@ Code:
+
+
"}
- user << browse(dat, "window=radio")
+ user << browse(HTML_SKELETON(dat), "window=radio")
onclose(user, "radio")
return
diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm
index cfa00a4c981..ccff47f542c 100644
--- a/code/modules/admin/DB ban/functions.dm
+++ b/code/modules/admin/DB ban/functions.dm
@@ -564,4 +564,4 @@
output += ""
- usr << browse(output,"window=lookupbans;size=900x700")
+ usr << browse(HTML_SKELETON(output), "window=lookupbans;size=900x700")
diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm
index b72d3187251..f828e2a737b 100644
--- a/code/modules/admin/banjob.dm
+++ b/code/modules/admin/banjob.dm
@@ -309,7 +309,7 @@ GLOBAL_PROTECT(jobban_keylist)
return
var/dat = ""
- var/header = "Job-Ban Panel: [ckey]"
+ var/header = ""
var/body
var/jobs = ""
diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm
index 5a500054593..f10af332994 100644
--- a/code/modules/tgui_panel/external.dm
+++ b/code/modules/tgui_panel/external.dm
@@ -19,19 +19,16 @@
// Failed to fix, using tgalert as fallback
action = input(src, "Did that work?", "", "Yes") in list("Yes", "No, switch to old ui")
if (action == "No, switch to old ui")
- winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
- winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ winset(src, "legacy_output_selector", "left=output_legacy")
log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel")
/client/proc/nuke_chat()
// Catch all solution (kick the whole thing in the pants)
- winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
- winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ winset(src, "legacy_output_selector", "left=output_legacy")
if(!tgui_panel || !istype(tgui_panel))
log_tgui(src, "tgui_panel datum is missing",
context = "verb/fix_tgui_panel")
tgui_panel = new(src)
tgui_panel.initialize(force = TRUE)
// Force show the panel to see if there are any errors
- winset(src, "output", "is-disabled=1&is-visible=0")
- winset(src, "browseroutput", "is-disabled=0;is-visible=1")
+ winset(src, "legacy_output_selector", "left=output_browser")
diff --git a/html/changelogs/20667-516fixes.yml b/html/changelogs/20667-516fixes.yml
new file mode 100644
index 00000000000..3e5ca51878e
--- /dev/null
+++ b/html/changelogs/20667-516fixes.yml
@@ -0,0 +1,8 @@
+author: harry, shadowlarkens, Absolucy
+
+delete-after: True
+
+changes:
+ - bugfix: "tgui-say now opens properly again."
+ - bugfix: "the chat no longer flickers weirdly."
+ - bugfix: "the chat now exports properly."
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 7b6022c53f3..9620c3e3447 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -1130,14 +1130,26 @@ window "outputwindow"
command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\""
is-flat = true
button-type = pushbox
- elem "browseroutput"
- type = BROWSER
+ elem "legacy_output_selector"
+ type = CHILD
pos = 0,0
size = 640x456
anchor1 = 0,0
anchor2 = 100,100
+ saved-params = "splitter"
+ left = "output_legacy"
+ is-vert = false
+
+window "output_legacy"
+ elem "output_legacy"
+ type = MAIN
+ pos = 0,0
+ size = 640x456
+ anchor1 = -1,-1
+ anchor2 = -1,-1
background-color = none
- saved-params = ""
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
elem "output"
type = OUTPUT
pos = 0,0
@@ -1145,6 +1157,24 @@ window "outputwindow"
anchor1 = 0,0
anchor2 = 100,100
is-default = true
+
+window "output_browser"
+ elem "output_browser"
+ type = MAIN
+ pos = 0,0
+ size = 640x456
+ anchor1 = -1,-1
+ anchor2 = -1,-1
+ background-color = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "browseroutput"
+ type = BROWSER
+ pos = 0,0
+ size = 640x456
+ anchor1 = 0,0
+ anchor2 = 100,100
+ background-color = none
saved-params = ""
window "preferences_window"
diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml
index 7fee3791fbb..4ab8f84dccc 100644
--- a/tgui/.eslintrc.yml
+++ b/tgui/.eslintrc.yml
@@ -337,7 +337,7 @@ rules:
## Require or disallow named function expressions
# func-names: error
## Enforce the consistent use of either function declarations or expressions
- func-style: [error, expression]
+ # func-style: [error, expression]
## Enforce line breaks between arguments of a function call
# function-call-argument-newline: error
## Enforce consistent line breaks inside function parentheses
diff --git a/tgui/global.d.ts b/tgui/global.d.ts
index 542788717ab..d21ac79b0f8 100644
--- a/tgui/global.d.ts
+++ b/tgui/global.d.ts
@@ -162,6 +162,11 @@ type ByondType = {
*/
parseJson(text: string): any;
+ /**
+ * Downloads a blob, platform-agnostic
+ */
+ saveBlob(blob: Blob, filename: string, ext: string): void;
+
/**
* Sends a message to `/datum/tgui_window` which hosts this window instance.
*/
diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts
new file mode 100644
index 00000000000..b3e6a4e814f
--- /dev/null
+++ b/tgui/packages/common/keys.ts
@@ -0,0 +1,139 @@
+/**
+ * ### Key codes.
+ * event.keyCode is deprecated, use this reference instead.
+ *
+ * Handles modifier keys (Shift, Alt, Control) and arrow keys.
+ *
+ * For alphabetical keys, use the actual character (e.g. 'a') instead of the key code.
+ * Don't access Esc or Escape directly, use isEscape() instead
+ *
+ * Something isn't here that you want? Just add it:
+ * @url https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
+ * @usage
+ * ```ts
+ * import { KEY } from 'tgui/common/keys';
+ *
+ * if (event.key === KEY.Enter) {
+ * // do something
+ * }
+ * ```
+ *
+ *
+ */
+export enum KEY {
+ A = 'a',
+ Alt = 'Alt',
+ Backspace = 'Backspace',
+ Control = 'Control',
+ D = 'd',
+ Delete = 'Delete',
+ Down = 'ArrowDown',
+ E = 'e',
+ End = 'End',
+ Enter = 'Enter',
+ Esc = 'Esc',
+ Escape = 'Escape',
+ Home = 'Home',
+ Insert = 'Insert',
+ Left = 'ArrowLeft',
+ N = 'n',
+ PageDown = 'PageDown',
+ PageUp = 'PageUp',
+ Right = 'ArrowRight',
+ S = 's',
+ Shift = 'Shift',
+ Space = ' ',
+ Tab = 'Tab',
+ Up = 'ArrowUp',
+ W = 'w',
+ Z = 'z',
+}
+
+/**
+ * ### isEscape
+ *
+ * Checks if the user has hit the 'ESC' key on their keyboard.
+ * There's a weirdness in BYOND where this could be either the string
+ * 'Escape' or 'Esc' depending on the browser. This function handles
+ * both cases.
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key is Escape or Esc, false otherwise
+ */
+export function isEscape(key: string): boolean {
+ return key === KEY.Esc || key === KEY.Escape;
+}
+
+/**
+ * ### isAlphabetic
+ *
+ * Checks if the user has hit any alphabetic key (a - z)
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key is in the range of a-z
+ */
+export function isAlphabetic(key: string): boolean {
+ return key >= KEY.A && key <= KEY.Z;
+}
+
+/**
+ * ### isNumeric
+ *
+ * Checks if the user has hit any numeric key (0 - 9)
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key is in the range of 0 - 9
+ */
+export function isNumeric(key: string): boolean {
+ return key >= '0' && key <= '9';
+}
+
+/**
+ * ### isCardinal
+ *
+ * Checks if the user has hit any cardinal key (n s w e)
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key matches any cardinal n s w e
+ */
+export function isCardinal(key: string): boolean {
+ return key === KEY.N || key === KEY.S || key === KEY.W || key === KEY.E;
+}
+
+/**
+ * ### isArrow
+ *
+ * Checks if the user has hit any arrow key
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key matches any arrow keys
+ */
+export function isArrow(key: string): boolean {
+ return (
+ key === KEY.Up || key === KEY.Down || key === KEY.Left || key === KEY.Right
+ );
+}
+
+/**
+ * ### isWasd
+ *
+ * Checks if the user has hit any w a s d key
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key matches any w a s d
+ */
+export function isWasd(key: string): boolean {
+ return key === KEY.W || key === KEY.A || key === KEY.S || key === KEY.D;
+}
+
+/**
+ * ### isMovement
+ *
+ * Checks if the user has hit any movement key (w a s d and arrow keys)
+ *
+ * @param key - the key to check, typically from event.key
+ * @returns true if key matches any movement key w a s d and arrow keys
+ */
+export function isMovement(key: string): boolean {
+ return isWasd(key) || isArrow(key);
+}
diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js
index ba0cac8772b..23e7d232cd7 100644
--- a/tgui/packages/tgui-panel/chat/middleware.js
+++ b/tgui/packages/tgui-panel/chat/middleware.js
@@ -21,7 +21,7 @@ const saveChatToStorage = async (store) => {
const settings = selectSettings(store.getState());
const state = selectChat(store.getState());
- if (!window.hubStorage) {
+ if (!window.hubStorage && !Byond.TRIDENT) {
const indexedDbBackend = await storage.backendPromise;
indexedDbBackend.processChatMessages(chatRenderer.storeQueue);
} else {
@@ -44,7 +44,7 @@ const loadChatFromStorage = async (store) => {
const state = await storage.get('chat-state');
let messages;
- if (!window.hubStorage) {
+ if (!window.hubStorage && !Byond.TRIDENT) {
messages = await (await storage.backendPromise).getChatMessages();
} else {
messages = await storage.get('chat-messages');
diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.js
index 53a4c7c2dc3..fe454411516 100644
--- a/tgui/packages/tgui-panel/chat/renderer.js
+++ b/tgui/packages/tgui-panel/chat/renderer.js
@@ -579,13 +579,13 @@ class ChatRenderer {
+ '\n'
+ '