DEV: add typescript support for core, themes and plugins (#41478)

We've been using basic type-checking via JSDoc for some time. This
commit allows us to author proper `.ts`/`.gts` files, and use the full
typescript syntax. Initially, only d-button and a single chat file are
migrated, as proof of functionality.

In future, we may migrate more files, and consider making our tsconfig
more strict.
This commit is contained in:
David Taylor
2026-07-08 12:57:18 +01:00
committed by GitHub
parent 209fb1ac60
commit de793bc6ed
31 changed files with 477 additions and 165 deletions
+1
View File
@@ -26,3 +26,4 @@ tmp/
**/*.json
!**/tsconfig.json
**/*.md
**/*.d.ts
+3 -3
View File
@@ -12,8 +12,8 @@ PROJECT_ROOT = File.expand_path("..", __dir__)
# live outside the core repo (e.g. plugins/*) where lefthook is not available.
class ExternalLinter
RUBY_EXTENSIONS = %w[rb rake thor].freeze
PRETTIER_EXTENSIONS = %w[css scss js gjs cjs mjs].freeze
ESLINT_EXTENSIONS = %w[js gjs].freeze
PRETTIER_EXTENSIONS = %w[css scss js gjs cjs mjs ts gts mts cts].freeze
ESLINT_EXTENSIONS = %w[js gjs ts gts mts cts].freeze
STYLELINT_EXTENSIONS = %w[scss].freeze
JS_EXTENSIONS = (PRETTIER_EXTENSIONS + ESLINT_EXTENSIONS + STYLELINT_EXTENSIONS).uniq.freeze
@@ -239,7 +239,7 @@ class LefthookLinter
end
# Check if file extension is lintable
lintable_extensions = %w[rb rake js gjs hbs scss css yml yaml thor]
lintable_extensions = %w[rb rake js gjs ts gts mts cts hbs scss css yml yaml thor]
lintable_extensions.include?(ext)
end
@@ -8,6 +8,10 @@ Discourse ships type information for its JavaScript code. This can provide inlin
Much of this will be automatically consumed by IDEs with TypeScript/JavaScript support. But for functionality in `.gjs` files, you'll need some specific configuration and/or IDE plugins.
## Writing TypeScript
Core, themes and plugins can be authored directly in TypeScript. Use a `.ts` extension for plain modules, or `.gts` for Glimmer components with a `<template>` tag. Type syntax is stripped at build time, so no separate compilation step is required. Linting (`@discourse/lint-configs`) and type-checking (`pnpm lint:types`) both understand these files.
## Usage
- **CLI**: Run `pnpm lint:types`
@@ -43,25 +47,14 @@ Then run `pnpm install` and start the type watcher with `pnpm types:watch`.
## Enable checking for a file
To enable type-checking for a specific file, add `/** @ts-check */` at the top. For some examples, search Discourse core for `@ts-check`.
`.ts` and `.gts` files are always type-checked. For `.js` / `.gjs` files, type-checking is opt-in: add `/** @ts-check */` at the top. For some examples, search Discourse core for `@ts-check`.
## Limitations
Discourse's build pipelines do not currently support `.ts` files. Types are built & checked using `.js` / `.gjs` files only.
We do not provide any guarantees about the accuracy of the types - they're provided on a best-effort basis. PRs to improve the JSDoc-based documentation in core are welcome.
We do not provide any guarantees about the accuracy of the types - they're provided on a best-effort basis. PRs to improve the type documentation in core are welcome.
## Known Issues
- When importing one gjs file from another, CLI checks will report "Cannot find module". This happens due to a bug in `glint`, which requires the `.gjs` file extension to be added to the type import. The problem can be worked-around by adding a type import alongside the regular extensionless import.
```js
/** @type {import("./slot.gjs").default} */
import Slot from "./slot";
```
Upstream issue [here](https://github.com/typed-ember/glint/issues/1021).
- Autocomplete inside `<template>` tags requires complete syntax. For example, if you start typing:
```
+4 -2
View File
@@ -17,12 +17,14 @@ export default [
"**/node_modules/",
"spec/",
"frontend/discourse/dist/",
"frontend/discourse-types/dts-generator.js",
"**/*.d.ts",
"frontend/discourse-types/external-types",
"frontend/discourse-types/dts-generator.{js,ts}",
"tmp/",
],
},
{
files: ["themes/**/*.{js,gjs}"],
files: ["themes/**/*.{js,gjs,ts,gts}"],
languageOptions: {
globals: {
settings: "readonly",
@@ -1,3 +1,4 @@
import BabelTransformTypescript from "@babel/plugin-transform-typescript";
import BabelPresetEnv from "@babel/preset-env";
import { rollup } from "@rollup/browser";
import { babel, getBabelOutputPlugin } from "@rollup/plugin-babel";
@@ -70,11 +71,17 @@ async function performRollup(modules, opts) {
compact: false,
}),
babel({
extensions: [".js", ".gjs", ".hbs"],
extensions: [".js", ".gjs", ".ts", ".gts", ".hbs"],
babelHelpers: "bundled",
compact: false,
// Support `import ... with { ... }` for cross-plugin imports
parserOpts: { plugins: ["importAttributes"] },
overrides: [
{
test: /\.(gts|ts|mts|cts)$/,
plugins: [[BabelTransformTypescript, { allowDeclareFields: true }]],
},
],
plugins: [
[DecoratorTransforms, { runEarly: true }],
opts.themeId ? AddThemeGlobals : null,
+1
View File
@@ -11,6 +11,7 @@
},
"dependencies": {
"@babel/core": "^7.29.7",
"@babel/plugin-transform-typescript": "^7.29.7",
"@babel/preset-env": "^7.29.2",
"@babel/standalone": "^7.29.7",
"@csstools/postcss-light-dark-function": "^3.0.1",
@@ -7,7 +7,7 @@ export default function discourseFileSearch() {
return null;
}
for (const ext of ["", ".js", ".gjs", ".hbs"]) {
for (const ext of ["", ".js", ".gjs", ".ts", ".gts", ".hbs"]) {
const resolved = await this.resolve(`${source}${ext}`, context, {
attributes: options.attributes,
});
@@ -10,7 +10,7 @@ export default function discourseGjs() {
// Enforce running the gjs transform before any others like babel that expect valid JS
order: "pre",
handler(input, id) {
if (!id.endsWith(".gjs")) {
if (!id.endsWith(".gjs") && !id.endsWith(".gts")) {
return null;
}
let { code, map } = preprocessor.process(input, {
@@ -1,4 +1,11 @@
const SUPPORTED_FILE_EXTENSIONS = [".js", ".js.es6", ".hbs", ".gjs"];
const SUPPORTED_FILE_EXTENSIONS = [
".js",
".js.es6",
".hbs",
".gjs",
".ts",
".gts",
];
const IS_CONNECTOR_REGEX = /(^|\/)connectors\//;
@@ -13,6 +20,11 @@ export default {
let i = 1;
for (const moduleFilename of moduleFilenames) {
// Type-only declaration files have no runtime module to export.
if (moduleFilename.endsWith(".d.ts")) {
continue;
}
if (
!SUPPORTED_FILE_EXTENSIONS.some((ext) => moduleFilename.endsWith(ext))
) {
+1 -1
View File
@@ -30,7 +30,7 @@ export class I18n {
pluralizationRules = Cardinals;
translate = (scope, options) => {
translate = (scope, options = {}) => {
return this.verbose
? this._verboseTranslate(scope, options)
: this._translate(scope, options);
+2 -2
View File
@@ -1,8 +1,8 @@
const rawModules = import.meta.glob("./**/*.{gjs,js}", { eager: true });
const rawModules = import.meta.glob("./**/*.{gjs,js,ts,gts}", { eager: true });
const adminCompatModules = {};
for (let [key, mod] of Object.entries(rawModules)) {
key = key.replace(/\.(gjs|js)$/, "");
key = key.replace(/\.(gjs|js|ts|gts)$/, "");
adminCompatModules[key] = mod;
}
@@ -1,4 +1,3 @@
// @ts-check
import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action, computed } from "@ember/object";
@@ -12,66 +11,74 @@ import dElement from "discourse/ui-kit/helpers/d-element";
import dIcon from "discourse/ui-kit/helpers/d-icon";
import { i18n } from "discourse-i18n";
/**
* @typedef DButtonSignature
*
* @property {object} Args
*
* // Text
* @property {string} [Args.title]
* @property {string} [Args.translatedTitle]
* @property {string} [Args.label]
* @property {string} [Args.translatedLabel]
*
* // Actions / events
* @property {function|object} [Args.action]
* @property {any} [Args.actionParam]
* @property {boolean} [Args.forwardEvent]
* @property {function} [Args.onKeyDown]
*
* // Navigation
* @property {string} [Args.href]
* @property {string} [Args.route]
* @property {object|object[]} [Args.routeModels]
*
* // State
* @property {boolean} [Args.isLoading]
* @property {boolean} [Args.disabled]
* @property {boolean} [Args.preventFocus]
*
* // Display mode
* @property {"link"} [Args.display]
*
* // Display / icon
* @property {string} [Args.icon]
* @property {boolean} [Args.ellipsis]
* @property {string} [Args.suffixIcon]
*
* // Accessibility
* @property {string} [Args.ariaLabel]
* @property {string} [Args.translatedAriaLabel]
* @property {boolean} [Args.ariaExpanded]
* @property {boolean} [Args.ariaPressed]
* @property {string} [Args.ariaControls]
* @property {boolean} [Args.ariaHidden]
*
* // HTML attributes
* @property {string} [Args.type]
* @property {string} [Args.id]
* @property {string} [Args.form]
* @property {string} [Args.tabindex]
* @property {string} [Args.class]
*
* // Root element type (enables ...attributes type checking)
* @property {HTMLButtonElement} Element
*
* // Optional yield
* @property {object} Blocks
* @property {[]} Blocks.default Contents of the button
*/
type DButtonActionCallback = (...args: unknown[]) => void;
/** @extends {Component<DButtonSignature>} */
export default class DButton extends Component {
interface DButtonActionObject {
value: DButtonActionCallback;
}
type DButtonAction = DButtonActionCallback | DButtonActionObject;
type RouteModel = string | number | object;
interface DButtonSignature {
Args: {
// Text
title?: string;
translatedTitle?: string;
label?: string;
translatedLabel?: string;
// Actions / events
action?: DButtonAction;
actionParam?: unknown;
forwardEvent?: boolean;
onKeyDown?: (event: KeyboardEvent) => void;
// Navigation
href?: string;
route?: string;
routeModels?: RouteModel | RouteModel[];
// State
isLoading?: boolean;
disabled?: boolean;
preventFocus?: boolean;
// Display mode
display?: "link";
// Display / icon
icon?: string;
ellipsis?: boolean;
suffixIcon?: string;
// Accessibility
ariaLabel?: string;
translatedAriaLabel?: string;
ariaExpanded?: boolean;
ariaPressed?: boolean;
ariaControls?: string;
ariaHidden?: boolean;
// HTML attributes
type?: string;
id?: string;
form?: string;
tabindex?: string;
class?: string;
};
// Root element type (enables ...attributes type checking)
Element: HTMLButtonElement;
// Optional yield
Blocks: {
default: [];
};
}
export default class DButton extends Component<DButtonSignature> {
@service router;
@service capabilities;
@@ -146,7 +153,7 @@ export default class DButton extends Component {
}
@action
keyDown(e) {
keyDown(e: KeyboardEvent) {
if (this.args.onKeyDown) {
e.stopPropagation();
this.args.onKeyDown(e);
@@ -156,18 +163,18 @@ export default class DButton extends Component {
}
@action
click(event) {
click(event: MouseEvent) {
return this._triggerAction(event);
}
@action
mouseDown(event) {
mouseDown(event: MouseEvent) {
if (this.args.preventFocus) {
event.preventDefault();
}
}
_triggerAction(event) {
_triggerAction(event: Event) {
const { action: actionVal, route, routeModels } = this.args;
const isIOS = this.capabilities?.isIOS;
@@ -179,9 +186,11 @@ export default class DButton extends Component {
if (isIOS) {
// Don't optimise INP in iOS
// it results in focus events not being triggered
forwardEvent
? actionVal.value(actionParam, event)
: actionVal.value(actionParam);
if (forwardEvent) {
actionVal.value(actionParam, event);
} else {
actionVal.value(actionParam);
}
} else {
// Using `next()` to optimise INP
next(() =>
@@ -194,9 +203,11 @@ export default class DButton extends Component {
if (isIOS) {
// Don't optimise INP in iOS
// it results in focus events not being triggered
forwardEvent
? actionVal(actionParam, event)
: actionVal(actionParam);
if (forwardEvent) {
actionVal(actionParam, event);
} else {
actionVal(actionParam);
}
} else {
// Using `next()` to optimise INP
next(() =>
+12
View File
@@ -70,6 +70,18 @@ module.exports = {
...macros.babelMacros,
],
overrides: [
{
test: /\.(gts|ts|mts|cts)$/,
plugins: [
[
require.resolve("@babel/plugin-transform-typescript"),
{ allowDeclareFields: true },
],
],
},
],
generatorOpts: {
compact: false,
},
@@ -1,8 +1,8 @@
const rawModules = import.meta.glob("./**/*.{gjs,js}", { eager: true });
const rawModules = import.meta.glob("./**/*.{gjs,js,ts,gts}", { eager: true });
const compatModules = {};
for (let [key, mod] of Object.entries(rawModules)) {
key = key.replace(/\.(gjs|js)$/, "");
key = key.replace(/\.(gjs|js|ts|gts)$/, "");
compatModules[key] = mod;
}
@@ -1,8 +1,8 @@
const rawModules = import.meta.glob("./**/*.{gjs,js}", { eager: true });
const rawModules = import.meta.glob("./**/*.{gjs,js,ts,gts}", { eager: true });
const compatModules = {};
for (let [key, mod] of Object.entries(rawModules)) {
key = key.replace(/\.(gjs|js)$/, "");
key = key.replace(/\.(gjs|js|ts|gts)$/, "");
compatModules[key] = mod;
}
@@ -1,4 +1,4 @@
const TEST_FILE_RE = /tests\/(?!helpers\/).*-test\.(?:gjs|js)$/;
const TEST_FILE_RE = /tests\/(?!helpers\/).*-test\.(?:gjs|js|ts|gts)$/;
export default function wrapTestModulesPlugin() {
return {
+1
View File
@@ -105,6 +105,7 @@
"devDependencies": {
"@babel/core": "^7.29.7",
"@babel/plugin-transform-runtime": "^7.29.7",
"@babel/plugin-transform-typescript": "^7.29.7",
"@babel/standalone": "^7.29.7",
"@colors/colors": "^1.6.0",
"@discourse/itsatrap": "^2.0.10",
@@ -1,8 +1,8 @@
const rawModules = import.meta.glob("./**/*.{gjs,js}", { eager: true });
const rawModules = import.meta.glob("./**/*.{gjs,js,ts,gts}", { eager: true });
const compatModules = {};
for (let [key, mod] of Object.entries(rawModules)) {
key = key.replace(/\.(gjs|js)$/, "");
key = key.replace(/\.(gjs|js|ts|gts)$/, "");
compatModules[key] = mod;
}
@@ -88,9 +88,12 @@ export function pretenderHelpers() {
}
export function applyDefaultHandlers() {
const pretenderModules = import.meta.glob("./**/*-pretender.{js,gjs}", {
const pretenderModules = import.meta.glob(
"./**/*-pretender.{js,gjs,ts,gts}",
{
eager: true,
});
}
);
Object.keys(requirejs.entries).forEach((entry) => {
if (/^discourse\/plugins\/.*helpers\/[a-z-]+\-pretender$/.test(entry)) {
+6 -3
View File
@@ -9,13 +9,16 @@ loaderShim("ember-qunit", () => importSync("ember-qunit"));
loaderShim("@faker-js/faker", () => importSync("@faker-js/faker"));
loaderShim("@ember/test-helpers", () => importSync("@ember/test-helpers"));
const rawModules = import.meta.glob("./{fixtures,helpers}/**/*.{gjs,js}", {
const rawModules = import.meta.glob(
"./{fixtures,helpers}/**/*.{gjs,js,ts,gts}",
{
eager: true,
});
}
);
const compatModules = {};
for (let [key, mod] of Object.entries(rawModules)) {
key = key.replace(/\.(gjs|js)$/, "");
key = key.replace(/\.(gjs|js|ts|gts)$/, "");
compatModules[key] = mod;
}
@@ -60,7 +60,7 @@ export async function startTests() {
let availableModules;
if (testingCore) {
const rawModules = import.meta.glob("./**/*-test.{gjs,js}", {
const rawModules = import.meta.glob("./**/*-test.{gjs,js,ts,gts}", {
eager: true,
});
availableModules = {};
@@ -1,8 +1,8 @@
const rawModules = import.meta.glob("./**/*.{gjs,js}", { eager: true });
const rawModules = import.meta.glob("./**/*.{gjs,js,ts,gts}", { eager: true });
const compatModules = {};
for (let [key, mod] of Object.entries(rawModules)) {
key = key.replace(/\.(gjs|js)$/, "");
key = key.replace(/\.(gjs|js|ts|gts)$/, "");
compatModules[key] = mod;
}
+6 -6
View File
@@ -31,15 +31,15 @@ pre-commit:
prettier:
glob: &prettier_glob
- "app/assets/stylesheets/**/*.{css,scss}"
- "frontend/**/*.{js,gjs,scss,css,cjs,mjs}"
- "plugins/**/*.{js,gjs,scss,css,cjs,mjs}"
- "themes/**/*.{js,gjs,scss,css,cjs,mjs}"
- "frontend/**/*.{js,gjs,ts,gts,mts,cts,scss,css,cjs,mjs}"
- "plugins/**/*.{js,gjs,ts,gts,mts,cts,scss,css,cjs,mjs}"
- "themes/**/*.{js,gjs,ts,gts,mts,cts,scss,css,cjs,mjs}"
run: pnpm pprettier --list-different {staged_files}
eslint:
glob: &eslint_glob
- "frontend/**/*.{js,gjs}"
- "plugins/**/*.{js,gjs}"
- "themes/**/*.{js,gjs}"
- "frontend/**/*.{js,gjs,ts,gts,mts,cts}"
- "plugins/**/*.{js,gjs,ts,gts,mts,cts}"
- "themes/**/*.{js,gjs,ts,gts,mts,cts}"
run: pnpm eslint --max-warnings 0 {staged_files}
yaml-syntax:
glob: &yaml_glob "**/*.{yaml,yml}"
+1 -1
View File
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class AssetProcessor
BASE_COMPILER_VERSION = 112
BASE_COMPILER_VERSION = 113
PROCESSOR_DIR = "tmp/asset-processor"
LOCK_FILE = "#{PROCESSOR_DIR}/build.lock"
+3 -1
View File
@@ -794,10 +794,12 @@ class Plugin::Instance
Any hbs files under `assets/javascripts` will be automatically compiled and included."
ERROR
raise <<~ERROR if file.start_with?("javascripts/") && file.end_with?(".js", ".js.es6")
if file.start_with?("javascripts/") && file.end_with?(".js", ".js.es6", ".ts", ".gts")
raise <<~ERROR
[#{name}] Javascript files under `assets/javascripts` are automatically included in JS bundles.
Manual register_asset calls should be removed. (attempted to add #{file})
ERROR
end
if opts && opts == :vendored_core_pretty_text
full_path = DiscoursePluginRegistry.core_asset_for_name(file)
+5 -4
View File
@@ -107,7 +107,7 @@ module Plugin
)
tree[normalized_file_path] = content
if name == "test" && file.match(%r{/(acceptance|integration|unit)/})
if file.match?(/-test\.g?js$/)
if file.match?(/-test\.g?[jt]s$/)
entrypoints_config[name][:modules] << normalized_file_path
end
else
@@ -189,7 +189,7 @@ module Plugin
listener =
Listen.to(
*Discourse.plugins.map(&:directory),
{ ignore: [%r{/node_modules/}], only: /\.(gjs|js|hbs)\z/ },
{ ignore: [%r{/node_modules/}], only: /\.(gjs|js|hbs|ts|gts)\z/ },
) do |modified, added, removed|
changed_files = modified + added + removed
changed_plugins = Set.new
@@ -245,8 +245,9 @@ module Plugin
end
private_class_method def self.has_source_files_in_dir(plugin_directory_name, dir)
Dir.glob("plugins/#{plugin_directory_name}/#{dir}/**/*.{js,hbs,gjs,es6}") { break true } ||
false
Dir.glob("plugins/#{plugin_directory_name}/#{dir}/**/*.{js,hbs,gjs,es6,ts,gts}") do
break true
end || false
end
end
end
+2 -2
View File
@@ -501,7 +501,7 @@ task "themes:qunit_all_official" => :environment do |task, args|
ThemeMetadata::OFFICIAL_THEMES.each do |theme_name|
path = Rails.root.join("tmp/themes/#{theme_name}").to_s
if Dir.glob("#{File.join(path, "test")}/**/*.{js,gjs}").any?
if Dir.glob("#{File.join(path, "test")}/**/*.{js,gjs,ts,gts}").any?
theme = RemoteTheme.import_theme_from_directory(path)
official_theme_ids_with_qunit_tests << theme.id
else
@@ -514,7 +514,7 @@ task "themes:qunit_all_official" => :environment do |task, args|
Theme::CORE_THEMES.each do |(theme_name, theme_id)|
path = Rails.root.join("themes/#{theme_name}").to_s
if Dir.glob("#{File.join(path, "test")}/**/*.{js,gjs}").any?
if Dir.glob("#{File.join(path, "test")}/**/*.{js,gjs,ts,gts}").any?
core_theme_ids_with_qunit_tests << theme_id
else
puts "Skipping #{theme_name} as no QUnit tests have been detected"
+3 -3
View File
@@ -4,7 +4,7 @@
"author": "Discourse",
"license": "GPL-2.0-only",
"devDependencies": {
"@discourse/lint-configs": "^3.1.2",
"@discourse/lint-configs": "^3.2.0",
"@discourse/moment-timezone-names-translations": "^1.0.0",
"@fortawesome/fontawesome-free": "7.2.0",
"@glint/ember-tsc": "^1.8.11",
@@ -31,8 +31,8 @@
"lint:css:fix": "pnpm stylelint --fix 'app/assets/stylesheets/**/*.scss' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') 'themes/**/*.scss'",
"lint:js": "eslint ./frontend $(script/list_bundled_plugins) ./themes --cache --no-error-on-unmatched-pattern --max-warnings 0",
"lint:js:fix": "eslint --fix ./frontend $(script/list_bundled_plugins) ./themes --no-error-on-unmatched-pattern",
"lint:prettier": "pnpm pprettier --list-different 'app/assets/stylesheets/**/*.scss' 'frontend/**/*.{js,gjs,css}' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') $(script/list_bundled_plugins '/{assets,admin/assets,test}/javascripts/**/*.{js,gjs}') 'themes/**/*.{js,gjs,scss}'",
"lint:prettier:fix": "pnpm prettier -w --no-error-on-unmatched-pattern 'app/assets/stylesheets/**/*.scss' 'frontend/**/*.{js,gjs,css}' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') $(script/list_bundled_plugins '/{assets,admin/assets,test}/javascripts/**/*.{js,gjs}') 'themes/**/*.{js,gjs,scss}'",
"lint:prettier": "pnpm pprettier --list-different 'app/assets/stylesheets/**/*.scss' 'frontend/**/*.{js,gjs,ts,gts,mts,cts,css}' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') $(script/list_bundled_plugins '/{assets,admin/assets,test}/javascripts/**/*.{js,gjs,ts,gts,mts,cts}') 'themes/**/*.{js,gjs,ts,gts,scss}'",
"lint:prettier:fix": "pnpm prettier -w --no-error-on-unmatched-pattern 'app/assets/stylesheets/**/*.scss' 'frontend/**/*.{js,gjs,ts,gts,mts,cts,css}' $(script/list_bundled_plugins '/assets/stylesheets/**/*.scss') $(script/list_bundled_plugins '/{assets,admin/assets,test}/javascripts/**/*.{js,gjs,ts,gts,mts,cts}') 'themes/**/*.{js,gjs,ts,gts,scss}'",
"lint:types": "ember-tsc -b",
"types:generate": "pnpm --dir=frontend/discourse-i18n ember-tsc && pnpm --dir=frontend/discourse-types generate-external",
"types:watch": "pnpm types:generate && pnpm ember-tsc -b --watch --preserveWatchOutput",
@@ -1,6 +1,9 @@
const BOTTOM_VISIBILITY_SLACK = 10;
export function checkMessageBottomVisibility(list, message) {
export function checkMessageBottomVisibility(
list: Element,
message: Element
): boolean {
const distanceToTop = window.pageYOffset + list.getBoundingClientRect().top;
const bounding = message.getBoundingClientRect();
return (
@@ -9,7 +12,10 @@ export function checkMessageBottomVisibility(list, message) {
);
}
export function checkMessageTopVisibility(list, message) {
export function checkMessageTopVisibility(
list: Element,
message: Element
): boolean {
const distanceToTop = window.pageYOffset + list.getBoundingClientRect().top;
const bounding = message.getBoundingClientRect();
return bounding.top - distanceToTop >= -1;
+248 -34
View File
@@ -46,8 +46,8 @@ importers:
.:
devDependencies:
'@discourse/lint-configs':
specifier: ^3.1.2
version: 3.1.2(jiti@2.6.1)(postcss@8.5.15)(prettier@3.8.1)
specifier: ^3.2.0
version: 3.2.0(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(postcss@8.5.15)(prettier@3.8.1)(stylelint@17.5.0(typescript@5.9.3))
'@discourse/moment-timezone-names-translations':
specifier: ^1.0.0
version: 1.0.0
@@ -104,7 +104,7 @@ importers:
devDependencies:
'@discourse/lint-configs':
specifier: ^3.1.2
version: 3.1.2(jiti@2.6.1)(postcss@8.5.15)(prettier@3.8.1)
version: 3.2.0(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(postcss@8.5.15)(prettier@3.8.1)(stylelint@17.5.0(typescript@5.9.3))
prettier:
specifier: 3.8.1
version: 3.8.1
@@ -114,6 +114,9 @@ importers:
'@babel/core':
specifier: ^7.29.7
version: 7.29.7
'@babel/plugin-transform-typescript':
specifier: ^7.29.7
version: 7.29.7(@babel/core@7.29.7)
'@babel/preset-env':
specifier: ^7.29.2
version: 7.29.7(@babel/core@7.29.7)
@@ -433,6 +436,9 @@ importers:
'@babel/plugin-transform-runtime':
specifier: ^7.29.7
version: 7.29.7(@babel/core@7.29.7)
'@babel/plugin-transform-typescript':
specifier: ^7.29.7
version: 7.29.7(@babel/core@7.29.7)
'@babel/standalone':
specifier: ^7.29.7
version: 7.29.7
@@ -1779,10 +1785,12 @@ packages:
'@discourse/jxl@1.3.0':
resolution: {integrity: sha512-Veu75DEDC7uKsCCsD94UIjFwvHE73+AATBQbBWb83QyJV34wmyxl2Ji+fiiM234mlNn4cpo3DUl0QFrXgRTcfA==}
'@discourse/lint-configs@3.1.2':
resolution: {integrity: sha512-XDioMsCXakfYrsXzMdM1CfYKXUImt7hlfDUmfkprd8gMo4E9VRsbDxS3ITo3rZL9toNUrFsquYfMcxx5/QVzPg==}
'@discourse/lint-configs@3.2.0':
resolution: {integrity: sha512-mEZYPNlGyf+Jq6i6QKzi2mXw9ofgcEh1XARoQKQS1Y+SsclJgGHT33MUxn5QwWEaGpSYA67/0hdPVL+HEW29gw==}
peerDependencies:
eslint: 10.6.0
prettier: 3.8.1
stylelint: 17.5.0
'@discourse/moment-timezone-names-translations@1.0.0':
resolution: {integrity: sha512-4xr1QWQ0nzmFa2ZXQgWZA+dtE/BU2ePA+qkJWPFzNpq4ZnQi8MmMMAS2285t3rc2ySMBQqYaAArmcSUiufUgRA==}
@@ -2347,6 +2355,12 @@ packages:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@napi-rs/wasm-runtime@1.1.6':
resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -2842,6 +2856,9 @@ packages:
'@tybys/wasm-util@0.10.2':
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
'@tybys/wasm-util@0.10.3':
resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
'@types/babel__code-frame@7.27.0':
resolution: {integrity: sha512-Dwlo+LrxDx/0SpfmJ/BKveHf7QXWvLBLc+x03l5sbzykj3oB9nHygCpSECF1a+s+QIxbghe+KHqC90vGtxLRAA==}
@@ -2924,12 +2941,71 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@typescript-eslint/tsconfig-utils@8.59.3':
resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==}
'@typescript-eslint/eslint-plugin@8.62.1':
resolution: {integrity: sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.62.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/parser@8.62.1':
resolution: {integrity: sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/project-service@8.62.1':
resolution: {integrity: sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/scope-manager@8.62.1':
resolution: {integrity: sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.62.1':
resolution: {integrity: sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/tsconfig-utils@8.63.0':
resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/type-utils@8.62.1':
resolution: {integrity: sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/types@8.62.1':
resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.62.1':
resolution: {integrity: sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/utils@8.62.1':
resolution: {integrity: sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/visitor-keys@8.62.1':
resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@uppy/aws-s3@5.1.0':
resolution: {integrity: sha512-UBz+shrtDbnOf11AboDrkc9Fq2Cdf8HbFftE+gqfxig6fkv5rHpHhBCLkl8wCGAq+X/CxdqvvNhm/OM23Uzw2w==}
peerDependencies:
@@ -4253,8 +4329,8 @@ packages:
ember-curry-component@0.4.0:
resolution: {integrity: sha512-mXtlgCA0eW5B4ZdIP4qqMeatFCxKni7uuLbo06UK1a9y/itL5nKQosHwbyx5N7TfXXQt8oQl0oXAliIvw2sdmA==}
ember-eslint-parser@0.14.0:
resolution: {integrity: sha512-lFF19I8DhsAdNfUIV5ljC+2lHVUp1So1iQE1/ZbhMwHCq/c1s2G2XnUtal/DxLM/L+ZCwU0VTf2hwDqvqCxkZw==}
ember-eslint-parser@0.14.3:
resolution: {integrity: sha512-jU1HtVHqlbzqRDMwS4lxggjIY7u/wr+lij4bEvyC1CpNd5YqHK7anK3ubmVZLiB19VrFO6tH2/iYUQ+drpSYuA==}
engines: {node: '>=16.0.0'}
peerDependencies:
'@babel/eslint-parser': ^7.28.6 || ^8.0.0
@@ -4265,8 +4341,8 @@ packages:
'@typescript-eslint/parser':
optional: true
ember-estree@0.6.4:
resolution: {integrity: sha512-/0+JLFt200RB/gUfLfRALTFWby6fGS7Bu+NN985Y8gadEntX4KoiYHhOl2hkzvy+AmBi+PbHxBa9QVCV7K/PUg==}
ember-estree@0.6.10:
resolution: {integrity: sha512-5nItZGzvxHgoCdASQbn9qBzF4sdjSggUeZpXdihAanmYWm6XkObT/TlUvakYtoKret1/8gm0jNLwNPqZYwHtCw==}
ember-exam@10.1.0:
resolution: {integrity: sha512-6jUftmu2zpmLZ35PCsZDbdsAXTm2GjCGT3SjRX+BkUr2yKYpbD9B2R7hqCUnqOOTkdp2lzhBcGZ+KwwLLdH7eg==}
@@ -4460,8 +4536,8 @@ packages:
'@babel/eslint-parser':
optional: true
eslint-plugin-ember@13.4.0:
resolution: {integrity: sha512-D7fDHqTEFPMw8bnvMLGmLsLqenUj0/cCKrg7/WTB4dv+MaSS6JjR4TYycOE9n5jKbIgOlmXA2zMUDya7sVkl0Q==}
eslint-plugin-ember@13.4.1:
resolution: {integrity: sha512-KyPKk3SfYQ5ci9U/bvVIA0NFB1UFn6rZAHNNb62AOgcMUNIR1/+0zqzyhrLHPyodo8BmE+kKDj7subsp71NkqA==}
engines: {node: '>= 20.19'}
peerDependencies:
'@typescript-eslint/parser': '*'
@@ -7478,8 +7554,8 @@ packages:
styled_string@0.0.1:
resolution: {integrity: sha512-DU2KZiB6VbPkO2tGSqQ9n96ZstUPjW7X4sGO6V2m1myIQluX0p1Ol8BrA/l6/EesqhMqXOIXs3cJNOy1UuU2BA==}
stylelint-config-recommended-scss@17.0.0:
resolution: {integrity: sha512-VkVD9r7jfUT/dq3mA3/I1WXXk2U71rO5wvU2yIil9PW5o1g3UM7Xc82vHmuVJHV7Y8ok5K137fmW5u3HbhtTOA==}
stylelint-config-recommended-scss@17.0.1:
resolution: {integrity: sha512-x5DVehzJudcwF0od3sGpgkln2PLLranFE7twwbp7dqDINCyZvwzFkMc6TLhNOvazRiVBJYATQLouJY0xPGB8WA==}
engines: {node: '>=20'}
peerDependencies:
postcss: ^8.3.3
@@ -7510,8 +7586,8 @@ packages:
peerDependencies:
stylelint: ^17.0.0
stylelint-scss@7.0.0:
resolution: {integrity: sha512-H88kCC+6Vtzj76NsC8rv6x/LW8slBzIbyeSjsKVlS+4qaEJoDrcJR4L+8JdrR2ORdTscrBzYWiiT2jq6leYR1Q==}
stylelint-scss@7.2.0:
resolution: {integrity: sha512-6E79Bachv0Iz0gqRUZgdqdXCsiq26DWBWIBNHYtjTmAp3wJu6cp/I37VfW7BPntmh2puF3bY09XWl4HZGrLhzw==}
engines: {node: '>=20.19.0'}
peerDependencies:
stylelint: ^16.8.2 || ^17.0.0
@@ -7710,6 +7786,12 @@ packages:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
ts-api-utils@2.5.0:
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
engines: {node: '>=18.12'}
peerDependencies:
typescript: '>=4.8.4'
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -7762,6 +7844,13 @@ packages:
typescript-auto-import-cache@0.3.6:
resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==}
typescript-eslint@8.62.1:
resolution: {integrity: sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
typescript-memoize@1.1.1:
resolution: {integrity: sha512-GQ90TcKpIH4XxYTI2F98yEQYZgjNMOGPpOgdjIBhaLaWji5HPWlRnZ4AeA1hfBxtY7bCGDJsqDDHk/KaHOl5bA==}
@@ -9327,15 +9416,16 @@ snapshots:
dependencies:
wasm-feature-detect: 1.8.0
'@discourse/lint-configs@3.1.2(jiti@2.6.1)(postcss@8.5.15)(prettier@3.8.1)':
'@discourse/lint-configs@3.2.0(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(postcss@8.5.15)(prettier@3.8.1)(stylelint@17.5.0(typescript@5.9.3))':
dependencies:
'@babel/core': 8.0.1
'@babel/eslint-parser': 8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1))
'@babel/plugin-proposal-decorators': 8.0.2(@babel/core@8.0.1)
'@eslint/js': 10.0.1(eslint@10.6.0(jiti@2.6.1))
ember-eslint-parser: 0.14.3(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3)
eslint: 10.6.0(jiti@2.6.1)
eslint-plugin-decorator-position: 6.1.1(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(eslint@10.6.0(jiti@2.6.1))
eslint-plugin-ember: 13.4.0(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-ember: 13.4.1(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-qunit: 8.2.6(eslint@10.6.0(jiti@2.6.1))
eslint-plugin-simple-import-sort: 13.0.0(eslint@10.6.0(jiti@2.6.1))
eslint-plugin-sort-class-members: 1.22.1(eslint@10.6.0(jiti@2.6.1))
@@ -9345,11 +9435,11 @@ snapshots:
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-standard: 40.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-config-standard-scss: 17.0.0(postcss@8.5.15)(stylelint@17.5.0(typescript@5.9.3))
stylelint-scss: 7.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-scss: 7.2.0(stylelint@17.5.0(typescript@5.9.3))
typescript: 5.9.3
typescript-eslint: 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
transitivePeerDependencies:
- '@typescript-eslint/parser'
- jiti
- postcss
- supports-color
@@ -10194,6 +10284,13 @@ snapshots:
'@tybys/wasm-util': 0.10.2
optional: true
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
'@tybys/wasm-util': 0.10.3
optional: true
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -10390,7 +10487,7 @@ snapshots:
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
'@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
'@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
'@oxc-parser/binding-win32-arm64-msvc@0.130.0':
@@ -10624,6 +10721,11 @@ snapshots:
tslib: 2.8.1
optional: true
'@tybys/wasm-util@0.10.3':
dependencies:
tslib: 2.8.1
optional: true
'@types/babel__code-frame@7.27.0': {}
'@types/chai@5.2.3':
@@ -10701,10 +10803,101 @@ snapshots:
dependencies:
'@types/node': 25.9.1
'@typescript-eslint/tsconfig-utils@8.59.3(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.62.1
'@typescript-eslint/type-utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.62.1
eslint: 10.6.0(jiti@2.6.1)
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.62.1
'@typescript-eslint/types': 8.62.1
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.62.1
debug: 4.4.3
eslint: 10.6.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.62.1(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.63.0(typescript@5.9.3)
'@typescript-eslint/types': 8.62.1
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.62.1':
dependencies:
'@typescript-eslint/types': 8.62.1
'@typescript-eslint/visitor-keys': 8.62.1
'@typescript-eslint/tsconfig-utils@8.62.1(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/tsconfig-utils@8.63.0(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.62.1
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
debug: 4.4.3
eslint: 10.6.0(jiti@2.6.1)
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.62.1': {}
'@typescript-eslint/typescript-estree@8.62.1(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.62.1(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3)
'@typescript-eslint/types': 8.62.1
'@typescript-eslint/visitor-keys': 8.62.1
debug: 4.4.3
minimatch: 10.2.5
semver: 7.8.4
tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.6.1))
'@typescript-eslint/scope-manager': 8.62.1
'@typescript-eslint/types': 8.62.1
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
eslint: 10.6.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.62.1':
dependencies:
'@typescript-eslint/types': 8.62.1
eslint-visitor-keys: 5.0.1
'@uppy/aws-s3@5.1.0(@uppy/core@5.2.0)':
dependencies:
'@uppy/companion-client': 5.1.1(@uppy/core@5.2.0)
@@ -12410,22 +12603,23 @@ snapshots:
- '@babel/core'
- supports-color
ember-eslint-parser@0.14.0(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(typescript@5.9.3):
ember-eslint-parser@0.14.3(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3):
dependencies:
'@glimmer/syntax': 0.95.0
'@typescript-eslint/tsconfig-utils': 8.59.3(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.63.0(typescript@5.9.3)
content-tag: 4.2.0
ember-estree: 0.6.4
ember-estree: 0.6.10
eslint-scope: 9.1.2
html-tags: 5.1.0
mathml-tag-names: 4.0.0
svg-tags: 1.0.0
optionalDependencies:
'@babel/eslint-parser': 8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1))
'@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
transitivePeerDependencies:
- typescript
ember-estree@0.6.4:
ember-estree@0.6.10:
dependencies:
'@glimmer/env': 0.1.7
'@glimmer/syntax': 0.95.0
@@ -12790,14 +12984,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-ember@13.4.0(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3):
eslint-plugin-ember@13.4.1(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@ember-data/rfc395-data': 0.0.4
aria-query: 5.3.2
axobject-query: 4.1.0
css-tree: 3.2.1
editorconfig: 3.0.2
ember-eslint-parser: 0.14.0(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(typescript@5.9.3)
ember-eslint-parser: 0.14.3(@babel/eslint-parser@8.0.1(@babel/core@8.0.1)(eslint@10.6.0(jiti@2.6.1)))(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3)
ember-rfc176-data: 0.3.18
eslint: 10.6.0(jiti@2.6.1)
eslint-utils: 3.0.0(eslint@10.6.0(jiti@2.6.1))
@@ -12810,6 +13004,8 @@ snapshots:
requireindex: 1.2.0
snake-case: 3.0.4
svg-tags: 1.0.0
optionalDependencies:
'@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
transitivePeerDependencies:
- '@babel/eslint-parser'
- typescript
@@ -16138,12 +16334,12 @@ snapshots:
styled_string@0.0.1: {}
stylelint-config-recommended-scss@17.0.0(postcss@8.5.15)(stylelint@17.5.0(typescript@5.9.3)):
stylelint-config-recommended-scss@17.0.1(postcss@8.5.15)(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
postcss-scss: 4.0.9(postcss@8.5.15)
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-recommended: 18.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-scss: 7.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-scss: 7.2.0(stylelint@17.5.0(typescript@5.9.3))
optionalDependencies:
postcss: 8.5.15
@@ -16154,7 +16350,7 @@ snapshots:
stylelint-config-standard-scss@17.0.0(postcss@8.5.15)(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-recommended-scss: 17.0.0(postcss@8.5.15)(stylelint@17.5.0(typescript@5.9.3))
stylelint-config-recommended-scss: 17.0.1(postcss@8.5.15)(stylelint@17.5.0(typescript@5.9.3))
stylelint-config-standard: 40.0.0(stylelint@17.5.0(typescript@5.9.3))
optionalDependencies:
postcss: 8.5.15
@@ -16164,12 +16360,15 @@ snapshots:
stylelint: 17.5.0(typescript@5.9.3)
stylelint-config-recommended: 18.0.0(stylelint@17.5.0(typescript@5.9.3))
stylelint-scss@7.0.0(stylelint@17.5.0(typescript@5.9.3)):
stylelint-scss@7.2.0(stylelint@17.5.0(typescript@5.9.3)):
dependencies:
'@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1)
'@csstools/css-tokenizer': 4.0.0
css-tree: 3.2.1
is-plain-object: 5.0.0
known-css-properties: 0.37.0
mdn-data: 2.27.1
postcss-media-query-parser: 0.2.3
postcss-resolve-nested-selector: 0.1.6
postcss-selector-parser: 7.1.4
@@ -16512,6 +16711,10 @@ snapshots:
triple-beam@1.4.1: {}
ts-api-utils@2.5.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
tslib@1.14.1: {}
tslib@2.8.1: {}
@@ -16579,6 +16782,17 @@ snapshots:
dependencies:
semver: 7.8.4
typescript-eslint@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.6.1))(typescript@5.9.3)
eslint: 10.6.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
typescript-memoize@1.1.1: {}
typescript@5.9.3: {}
+43
View File
@@ -0,0 +1,43 @@
# frozen_string_literal: true
describe "Theme TypeScript support" do
it "compiles and runs a theme .gts file authored in TypeScript" do
theme = Fabricate(:theme, name: "TypeScript Theme")
theme.set_field(
target: :extra_js,
type: :js,
name: "discourse/connectors/below-footer/typescript-proof.gts",
value: <<~GTS,
import Component from "@glimmer/component";
interface Rgb {
r: number;
g: number;
b: number;
}
function toCss({ r, g, b }: Rgb): string {
return `rgb(${r}, ${g}, ${b})`;
}
export default class TypescriptProof extends Component {
color: Rgb = { r: 0, g: 128, b: 0 };
get label(): string {
return toCss(this.color);
}
<template>
<div class="ts-theme-proof">{{this.label}}</div>
</template>
}
GTS
)
theme.save!
SiteSetting.default_theme_id = theme.id
visit "/latest"
expect(page).to have_css(".ts-theme-proof", text: "rgb(0, 128, 0)")
end
end