diff --git a/components/news/NewsViewCount.vue b/components/news/NewsViewCount.vue new file mode 100644 index 000000000..b0fa5b557 --- /dev/null +++ b/components/news/NewsViewCount.vue @@ -0,0 +1,30 @@ + + + diff --git a/components/utility/UtilityPracticeCommands.vue b/components/utility/UtilityPracticeCommands.vue new file mode 100644 index 000000000..4b018d46e --- /dev/null +++ b/components/utility/UtilityPracticeCommands.vue @@ -0,0 +1,136 @@ + + + diff --git a/components/utility/UtilityPracticeSessionPanel.vue b/components/utility/UtilityPracticeSessionPanel.vue index 52dfaf5cd..ce2e118d6 100644 --- a/components/utility/UtilityPracticeSessionPanel.vue +++ b/components/utility/UtilityPracticeSessionPanel.vue @@ -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 { @@ -444,6 +445,13 @@ const panelCta = "w-full font-bold uppercase tracking-[0.22em]"; + + + + + diff --git a/graphql/newsGraphql.ts b/graphql/newsGraphql.ts index 286bf8ff9..a590de9dd 100644 --- a/graphql/newsGraphql.ts +++ b/graphql/newsGraphql.ts @@ -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; diff --git a/i18n/locales/ar_SA.json b/i18n/locales/ar_SA.json index 4cde648b7..19ea2f365 100644 --- a/i18n/locales/ar_SA.json +++ b/i18n/locales/ar_SA.json @@ -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": "المجموعات", diff --git a/i18n/locales/da_DK.json b/i18n/locales/da_DK.json index cf1e1fc64..f5bb965a4 100644 --- a/i18n/locales/da_DK.json +++ b/i18n/locales/da_DK.json @@ -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", diff --git a/i18n/locales/de_DE.json b/i18n/locales/de_DE.json index b00bd651f..0ef3a7a81 100644 --- a/i18n/locales/de_DE.json +++ b/i18n/locales/de_DE.json @@ -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", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 32a9ff2c3..cdf4ea2ea 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -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", diff --git a/i18n/locales/es_ES.json b/i18n/locales/es_ES.json index e0e04a93b..a1e3bf6f5 100644 --- a/i18n/locales/es_ES.json +++ b/i18n/locales/es_ES.json @@ -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", diff --git a/i18n/locales/fr_FR.json b/i18n/locales/fr_FR.json index dcf2fd4ae..2b6fe6e92 100644 --- a/i18n/locales/fr_FR.json +++ b/i18n/locales/fr_FR.json @@ -4106,7 +4106,36 @@ "switch_failed": "Impossible de changer de map", "ask_host": "Demandez à {host} de la changer.", "ask_the_host": "Demandez à l'hôte de la changer.", - "nav_switching": "Changement" + "nav_switching": "Changement", + "commands": { + "title": "Commandes en jeu", + "hint": "Tapez-les dans le chat une fois sur le serveur. .help affiche la même liste en jeu.", + "bind_hint": "Ce sont des commandes de chat : chacune peut donc être liée à une touche :", + "group_lineups": "Lineups", + "group_drills": "Drills et exécutions", + "group_tools": "Outils du serveur", + "save": "enregistre le lancer que vous venez de faire", + "load": "vous place sur un lineup correspondant", + "next": "parcourir la dernière recherche", + "jump": "vous place là où le lineup chargé retombe", + "rethrow": "retour au lineup chargé", + "last": "retour à un de vos lancers", + "list": "gérez votre bibliothèque depuis le jeu", + "drill": "enchaîne votre carnet, trois lancers chacun, et le note", + "skip": "passe le lineup en cours de drill", + "cancel": "arrête le drill en cours", + "playbook": "joue l'exécution chargée sur ses timings", + "playbook_stop": "l'arrête", + "bloom": "dessine où retombe le smoke chargé", + "solve": "trouve un lancer vers l'endroit que vous regardez", + "pos": "enregistrer une position et y revenir", + "spawn": "vous téléporte à un point d'apparition", + "noclip": "voler, et ne plus prendre de dégâts", + "timer": "démarrer et arrêter un chronomètre", + "solo": "ne voir que vos propres aperçus", + "clear": "efface le lineup chargé, les marqueurs et les ghosts", + "help": "affiche toutes les commandes en jeu" + } }, "collections": { "tab": "Collections", diff --git a/i18n/locales/it_IT.json b/i18n/locales/it_IT.json index aba939df0..595aa6d88 100644 --- a/i18n/locales/it_IT.json +++ b/i18n/locales/it_IT.json @@ -4106,7 +4106,36 @@ "switch_failed": "Impossibile cambiare mappa", "ask_host": "Chiedi a {host} di cambiarla.", "ask_the_host": "Chiedi all'host di cambiarla.", - "nav_switching": "Cambio mappa" + "nav_switching": "Cambio mappa", + "commands": { + "title": "Comandi in partita", + "hint": "Scrivili in chat una volta sul server. .help stampa la stessa lista in gioco.", + "bind_hint": "Sono comandi di chat, quindi ognuno si può assegnare a un tasto:", + "group_lineups": "Lineup", + "group_drills": "Drill ed execute", + "group_tools": "Strumenti del server", + "save": "salva il lancio che hai appena fatto", + "load": "ti mette su un lineup corrispondente", + "next": "scorri l'ultima ricerca", + "jump": "mettiti dove atterra il lineup caricato", + "rethrow": "torna al lineup caricato", + "last": "torna a un tuo lancio", + "list": "gestisci la tua libreria dal gioco", + "drill": "allena il tuo repertorio, tre lanci ciascuno, e lo valuta", + "skip": "salta il lineup che stai allenando", + "cancel": "ferma il drill in corso", + "playbook": "esegue l'execute caricato sui suoi tempi", + "playbook_stop": "lo ferma", + "bloom": "disegna dove atterra lo smoke caricato", + "solve": "trova un lancio sul punto che stai guardando", + "pos": "salva una posizione e torna lì", + "spawn": "ti teletrasporta a un punto di spawn", + "noclip": "vola, e non prendi più danni", + "timer": "avvia e ferma un cronometro", + "solo": "vedi solo le tue anteprime", + "clear": "cancella il lineup caricato, i marker e i ghost", + "help": "stampa tutti i comandi in gioco" + } }, "collections": { "tab": "Raccolte", diff --git a/i18n/locales/ja_JP.json b/i18n/locales/ja_JP.json index a11c0ec93..82579fef5 100644 --- a/i18n/locales/ja_JP.json +++ b/i18n/locales/ja_JP.json @@ -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": "自分の登録分を3投ずつドリルして採点", + "skip": "ドリル中のラインナップをスキップ", + "cancel": "実行中のドリルを止める", + "playbook": "読み込んだエグゼキュートをタイミング通りに再生", + "playbook_stop": "それを停止", + "bloom": "読み込んだスモークの着弾範囲を表示", + "solve": "見ている地点に届く投げを探す", + "pos": "位置を保存して、そこに戻る", + "spawn": "スポーン地点にテレポート", + "noclip": "飛行し、ダメージを受けなくなる", + "timer": "ストップウォッチの開始と停止", + "solo": "自分のプレビューだけを表示", + "clear": "読み込んだラインナップ、マーカー、ゴーストを消去", + "help": "全コマンドをゲーム内に表示" + } }, "collections": { "tab": "コレクション", diff --git a/i18n/locales/ko_KR.json b/i18n/locales/ko_KR.json index 80c2e6ec4..b25c9af32 100644 --- a/i18n/locales/ko_KR.json +++ b/i18n/locales/ko_KR.json @@ -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": "컬렉션", diff --git a/i18n/locales/pl_PL.json b/i18n/locales/pl_PL.json index 232f732bf..baf9569bb 100644 --- a/i18n/locales/pl_PL.json +++ b/i18n/locales/pl_PL.json @@ -4106,7 +4106,36 @@ "switch_failed": "Nie udało się zmienić mapy", "ask_host": "Poproś {host}, żeby ją przełączył.", "ask_the_host": "Poproś gospodarza, żeby ją przełączył.", - "nav_switching": "Przełączanie" + "nav_switching": "Przełączanie", + "commands": { + "title": "Komendy w grze", + "hint": "Wpisuj je na czacie, gdy już jesteś na serwerze. .help wypisuje tę samą listę w grze.", + "bind_hint": "To komendy czatu, więc każdą można podpiąć pod klawisz:", + "group_lineups": "Ustawki", + "group_drills": "Drille i execute", + "group_tools": "Narzędzia serwera", + "save": "zapisuje rzut, który właśnie wykonałeś", + "load": "stawia cię na pasującej ustawce", + "next": "przechodzenie po ostatnim wyszukiwaniu", + "jump": "stań tam, gdzie ląduje wczytana ustawka", + "rethrow": "z powrotem na wczytaną ustawkę", + "last": "z powrotem do twojego rzutu", + "list": "zarządzaj biblioteką z poziomu gry", + "drill": "przerabia twój zestaw, po trzy rzuty, i ocenia go", + "skip": "pomiń ustawkę, którą właśnie ćwiczysz", + "cancel": "przerwij trwający drill", + "playbook": "odtwarza wczytany execute w jego czasach", + "playbook_stop": "zatrzymuje go", + "bloom": "zaznacza, gdzie ląduje wczytany smoke", + "solve": "znajduje rzut w punkt, na który patrzysz", + "pos": "zapisz pozycję i wróć do niej", + "spawn": "teleportuje cię do punktu startowego", + "noclip": "latanie i brak obrażeń", + "timer": "start i stop stopera", + "solo": "widzisz tylko własne podglądy", + "clear": "czyści wczytaną ustawkę, znaczniki i duchy", + "help": "wypisuje wszystkie komendy w grze" + } }, "collections": { "tab": "Kolekcje", diff --git a/i18n/locales/pt_BR.json b/i18n/locales/pt_BR.json index 70a0cf574..90b14116a 100644 --- a/i18n/locales/pt_BR.json +++ b/i18n/locales/pt_BR.json @@ -4106,7 +4106,36 @@ "switch_failed": "Não foi possível trocar o mapa", "ask_host": "Peça para {host} trocar.", "ask_the_host": "Peça para o dono trocar.", - "nav_switching": "Trocando" + "nav_switching": "Trocando", + "commands": { + "title": "Comandos no jogo", + "hint": "Digite no chat assim que estiver no servidor. .help mostra a mesma lista dentro do jogo.", + "bind_hint": "São comandos de chat, então dá para ligar qualquer um a uma tecla:", + "group_lineups": "Lineups", + "group_drills": "Drills e executes", + "group_tools": "Ferramentas do servidor", + "save": "salva o arremesso que você acabou de fazer", + "load": "te coloca em um lineup correspondente", + "next": "percorrer a última busca", + "jump": "fique onde o lineup carregado cai", + "rethrow": "volta para o lineup carregado", + "last": "volta para um arremesso seu", + "list": "gerencie sua biblioteca de dentro do jogo", + "drill": "treina seu repertório, três arremessos cada, e pontua", + "skip": "pula o lineup que você está treinando", + "cancel": "para o drill em que você está", + "playbook": "roda o execute carregado nos tempos dele", + "playbook_stop": "para ele", + "bloom": "desenha onde a smoke carregada cai", + "solve": "acha um arremesso para o ponto que você está olhando", + "pos": "salvar uma posição e voltar para ela", + "spawn": "te teleporta para um ponto de spawn", + "noclip": "voar, e parar de tomar dano", + "timer": "inicia e para um cronômetro", + "solo": "ver só as suas prévias", + "clear": "limpa o lineup carregado, os marcadores e os ghosts", + "help": "mostra todos os comandos no jogo" + } }, "collections": { "tab": "Coleções", diff --git a/i18n/locales/ru_RU.json b/i18n/locales/ru_RU.json index e00ead348..529211bc7 100644 --- a/i18n/locales/ru_RU.json +++ b/i18n/locales/ru_RU.json @@ -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": "Подборки", diff --git a/i18n/locales/sv_SE.json b/i18n/locales/sv_SE.json index b35bd6bcf..ecf8edc76 100644 --- a/i18n/locales/sv_SE.json +++ b/i18n/locales/sv_SE.json @@ -4106,7 +4106,36 @@ "switch_failed": "Kunde inte byta karta", "ask_host": "Be {host} byta den.", "ask_the_host": "Be värden byta den.", - "nav_switching": "Byter" + "nav_switching": "Byter", + "commands": { + "title": "Kommandon i spelet", + "hint": "Skriv dem i chatten när du är inne på servern. .help visar samma lista i spelet.", + "bind_hint": "Det är chattkommandon, så alla går att binda till en tangent:", + "group_lineups": "Uppställningar", + "group_drills": "Drills och executes", + "group_tools": "Serververktyg", + "save": "sparar kastet du precis gjorde", + "load": "ställer dig på en uppställning som matchar", + "next": "bläddra i den senaste sökningen", + "jump": "ställ dig där den inlästa uppställningen landar", + "rethrow": "tillbaka till den inlästa uppställningen", + "last": "tillbaka till ett kast du gjort", + "list": "sköt ditt bibliotek inifrån spelet", + "drill": "kör igenom din bok, tre kast var, och sätter poäng", + "skip": "hoppa över uppställningen du tränar", + "cancel": "avbryt drillen du är i", + "playbook": "kör den inlästa executen på sina tider", + "playbook_stop": "stoppar den", + "bloom": "ritar ut var den inlästa smoken landar", + "solve": "hittar ett kast till platsen du tittar på", + "pos": "spara en position och gå tillbaka till den", + "spawn": "teleporterar dig till en spawnpunkt", + "noclip": "flyg, och ta ingen skada", + "timer": "starta och stoppa ett tidtagarur", + "solo": "se bara dina egna förhandsvisningar", + "clear": "rensar den inlästa uppställningen, markörer och ghosts", + "help": "visar alla kommandon i spelet" + } }, "collections": { "tab": "Samlingar", diff --git a/i18n/locales/tr_TR.json b/i18n/locales/tr_TR.json index 98f8afff3..fdf508907 100644 --- a/i18n/locales/tr_TR.json +++ b/i18n/locales/tr_TR.json @@ -4106,7 +4106,36 @@ "switch_failed": "Harita değiştirilemedi", "ask_host": "Değiştirmesi için {host} kişisine söyle.", "ask_the_host": "Değiştirmesi için sunucu sahibine söyle.", - "nav_switching": "Değişiyor" + "nav_switching": "Değişiyor", + "commands": { + "title": "Oyun içi komutlar", + "hint": "Sunucuya girdikten sonra sohbete yaz. .help aynı listeyi oyun içinde basar.", + "bind_hint": "Bunlar sohbet komutu, yani hepsi bir tuşa bağlanabilir:", + "group_lineups": "Dizilimler", + "group_drills": "Drill ve execute", + "group_tools": "Sunucu araçları", + "save": "az önce yaptığın atışı kaydeder", + "load": "eşleşen bir dizilimin üzerine ışınlar", + "next": "son aramada gezin", + "jump": "yüklü dizilimin düştüğü yere geç", + "rethrow": "yüklü dizilime geri dön", + "last": "yaptığın bir atışa geri dön", + "list": "kütüphaneni oyun içinden yönet", + "drill": "defterini çalıştırır, üçer atış, ve puanlar", + "skip": "çalıştığın dizilimi atla", + "cancel": "içinde olduğun drill'i durdur", + "playbook": "yüklü execute'u kendi zamanlamasıyla oynatır", + "playbook_stop": "onu durdurur", + "bloom": "yüklü sisin nereye düştüğünü çizer", + "solve": "baktığın noktaya bir atış bulur", + "pos": "bir konum kaydet ve oraya dön", + "spawn": "seni bir spawn noktasına ışınlar", + "noclip": "uç, ve hasar alma", + "timer": "kronometreyi başlat ve durdur", + "solo": "sadece kendi önizlemelerini gör", + "clear": "yüklü dizilimi, işaretleri ve ghost'ları temizler", + "help": "bütün komutları oyun içinde basar" + } }, "collections": { "tab": "Koleksiyonlar", diff --git a/i18n/locales/uk_UA.json b/i18n/locales/uk_UA.json index a092d67f1..7ce49a792 100644 --- a/i18n/locales/uk_UA.json +++ b/i18n/locales/uk_UA.json @@ -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": "Добірки", diff --git a/i18n/locales/zh_Hans.json b/i18n/locales/zh_Hans.json index e17961db2..fb9a03355 100644 --- a/i18n/locales/zh_Hans.json +++ b/i18n/locales/zh_Hans.json @@ -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": "合集", diff --git a/i18n/locales/zh_Hant.json b/i18n/locales/zh_Hant.json index 2b7f00159..096d51d04 100644 --- a/i18n/locales/zh_Hant.json +++ b/i18n/locales/zh_Hant.json @@ -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": "合輯", diff --git a/pages/news/[slug].vue b/pages/news/[slug].vue index 5468a2495..8fbce9162 100644 --- a/pages/news/[slug].vue +++ b/pages/news/[slug].vue @@ -11,7 +11,8 @@ import { resolveAvatarUrl } from "~/utilities/avatarUrl"; import getGraphqlClient from "~/graphql/getGraphqlClient"; import { order_by } from "~/generated/zeus"; import { generateQuery } from "~/graphql/graphqlGen"; -import { newsArticleFields } from "~/graphql/newsGraphql"; +import { newsArticleFields, newsPostViewFields } from "~/graphql/newsGraphql"; +import NewsViewCount from "~/components/news/NewsViewCount.vue"; interface NewsAuthor { steam_id: string; @@ -40,6 +41,7 @@ const route = useRoute(); const slug = computed(() => String(route.params.slug)); const article = ref(null); +const viewCount = ref(null); const loading = ref(true); const newsEnabled = computed(() => useApplicationSettingsStore().newsEnabled); @@ -84,7 +86,7 @@ const wasUpdated = computed(() => { return new Date(a.updated_at).getTime() - new Date(a.published_at).getTime() > 60_000; }); -const trackView = (articleSlug: string | null) => { +const trackView = async (articleSlug: string | null) => { if (!import.meta.client || !articleSlug) { return; } @@ -93,10 +95,34 @@ const trackView = (articleSlug: string | null) => { return; } sessionStorage.setItem(key, "1"); - fetch(`https://${apiDomain.value}/news/${encodeURIComponent(articleSlug)}/view`, { - method: "POST", - credentials: "include", - }).catch(() => {}); + await fetch( + `https://${apiDomain.value}/news/${encodeURIComponent(articleSlug)}/view`, + { + method: "POST", + credentials: "include", + }, + ).catch(() => {}); +}; + +// The public article query can't select view_count — only authors get it, via +// the admin action, once we know which post we're on. +const fetchViewCount = async (id: string) => { + if (!canPostNews.value) { + return; + } + try { + const { data } = await getGraphqlClient().query({ + query: generateQuery({ + newsPostAdmin: [{ id }, newsPostViewFields], + }), + fetchPolicy: "network-only", + }); + viewCount.value = + (data as { newsPostAdmin: { view_count: string } | null }).newsPostAdmin + ?.view_count ?? null; + } catch { + viewCount.value = null; + } }; const fetchArticle = async () => { @@ -122,7 +148,8 @@ const fetchArticle = async () => { } article.value = found; useNotificationStore().markNewsRead(found.published_at); - trackView(found.slug); + // Count this visit first so authors don't read a number that's one behind. + trackView(found.slug).finally(() => fetchViewCount(found.id)); } catch { return navigateTo("/news"); } finally { @@ -149,12 +176,15 @@ onMounted(() => { {{ $t("pages.news.back") }} - - - +
+ + + + +
diff --git a/pages/news/index.vue b/pages/news/index.vue index 109dfb057..f601e5a9d 100644 --- a/pages/news/index.vue +++ b/pages/news/index.vue @@ -10,7 +10,8 @@ import PageTransition from "~/components/ui/transitions/PageTransition.vue"; import { order_by } from "~/generated/zeus"; import getGraphqlClient from "~/graphql/getGraphqlClient"; import { generateQuery } from "~/graphql/graphqlGen"; -import { newsArticleListFields } from "~/graphql/newsGraphql"; +import { newsArticleListFields, newsPostViewFields } from "~/graphql/newsGraphql"; +import NewsViewCount from "~/components/news/NewsViewCount.vue"; interface NewsArticle { id: string; @@ -24,6 +25,7 @@ interface NewsArticle { const PER_PAGE = 12; const articles = ref([]); +const viewCounts = ref>({}); const total = ref(0); const loading = ref(true); const page = ref(1); @@ -68,6 +70,30 @@ const fetchCount = async () => { ).news_articles_aggregate.aggregate.count; }; +// view_count is admin-only, so it comes from the admin action rather than the +// public list query — one fetch covers every page of the list. +const fetchViewCounts = async () => { + if (!canPostNews.value) { + return; + } + try { + const { data } = await getGraphqlClient().query({ + query: generateQuery({ + newsPostsAdmin: newsPostViewFields, + }), + fetchPolicy: "network-only", + }); + const posts = + (data as { newsPostsAdmin: Array<{ id: string; view_count: string }> }) + .newsPostsAdmin ?? []; + viewCounts.value = Object.fromEntries( + posts.map((post) => [post.id, post.view_count]), + ); + } catch { + viewCounts.value = {}; + } +}; + const fetchArticles = async () => { loading.value = true; try { @@ -91,6 +117,7 @@ const fetchArticles = async () => { }; watch(page, fetchArticles); +watch(canPostNews, () => fetchViewCounts()); onMounted(() => { if (!newsEnabled.value) { @@ -99,6 +126,7 @@ onMounted(() => { } fetchCount(); fetchArticles(); + fetchViewCounts(); }); @@ -157,6 +185,10 @@ onMounted(() => {
{{ formatDate(article.published_at) }} +

{{ article.title }} diff --git a/pages/news/manage/[id].vue b/pages/news/manage/[id].vue index de950dea2..ac1d181cf 100644 --- a/pages/news/manage/[id].vue +++ b/pages/news/manage/[id].vue @@ -20,6 +20,7 @@ import { toast } from "@/components/ui/toast"; import getGraphqlClient from "~/graphql/getGraphqlClient"; import { generateQuery, generateMutation } from "~/graphql/graphqlGen"; import { newsPostAdminFields } from "~/graphql/newsGraphql"; +import NewsViewCount from "~/components/news/NewsViewCount.vue"; interface NewsPost { id: string; @@ -226,14 +227,7 @@ onBeforeUnmount(() => { : $t("pages.news.form.preview_full") }} - - - {{ viewCount.toLocaleString() }} - + { {{ formatDate(post.published_at || post.updated_at) }} - - - {{ Number(post.view_count || 0).toLocaleString() }} - +