- You can keep replying to this email — your replies are added to the ticket automatically.
+ You can keep replying to this email. Your replies are added to the ticket automatically.
If you would rather follow the ticket on the web, you can link it to a {{ registry_name }} account:
diff --git a/api/src/emails/templates/support_ticket_auto_reply.txt.hbs b/api/src/emails/templates/support_ticket_auto_reply.txt.hbs
index 2f429eb8d..06ced35da 100644
--- a/api/src/emails/templates/support_ticket_auto_reply.txt.hbs
+++ b/api/src/emails/templates/support_ticket_auto_reply.txt.hbs
@@ -3,7 +3,7 @@ Hey{{#if name}} {{ name }}{{/if}},
Thanks for getting in touch. We have opened support ticket {{ ticket_number }} for your email, and someone will get back to you as soon as possible.
-You can keep replying to this email — your replies are added to the ticket automatically.
+You can keep replying to this email. Your replies are added to the ticket automatically.
If you would rather follow the ticket on the web, you can link it to a {{ registry_name }} account here:
diff --git a/api/src/emails/templates/support_ticket_created.html.hbs b/api/src/emails/templates/support_ticket_created.html.hbs
index fb1d52bc2..4cc37e6bc 100644
--- a/api/src/emails/templates/support_ticket_created.html.hbs
+++ b/api/src/emails/templates/support_ticket_created.html.hbs
@@ -26,7 +26,7 @@
You will receive emails whenever a new message is posted to the ticket.
- You can reply to the ticket via the link above, or simply reply to this email — your replies are added to the ticket automatically.
+ You can reply to the ticket via the link above, or simply reply to this email. Your replies are added to the ticket automatically.
Cheers,
diff --git a/api/src/emails/templates/support_ticket_created.txt.hbs b/api/src/emails/templates/support_ticket_created.txt.hbs
index 19359c9b0..94e2206f7 100644
--- a/api/src/emails/templates/support_ticket_created.txt.hbs
+++ b/api/src/emails/templates/support_ticket_created.txt.hbs
@@ -5,7 +5,7 @@ You have created a support ticket on {{ registry_name }}.
You will receive emails to this address whenever a new message is posted to the ticket.
-You can also view the ticket on {{ registry_url }}ticket/{{ ticket_id }}. You can reply to the ticket from there, or simply reply to this email — your replies are added to the ticket automatically.
+You can also view the ticket on {{ registry_url }}ticket/{{ ticket_id }}. You can reply to the ticket from there, or simply reply to this email. Your replies are added to the ticket automatically.
Cheers,
{{ registry_name }}
diff --git a/api/src/emails/templates/support_ticket_message.html.hbs b/api/src/emails/templates/support_ticket_message.html.hbs
index fbbf05216..5277f1488 100644
--- a/api/src/emails/templates/support_ticket_message.html.hbs
+++ b/api/src/emails/templates/support_ticket_message.html.hbs
@@ -16,7 +16,7 @@
- You can reply to the ticket via the link above, or simply reply to this email — your replies are added to the ticket automatically.
+ You can reply to the ticket via the link above, or simply reply to this email. Your replies are added to the ticket automatically.
Cheers,
diff --git a/api/src/emails/templates/support_ticket_message.txt.hbs b/api/src/emails/templates/support_ticket_message.txt.hbs
index 853af7263..07fa53789 100644
--- a/api/src/emails/templates/support_ticket_message.txt.hbs
+++ b/api/src/emails/templates/support_ticket_message.txt.hbs
@@ -7,7 +7,7 @@ You have received a reply to your support ticket on {{ registry_name }}:
{{content}}
---
-To view the ticket, you can go to {{ registry_url }}ticket/{{ ticket_id }}. You can reply to the ticket from there, or simply reply to this email — your replies are added to the ticket automatically.
+To view the ticket, you can go to {{ registry_url }}ticket/{{ ticket_id }}. You can reply to the ticket from there, or simply reply to this email. Your replies are added to the ticket automatically.
Cheers,
{{ registry_name }}
diff --git a/api/src/tree_sitter.rs b/api/src/tree_sitter.rs
index f3dedef2e..4910aee94 100644
--- a/api/src/tree_sitter.rs
+++ b/api/src/tree_sitter.rs
@@ -140,9 +140,9 @@ pub fn tree_sitter_language_cb(
) -> Option<&'static HighlightConfiguration> {
for lang in lang.split(',') {
let cfg = match lang.trim() {
- "js" | "javascript" => tree_sitter_language_javascript(),
+ "js" | "javascript" | "mjs" | "cjs" => tree_sitter_language_javascript(),
"jsx" => tree_sitter_language_jsx(),
- "ts" | "typescript" => tree_sitter_language_typescript(),
+ "ts" | "typescript" | "mts" | "cts" => tree_sitter_language_typescript(),
"tsx" => tree_sitter_language_tsx(),
"json" | "jsonc" => tree_sitter_language_json(),
"css" => tree_sitter_language_css(),
@@ -153,6 +153,9 @@ pub fn tree_sitter_language_cb(
"rs" | "rust" => tree_sitter_language_rust(),
"html" => tree_sitter_language_html(),
"sh" | "bash" => tree_sitter_language_bash(),
+ "toml" => tree_sitter_language_toml(),
+ "yaml" | "yml" => tree_sitter_language_yaml(),
+ "c" | "h" => tree_sitter_language_c(),
_ => continue,
};
return Some(cfg);
@@ -393,3 +396,87 @@ fn tree_sitter_language_bash() -> &'static HighlightConfiguration {
config
})
}
+
+fn tree_sitter_language_toml() -> &'static HighlightConfiguration {
+ static CONFIG: OnceLock = OnceLock::new();
+ CONFIG.get_or_init(|| {
+ let mut config = HighlightConfiguration::new(
+ tree_sitter_toml_ng::language(),
+ "toml",
+ tree_sitter_toml_ng::HIGHLIGHTS_QUERY,
+ "",
+ "",
+ )
+ .expect("failed to initialize tree_sitter_toml highlighter");
+ config.configure(CAPTURE_NAMES);
+ config
+ })
+}
+
+fn tree_sitter_language_yaml() -> &'static HighlightConfiguration {
+ static CONFIG: OnceLock = OnceLock::new();
+ CONFIG.get_or_init(|| {
+ let mut config = HighlightConfiguration::new(
+ tree_sitter_yaml::language(),
+ "yaml",
+ tree_sitter_yaml::HIGHLIGHTS_QUERY,
+ "",
+ "",
+ )
+ .expect("failed to initialize tree_sitter_yaml highlighter");
+ config.configure(CAPTURE_NAMES);
+ config
+ })
+}
+
+fn tree_sitter_language_c() -> &'static HighlightConfiguration {
+ static CONFIG: OnceLock = OnceLock::new();
+ CONFIG.get_or_init(|| {
+ let mut config = HighlightConfiguration::new(
+ tree_sitter_c::language(),
+ "c",
+ tree_sitter_c::HIGHLIGHT_QUERY,
+ "",
+ "",
+ )
+ .expect("failed to initialize tree_sitter_c highlighter");
+ config.configure(CAPTURE_NAMES);
+ config
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use comrak::adapters::SyntaxHighlighterAdapter;
+
+ fn highlight(lang: &str, code: &str) -> String {
+ let adapter = ComrakAdapter {
+ show_line_numbers: false,
+ };
+ let mut out = Vec::new();
+ adapter
+ .write_highlighted(&mut out, Some(lang), code)
+ .unwrap();
+ String::from_utf8(out).unwrap()
+ }
+
+ #[test]
+ fn highlights_added_languages() {
+ for (lang, code) in [
+ ("toml", "[package]\nname = \"demo\"\n"),
+ ("yaml", "key: value\nlist:\n - a\n"),
+ ("yml", "key: value\n"),
+ ("c", "int main(void) { return 0; }\n"),
+ ("mjs", "export const x = 1;\n"),
+ ("mts", "export const x: number = 1;\n"),
+ ] {
+ assert!(
+ tree_sitter_language_cb(lang).is_some(),
+ "no highlighter for {lang}"
+ );
+ let html = highlight(lang, code);
+ assert!(html.contains("
diff --git a/frontend/deno.json b/frontend/deno.json
index 31d440497..6c3d6e552 100644
--- a/frontend/deno.json
+++ b/frontend/deno.json
@@ -14,7 +14,7 @@
}
},
"imports": {
- "@deno/doc": "jsr:@deno/doc@^0.204.0",
+ "@deno/doc": "jsr:@deno/doc@^0.205.0",
"@fresh/plugin-tailwind": "jsr:@fresh/plugin-tailwind@^1.0.0",
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.1.2",
"@tailwindcss/vite": "npm:@tailwindcss/vite@^4.3.0",
diff --git a/frontend/deno.lock b/frontend/deno.lock
index c88c5e730..00dddeac6 100644
--- a/frontend/deno.lock
+++ b/frontend/deno.lock
@@ -2,7 +2,7 @@
"version": "5",
"specifiers": {
"jsr:@deno/cache-dir@0.25": "0.25.0",
- "jsr:@deno/doc@0.204": "0.204.0",
+ "jsr:@deno/doc@0.205": "0.205.0",
"jsr:@deno/esbuild-plugin@^1.2.0": "1.2.1",
"jsr:@deno/gfm@0.11": "0.11.0",
"jsr:@deno/graph@0.100": "0.100.1",
@@ -29,7 +29,6 @@
"jsr:@std/fmt@^1.0.7": "1.0.10",
"jsr:@std/fmt@^1.0.8": "1.0.10",
"jsr:@std/front-matter@1": "1.0.9",
- "jsr:@std/fs@1": "1.0.24",
"jsr:@std/fs@^1.0.19": "1.0.24",
"jsr:@std/fs@^1.0.24": "1.0.24",
"jsr:@std/fs@^1.0.6": "1.0.24",
@@ -113,8 +112,8 @@
"jsr:@std/path@^1.0.8"
]
},
- "@deno/doc@0.204.0": {
- "integrity": "2098f4924af3b193984fd0a195321bc12a996a90a428fb93d27746bb4efb5009",
+ "@deno/doc@0.205.0": {
+ "integrity": "86e01dd7f2bc8b5d9059b3458cfce10a9e8a9f06245be612d82e09f21373c36e",
"dependencies": [
"jsr:@deno/cache-dir",
"jsr:@deno/graph@0.100"
@@ -260,7 +259,6 @@
"@std/fs@1.0.24": {
"integrity": "f3061b45b81673a2bece689da041df32d174be064c89eb6397fb5718d3fb7877",
"dependencies": [
- "jsr:@std/internal",
"jsr:@std/path@^1.1.5"
]
},
@@ -3021,7 +3019,7 @@
},
"workspace": {
"dependencies": [
- "jsr:@deno/doc@0.204",
+ "jsr:@deno/doc@0.205",
"jsr:@deno/gfm@0.11",
"jsr:@fresh/core@^2.3.3",
"jsr:@fresh/plugin-tailwind@1",
diff --git a/frontend/islands/ClaimTicket.tsx b/frontend/islands/ClaimTicket.tsx
index 693ca154e..e5b6653ae 100644
--- a/frontend/islands/ClaimTicket.tsx
+++ b/frontend/islands/ClaimTicket.tsx
@@ -6,10 +6,14 @@ import { api, path } from "../utils/api.ts";
/// rather than an account. Claiming binds it to the signed-in account, so it
/// shows up alongside their other tickets instead of only in their inbox.
export function ClaimTicket(
- { ticketId, claimToken, signedIn }: {
+ { ticketId, claimToken, signedIn, returnTo }: {
ticketId: string;
claimToken: string;
signedIn: boolean;
+ /// Where to come back to after signing in — this page, claim token and all.
+ /// Passed in rather than read from `location`, which does not exist when
+ /// this is rendered on the server.
+ returnTo: string;
},
) {
const [error, setError] = useState(null);
@@ -53,7 +57,12 @@ export function ClaimTicket(
)
: (
- Sign in{" "}
+
+ Sign in
+ {" "}
to link it to your account. You can also just reply to our email —
that works either way.
(function Ticket({
data,
state,
+ url,
}) {
const ticket = data.ticket;
// The reporter of an unclaimed ticket reaches this page with the token from
@@ -80,6 +81,10 @@ export default define.page(function Ticket({
ticketId={ticket.id}
claimToken={data.claimToken}
signedIn={state.user !== null}
+ // Carries the claim token through the login round trip, so the
+ // reporter lands back here able to claim rather than on the home page
+ // with the link they followed spent.
+ returnTo={`${url.pathname}${url.search}`}
/>
)}
diff --git a/frontend/utils/api_types.ts b/frontend/utils/api_types.ts
index 994288f78..d04b84e49 100644
--- a/frontend/utils/api_types.ts
+++ b/frontend/utils/api_types.ts
@@ -196,6 +196,7 @@ export interface SourceFile {
kind: "file";
size: number;
view: string | null;
+ rendered: string | null;
}
export interface PackageVersionSource {