Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions components/news/NewsViewCount.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { computed } from "vue";
import { Eye } from "lucide-vue-next";
import FiveStackToolTip from "~/components/FiveStackToolTip.vue";

/**
* View counter for a news post. The count only exists on the admin queries
* (newsPostsAdmin / newsPostAdmin), so every call site is already gated on
* `canPostNews` — this is chrome for authors, not readers.
*/
const props = defineProps<{
count?: string | number | null;
}>();

const formatted = computed(() => Number(props.count || 0).toLocaleString());
</script>

<template>
<FiveStackToolTip as-child :delay-duration="120">
<template #trigger>
<span
class="inline-flex items-center gap-1 text-xs text-muted-foreground"
>
<Eye class="h-3.5 w-3.5" />
{{ formatted }}
</span>
</template>
{{ $t("pages.news.manage.views") }}
</FiveStackToolTip>
</template>
136 changes: 136 additions & 0 deletions components/utility/UtilityPracticeCommands.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<script setup lang="ts">
/**
* What you can type once you are standing on the server.
*
* The panel hands over a connect string and stops there, so everything the
* practice plugin can do -- drills, executes, the throw you just made -- was
* reachable only by already knowing to type `.help` in chat. This is that same
* list, in the same order the plugin prints it, on the surface that put you on
* the server in the first place.
*
* Closed by default: the dialog has no scroller of its own, and twenty rows
* unfolding under the join button would push the way out of the session off the
* bottom of the screen.
*/
import { ref } from "vue";
import { ChevronDown, Terminal } from "lucide-vue-next";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "~/components/ui/collapsible";

const open = ref(false);

const base = "pages.utility.practice.commands";

// Grouped the way a session is actually used -- find a throw, drill it, then
// the conveniences -- rather than alphabetically, which puts `.bloom` first and
// `.save` two thirds of the way down.
const groups = [
{
label: `${base}.group_lineups`,
items: [
{ command: ".save <name>", desc: `${base}.save` },
{ command: ".load <query>", desc: `${base}.load` },
{ command: ".next / .prev", desc: `${base}.next` },
{ command: ".jump", desc: `${base}.jump` },
{ command: ".rethrow", desc: `${base}.rethrow` },
{ command: ".last / .back <n>", desc: `${base}.last` },
{ command: ".list / .reload / .delete", desc: `${base}.list` },
],
},
{
label: `${base}.group_drills`,
items: [
{ command: ".drill [count] [worst]", desc: `${base}.drill` },
{ command: ".skip", desc: `${base}.skip` },
{ command: ".drill / .cancel", desc: `${base}.cancel` },
{ command: ".playbook / .run", desc: `${base}.playbook` },
{ command: ".playbook stop", desc: `${base}.playbook_stop` },
],
},
{
label: `${base}.group_tools`,
items: [
{ command: ".bloom", desc: `${base}.bloom` },
{ command: ".solve [name]", desc: `${base}.solve` },
{ command: ".pos save <name> / .pos <name>", desc: `${base}.pos` },
{ command: ".spawn <n>", desc: `${base}.spawn` },
{ command: ".noclip / .god", desc: `${base}.noclip` },
{ command: ".timer", desc: `${base}.timer` },
{ command: ".solo", desc: `${base}.solo` },
{ command: ".clear", desc: `${base}.clear` },
{ command: ".help", desc: `${base}.help` },
],
},
];

const sectionLabel =
"flex items-center gap-1.5 font-mono text-[0.62rem] uppercase tracking-[0.16em] text-muted-foreground";
</script>

<template>
<Collapsible v-model:open="open" class="space-y-2">
<CollapsibleTrigger as-child>
<button
type="button"
:class="[
sectionLabel,
'w-full cursor-pointer border-0 bg-transparent p-0 text-left transition-colors hover:text-foreground',
]"
>
<Terminal class="h-3 w-3" />
{{ $t(`${base}.title`) }}
<ChevronDown
class="ml-auto h-3.5 w-3.5 transition-transform duration-150"
:class="open ? 'rotate-180' : ''"
/>
</button>
</CollapsibleTrigger>

<CollapsibleContent>
<div class="space-y-2 pt-1">
<p class="text-xs leading-relaxed text-muted-foreground/80">
{{ $t(`${base}.hint`) }}
</p>

<!-- Its own scroller, capped: the dialog it sits in is a fixed panel
with no scroll of its own, so an open list has to keep its height
rather than growing past the viewport. -->
<div class="max-h-60 space-y-3 overflow-y-auto overscroll-contain pr-1">
<div v-for="group of groups" :key="group.label" class="space-y-1">
<div
class="font-mono text-[0.55rem] uppercase tracking-[0.16em] text-muted-foreground/60"
>
{{ $t(group.label) }}
</div>
<div
v-for="item of group.items"
:key="item.command"
class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5"
>
<code
class="font-mono text-[0.68rem] text-[hsl(var(--tac-amber))]"
>
{{ item.command }}
</code>
<span class="text-[0.7rem] text-muted-foreground">
{{ $t(item.desc) }}
</span>
</div>
</div>
</div>

<!-- The commands are chat text, so any of them binds to a key -- the
one thing the in-game .help output never says. -->
<p class="text-[0.7rem] leading-relaxed text-muted-foreground/70">
{{ $t(`${base}.bind_hint`) }}
<code class="font-mono text-[0.68rem] text-muted-foreground">
bind n "say .next"
</code>
</p>
</div>
</CollapsibleContent>
</Collapsible>
</template>
8 changes: 8 additions & 0 deletions components/utility/UtilityPracticeSessionPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Spinner } from "~/components/ui/spinner";
import { Separator } from "~/components/ui/separator";
import ClipBoard from "~/components/ClipBoard.vue";
import PlayerSearch from "~/components/PlayerSearch.vue";
import UtilityPracticeCommands from "~/components/utility/UtilityPracticeCommands.vue";
import { toast } from "~/components/ui/toast";
import getGraphqlClient from "~/graphql/getGraphqlClient";
import {
Expand Down Expand Up @@ -444,6 +445,13 @@ const panelCta = "w-full font-bold uppercase tracking-[0.22em]";
</div>
</div>
</template>

<!-- Everything the server can do once you are on it. The connect string
is only half of what a practice session is, and the other half was
discoverable only by knowing to type .help in chat. -->
<Separator />

<UtilityPracticeCommands />
</template>

<!-- Whatever the surface wants between the session and the way out of it. -->
Expand Down
8 changes: 8 additions & 0 deletions graphql/newsGraphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ export const newsPostAdminFields = {
updated_at: true,
view_count: true,
} as const;

// Views live on the admin actions only — the public news_articles table never
// exposes view_count — so surfaces that show counts to authors pull this
// alongside their normal query instead of widening the guest permission.
export const newsPostViewFields = {
id: true,
view_count: true,
} as const;
31 changes: 30 additions & 1 deletion i18n/locales/ar_SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,36 @@
"switch_failed": "تعذّر تغيير الخريطة",
"ask_host": "اطلب من {host} تبديلها.",
"ask_the_host": "اطلب من المضيف تبديلها.",
"nav_switching": "قيد التبديل"
"nav_switching": "قيد التبديل",
"commands": {
"title": "أوامر داخل اللعبة",
"hint": "اكتبها في الدردشة بعد دخولك إلى الخادم، والأمر .help يعرض القائمة نفسها داخل اللعبة.",
"bind_hint": "هي أوامر دردشة، لذا يمكن ربط أي منها بمفتاح:",
"group_lineups": "الرميات",
"group_drills": "التمارين والتنفيذات",
"group_tools": "أدوات الخادم",
"save": "يحفظ الرمية التي قمت بها للتو",
"load": "ينقلك إلى رمية مطابقة",
"next": "التنقل في نتائج آخر بحث",
"jump": "قف حيث تسقط الرمية المحمّلة",
"rethrow": "العودة إلى الرمية المحمّلة",
"last": "العودة إلى رمية قمت بها",
"list": "أدر مكتبتك من داخل اللعبة",
"drill": "يدرّبك على مكتبتك بثلاث رميات لكل واحدة ويمنحك نتيجة",
"skip": "تخطَّ الرمية التي تتدرب عليها",
"cancel": "أوقف التمرين الجاري",
"playbook": "يشغّل التنفيذ المحمّل بتوقيتاته",
"playbook_stop": "يوقفه",
"bloom": "يرسم مكان سقوط الدخان المحمّل",
"solve": "يبحث عن رمية إلى المكان الذي تنظر إليه",
"pos": "احفظ موضعًا وعُد إليه",
"spawn": "ينقلك إلى نقطة ظهور",
"noclip": "الطيران، وبلا أي ضرر",
"timer": "تشغيل وإيقاف ساعة الإيقاف",
"solo": "شاهد معايناتك وحدها",
"clear": "يمسح الرمية المحمّلة والعلامات والأشباح",
"help": "يعرض كل الأوامر داخل اللعبة"
}
},
"collections": {
"tab": "المجموعات",
Expand Down
31 changes: 30 additions & 1 deletion i18n/locales/da_DK.json
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,36 @@
"switch_failed": "Kunne ikke skifte bane",
"ask_host": "Bed {host} om at skifte den.",
"ask_the_host": "Bed værten om at skifte den.",
"nav_switching": "Skifter"
"nav_switching": "Skifter",
"commands": {
"title": "Kommandoer i spillet",
"hint": "Skriv dem i chatten, når du er inde på serveren. .help viser den samme liste i spillet.",
"bind_hint": "Det er chatkommandoer, så de kan alle bindes til en tast:",
"group_lineups": "Opstillinger",
"group_drills": "Drills og executes",
"group_tools": "Serverværktøjer",
"save": "gemmer det kast, du lige har lavet",
"load": "stiller dig på en opstilling, der matcher",
"next": "gå gennem den seneste søgning",
"jump": "stå der, hvor den indlæste opstilling lander",
"rethrow": "tilbage til den indlæste opstilling",
"last": "tilbage til et kast, du har lavet",
"list": "styr dit bibliotek inde fra spillet",
"drill": "kører din bog igennem, tre kast hver, og giver point",
"skip": "spring den opstilling over, du træner",
"cancel": "stop den drill, du er i gang med",
"playbook": "kører den indlæste execute på dens timings",
"playbook_stop": "stopper den",
"bloom": "tegner op, hvor den indlæste smoke lander",
"solve": "finder et kast til det sted, du kigger på",
"pos": "gem en position, og vend tilbage til den",
"spawn": "teleporterer dig til et spawnpunkt",
"noclip": "flyv, og tag ikke skade",
"timer": "start og stop et stopur",
"solo": "se kun dine egne forhåndsvisninger",
"clear": "rydder den indlæste opstilling, markører og ghosts",
"help": "viser alle kommandoer i spillet"
}
},
"collections": {
"tab": "Samlinger",
Expand Down
31 changes: 30 additions & 1 deletion i18n/locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,36 @@
"switch_failed": "Die Map konnte nicht gewechselt werden",
"ask_host": "Bitte {host}, sie zu wechseln.",
"ask_the_host": "Bitte den Host, sie zu wechseln.",
"nav_switching": "Wechselt"
"nav_switching": "Wechselt",
"commands": {
"title": "Befehle im Spiel",
"hint": "Tippe sie im Chat ein, sobald du auf dem Server bist. .help gibt dieselbe Liste im Spiel aus.",
"bind_hint": "Es sind Chat-Befehle, also lässt sich jeder auf eine Taste legen:",
"group_lineups": "Lineups",
"group_drills": "Drills & Executes",
"group_tools": "Server-Werkzeuge",
"save": "speichert den Wurf, den du gerade gemacht hast",
"load": "setzt dich auf ein passendes Lineup",
"next": "durch die letzte Suche blättern",
"jump": "stell dich dorthin, wo das geladene Lineup landet",
"rethrow": "zurück zum geladenen Lineup",
"last": "zurück zu einem Wurf von dir",
"list": "verwalte deine Bibliothek aus dem Spiel",
"drill": "drillt dein Buch, je drei Würfe, und wertet es aus",
"skip": "überspringt das Lineup, das du gerade drillst",
"cancel": "beendet den laufenden Drill",
"playbook": "spielt den geladenen Execute nach seinen Zeiten ab",
"playbook_stop": "stoppt ihn",
"bloom": "zeigt an, wo der geladene Smoke landet",
"solve": "sucht einen Wurf auf den Punkt, den du ansiehst",
"pos": "eine Position speichern und wieder dorthin zurück",
"spawn": "teleportiert dich zu einem Spawnpunkt",
"noclip": "fliegen, und keinen Schaden mehr nehmen",
"timer": "Stoppuhr starten und stoppen",
"solo": "nur noch deine eigenen Vorschauen sehen",
"clear": "löscht das geladene Lineup, Marker und Ghosts",
"help": "gibt alle Befehle im Spiel aus"
}
},
"collections": {
"tab": "Sammlungen",
Expand Down
31 changes: 30 additions & 1 deletion i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,36 @@
"switch_failed": "Could not change the map",
"ask_host": "Ask {host} to switch it.",
"ask_the_host": "Ask the host to switch it.",
"nav_switching": "Switching"
"nav_switching": "Switching",
"commands": {
"title": "In-game commands",
"hint": "Type these in chat once you are on the server. .help prints the same list in game.",
"bind_hint": "They are chat commands, so any of them binds to a key:",
"group_lineups": "Lineups",
"group_drills": "Drills & executes",
"group_tools": "Server tools",
"save": "saves the throw you just made",
"load": "teleports you onto a matching lineup",
"next": "walk the last search",
"jump": "stand where the loaded lineup lands",
"rethrow": "back to the loaded lineup",
"last": "back to a throw you made",
"list": "manage your library from in game",
"drill": "drills your book, three throws each, and scores it",
"skip": "skip the lineup you are drilling",
"cancel": "stop the drill you are in",
"playbook": "runs the loaded execute on its timings",
"playbook_stop": "stops it",
"bloom": "outlines where the loaded smoke lands",
"solve": "finds a throw onto the spot you are looking at",
"pos": "save a position and come back to it",
"spawn": "teleports to a spawn point",
"noclip": "fly, and stop taking damage",
"timer": "start and stop a stopwatch",
"solo": "see only your own previews",
"clear": "clears the loaded lineup, markers and ghosts",
"help": "prints every command in game"
}
},
"collections": {
"tab": "Collections",
Expand Down
31 changes: 30 additions & 1 deletion i18n/locales/es_ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,36 @@
"switch_failed": "No se pudo cambiar el mapa",
"ask_host": "Pídele a {host} que lo cambie.",
"ask_the_host": "Pídele al anfitrión que lo cambie.",
"nav_switching": "Cambiando"
"nav_switching": "Cambiando",
"commands": {
"title": "Comandos en el juego",
"hint": "Escríbelos en el chat una vez estés en el servidor. .help imprime la misma lista dentro del juego.",
"bind_hint": "Son comandos de chat, así que cualquiera se puede asignar a una tecla:",
"group_lineups": "Lineups",
"group_drills": "Drills y ejecuciones",
"group_tools": "Herramientas del servidor",
"save": "guarda el lanzamiento que acabas de hacer",
"load": "te coloca sobre un lineup que coincida",
"next": "recorrer la última búsqueda",
"jump": "colócate donde cae el lineup cargado",
"rethrow": "vuelve al lineup cargado",
"last": "vuelve a un lanzamiento tuyo",
"list": "gestiona tu biblioteca desde el juego",
"drill": "entrena tu repertorio, tres lanzamientos cada uno, y lo puntúa",
"skip": "salta el lineup que estás entrenando",
"cancel": "detén el drill en el que estás",
"playbook": "reproduce la ejecución cargada con sus tiempos",
"playbook_stop": "la detiene",
"bloom": "dibuja dónde cae el humo cargado",
"solve": "busca un lanzamiento hacia el punto que estás mirando",
"pos": "guarda una posición y vuelve a ella",
"spawn": "te teletransporta a un punto de aparición",
"noclip": "vuela y deja de recibir daño",
"timer": "inicia y detiene un cronómetro",
"solo": "ver solo tus propias vistas previas",
"clear": "borra el lineup cargado, los marcadores y los ghosts",
"help": "imprime todos los comandos en el juego"
}
},
"collections": {
"tab": "Colecciones",
Expand Down
Loading