diff --git a/mw/Api.d.ts b/mw/Api.d.ts index a391580..4f27c01 100644 --- a/mw/Api.d.ts +++ b/mw/Api.d.ts @@ -17,7 +17,8 @@ type TypeOrArray = T extends any ? T | T[] : never; // T[] would be a mixed a type ReplaceValue = T extends U[] ? V[] : V; type UnknownApiParams = Record; -type ApiResponse = Record; // it will always be a JSON object, the rest is uncertain ... + +export type ApiResponse = Record; // it will always be a JSON object, the rest is uncertain ... interface Revision { content: string; diff --git a/mw/ForeignApi.d.ts b/mw/ForeignApi.d.ts index afb5743..0ac4fd3 100644 --- a/mw/ForeignApi.d.ts +++ b/mw/ForeignApi.d.ts @@ -1,6 +1,6 @@ import { ApiOptions } from "./Api"; -interface ForeignApiOptions extends ApiOptions { +export interface ForeignApiOptions extends ApiOptions { /** * Whether to perform all requests anonymously. Use this option if the target wiki may otherwise not accept cross-origin requests, or if you don't need to perform write actions or read restricted information and want to avoid the overhead. */ diff --git a/mw/ForeignUpload.d.ts b/mw/ForeignUpload.d.ts new file mode 100644 index 0000000..441b43c --- /dev/null +++ b/mw/ForeignUpload.d.ts @@ -0,0 +1,59 @@ +import { ApiOptions } from "./Api"; +import { ForeignApiOptions } from "./ForeignApi"; + +declare global { + namespace mw { + /** + * Used to represent an upload in progress on the frontend. + * + * Subclassed to upload to a foreign API, with no other goodies. Use + * this for a generic foreign image repository on your wiki farm. + * + * Note you can provide the {@link target} or not - if the first argument is + * an object, we assume you want the default, and treat it as apiconfig + * instead. + */ + class ForeignUpload extends Upload { + static static: {}; + static super: typeof Upload; + /** @deprecated Use `super` instead */ + static parent: typeof Upload; + + /** + * Used to specify the target repository of the upload. + * + * If you set this to something that isn't 'local', you must be sure to + * add that target to $wgForeignUploadTargets in LocalSettings, and the + * repository must be set up to use CORS and CentralAuth. + * + * Most wikis use "shared" to refer to Wikimedia Commons, we assume that + * in this class and in the messages linked to it. + * + * Defaults to the first available foreign upload target, + * or to local uploads if no foreign target is configured. + */ + target: string; + + /** + * Used to represent an upload in progress on the frontend. + * + * Subclassed to upload to a foreign API, with no other goodies. Use + * this for a generic foreign image repository on your wiki farm. + * + * Note you can provide the {@link target} or not - if the first argument is + * an object, we assume you want the default, and treat it as apiconfig + * instead. + * + * @param {string} [target] Used to set up the target + * wiki. If not remote, this class behaves identically to mw.Upload (unless further subclassed) + * Use the same names as set in $wgForeignFileRepos for this. Also, + * make sure there is an entry in the $wgForeignUploadTargets array for this name. + * @param {Partial} [apiconfig] Passed to the constructor of mw.ForeignApi or mw.Api, as needed. + */ + constructor(target: string, apiconfig?: Partial); + constructor(apiconfig?: Partial); + } + } +} + +export {}; diff --git a/mw/Upload.d.ts b/mw/Upload.d.ts new file mode 100644 index 0000000..222489a --- /dev/null +++ b/mw/Upload.d.ts @@ -0,0 +1,288 @@ +import { ApiOptions, ApiResponse } from "./Api"; + +declare global { + namespace mw { + /** + * Used to represent an upload in progress on the frontend. + * Most of the functionality is implemented in mw.Api.plugin.upload, + * but this model class will tie it together as well as let you perform + * actions in a logical way. + * + * A simple example: + * + * ```js + * var file = new OO.ui.SelectFileWidget(), + * button = new OO.ui.ButtonWidget( { label: 'Save' } ), + * upload = new mw.Upload; + * + * button.on( 'click', function () { + * upload.setFile( file.getValue() ); + * upload.setFilename( file.getValue().name ); + * upload.upload(); + * } ); + * + * $( document.body ).append( file.$element, button.$element ); + * ``` + * + * You can also choose to {@link uploadToStash stash the upload} and + * {@link finishStashUpload finalize} it later: + * + * ```js + * var file, // Some file object + * upload = new mw.Upload, + * stashPromise = $.Deferred(); + * + * upload.setFile( file ); + * upload.uploadToStash().then( function () { + * stashPromise.resolve(); + * } ); + * + * stashPromise.then( function () { + * upload.setFilename( 'foo' ); + * upload.setText( 'bar' ); + * upload.finishStashUpload().then( function () { + * console.log( 'Done!' ); + * } ); + * } ); + * ``` + */ + class Upload { + /** + * Used to represent an upload in progress on the frontend. + * Most of the functionality is implemented in mw.Api.plugin.upload, + * but this model class will tie it together as well as let you perform + * actions in a logical way. + * + * A simple example: + * + * ```js + * var file = new OO.ui.SelectFileWidget(), + * button = new OO.ui.ButtonWidget( { label: 'Save' } ), + * upload = new mw.Upload; + * + * button.on( 'click', function () { + * upload.setFile( file.getValue() ); + * upload.setFilename( file.getValue().name ); + * upload.upload(); + * } ); + * + * $( document.body ).append( file.$element, button.$element ); + * ``` + * + * You can also choose to {@link uploadToStash stash the upload} and + * {@link finishStashUpload finalize} it later: + * + * ```js + * var file, // Some file object + * upload = new mw.Upload, + * stashPromise = $.Deferred(); + * + * upload.setFile( file ); + * upload.uploadToStash().then( function () { + * stashPromise.resolve(); + * } ); + * + * stashPromise.then( function () { + * upload.setFilename( 'foo' ); + * upload.setText( 'bar' ); + * upload.finishStashUpload().then( function () { + * console.log( 'Done!' ); + * } ); + * } ); + * ``` + * + * @param {Api|Partial} [apiconfig] A mw.Api object (or subclass), or configuration + * to pass to the constructor of mw.Api. + */ + constructor(apiconfig?: Api | Partial); + + /** + * Finish a stash upload. + * + * @returns {JQuery.Promise} + */ + finishStashUpload(): JQuery.Promise; + + /** + * Get the mw.Api instance used by this Upload object. + * + * @returns {JQuery.Promise} + */ + getApi(): JQuery.Promise; + + /** + * Gets the base filename from a path name. + * + * @param {string} path + * @returns {string} + */ + getBasename(path: string): string; + + /** + * Get the current value of the edit comment for the upload. + * + * @returns {string} + */ + getComment(): string; + + /** + * Get the file being uploaded. + * + * @returns {HTMLInputElement|File|Blob} + */ + getFile(): HTMLInputElement | File | Blob; + + /** + * Get the filename, to be finalized on upload. + * + * @returns {string} + */ + getFilename(): string; + + /** + * Get the imageinfo object for the finished upload. + * Only available once the upload is finished! Don't try to get it + * beforehand. + * + * @returns {ApiResponse|undefined} + */ + getImageInfo(): ApiResponse | undefined; + + /** + * Gets the state of the upload. + * + * @returns {Upload.State} + */ + getState(): Upload.State; + + /** + * Gets details of the current state. + * + * @returns {any} + */ + getStateDetails(): any; + + /** + * Get the text of the file page, to be created on file upload. + * + * @returns {string} + */ + getText(): string; + + /** + * Get the boolean for whether the file will be watchlisted after upload. + * + * @returns {boolean} + */ + getWatchlist(): boolean; + + /** + * Set the edit comment for the upload. + * + * @param {string} comment + */ + setComment(comment: string): void; + + /** + * Set the file to be uploaded. + * + * @param {HTMLInputElement|File|Blob} file + */ + setFile(file: HTMLInputElement | File | Blob): void; + + /** + * Set the stashed file to finish uploading. + * + * @param {string} filekey + */ + setFilekey(filekey: string): void; + + /** + * Set the filename, to be finalized on upload. + * + * @param {string} filename + */ + setFilename(filename: string): void; + + /** + * Sets the filename based on the filename as it was on the upload. + */ + setFilenameFromFile(): void; + + /** + * Sets the state and state details (if any) of the upload. + * + * @param {Upload.State} state + * @param {any} stateDetails + */ + setState(state: Upload.State, stateDetails: any): void; + + /** + * Set the text of the file page, to be created on file upload. + * + * @param {string} text + */ + setText(text: string): void; + + /** + * Set whether the file should be watchlisted after upload. + * + * @param {boolean} watchlist + */ + setWatchlist(watchlist: boolean): void; + + /** + * Upload the file directly. + * + * @returns {JQuery.Promise} + */ + upload(): JQuery.Promise; + + /** + * Upload the file to the stash to be completed later. + * + * @returns {JQuery.Promise} + */ + uploadToStash(): JQuery.Promise; + } + + namespace Upload { + /** + * State of uploads represented in simple terms. + */ + enum State { + /** + * Upload not yet started + */ + NEW, + + /** + * Upload finished, but there was a warning + */ + WARNING, + + /** + * Upload finished, but there was an error + */ + ERROR, + + /** + * Upload in progress + */ + UPLOADING, + + /** + * Upload finished, but not published, call #finishStashUpload + */ + STASHED, + + /** + * Upload finished and published + */ + UPLOADED, + } + } + } +} + +export {}; diff --git a/mw/cldr.d.ts b/mw/cldr.d.ts new file mode 100644 index 0000000..ac27f04 --- /dev/null +++ b/mw/cldr.d.ts @@ -0,0 +1,22 @@ +declare global { + namespace mw { + /** + * Namespace for CLDR-related utility methods. + */ + namespace cldr { + /** + * Get the plural form index for the number. + * + * In case none of the rules passed, we return `pluralRules.length` - + * that means it is the "other" form. + * + * @param {number} number + * @param {string[]} pluralRules + * @returns {number} plural form index + */ + function getPluralForm(number: number, pluralRules: string[]): number; + } + } +} + +export {}; diff --git a/mw/confirmCloseWindow.d.ts b/mw/confirmCloseWindow.d.ts new file mode 100644 index 0000000..0378fda --- /dev/null +++ b/mw/confirmCloseWindow.d.ts @@ -0,0 +1,69 @@ +interface Options { + /** + * Optional jQuery event namespace, to allow loosely coupled + * external code to release your trigger. For example, the VisualEditor extension can use this + * remove the trigger registered by mediawiki.action.edit, without strong runtime coupling. + */ + namespace?: string; + + /** + * @returns {boolean} Whether to show the dialog to the user. + */ + test?(): boolean; +} + +interface ConfirmCloseWindow { + /** + * Remove the event listener and don't show an alert anymore, if the user wants to leave + * the page. + */ + release(): void; + + /** + * Trigger the module's function manually. + * + * Check, if options.test() returns true and show an alert to the user if he/she want + * to leave this page. Returns false, if options.test() returns false or the user + * cancelled the alert window (~don't leave the page), true otherwise. + * + * @returns {boolean} + */ + trigger(): boolean; +} + +declare global { + namespace mw { + /** + * Prevent the closing of a window with a confirm message (the onbeforeunload event seems to + * work in most browsers.) + * + * This supersedes any previous onbeforeunload handler. If there was a handler before, it is + * restored when you execute the returned release() function. + * + * ```js + * var allowCloseWindow = mw.confirmCloseWindow(); + * // ... do stuff that can't be interrupted ... + * allowCloseWindow.release(); + * ``` + * + * The second function returned is a trigger function to trigger the check and an alert + * window manually, e.g.: + * + * ```js + * var allowCloseWindow = mw.confirmCloseWindow(); + * // ... do stuff that can't be interrupted ... + * if ( allowCloseWindow.trigger() ) { + * // don't do anything (e.g. destroy the input field) + * } else { + * // do whatever you wanted to do + * } + * ``` + * + * @param {Options} [options] + * @returns {ConfirmCloseWindow} An object of functions to work with this module + */ + function confirmCloseWindow(options?: Options): ConfirmCloseWindow; + } +} + +export {}; diff --git a/mw/debug.d.ts b/mw/debug.d.ts new file mode 100644 index 0000000..7349dbf --- /dev/null +++ b/mw/debug.d.ts @@ -0,0 +1,119 @@ +type LogEntryType = "deprecated" | "log" | "warn"; + +interface Data { + debugLog: string[]; + gitBranch: string | false; + gitRevision: string | false; + gitViewUrl: string | false; + includes: File[]; + log: LogEntry[]; + memory: string; + memoryPeak: string; + mwVersion: string; + phpVersion: string; + queries: Query[]; + time: number; +} + +interface File { + name: string; + size: string; +} + +interface LogEntry { + caller: string; + msg: string; + type: LogEntryType; + typeText?: string; +} + +interface Query { + function: string; + sql: string; + time: number; +} + +declare global { + namespace mw { + /** + * Debug toolbar. + * + * Enabled server-side through `$wgDebugToolbar`. + * + * @since 1.19 + */ + namespace Debug { + /** + * Toolbar container element. + */ + const $container: JQuery; + + /** + * Object containing data for the debug toolbar. + */ + const data: Data; + + /** + * Build the console panel. + * + * @returns {JQuery} Console panel + */ + function buildConsoleTable(): JQuery; + + /** + * Build legacy debug log pane. + * + * @returns {JQuery} + */ + function buildDebugLogTable(): JQuery; + + /** + * Construct the HTML for the debugging toolbar. + */ + function buildHtml(): void; + + /** + * Build included files pane. + * + * @returns {JQuery} + */ + function buildIncludesPane(): JQuery; + + /** + * Build query list pane. + * + * @returns {JQuery} + */ + function buildQueryTable(): JQuery; + + /** + * Build request information pane. + * + * @returns {JQuery} + */ + function buildRequestPane(): JQuery; + + /** + * Initialize the debugging pane. + * + * Shouldn't be called before the document is ready + * (since it binds to elements on the page). + * + * @param {Data} [data] Defaults to 'debugInfo' from {@link mw.config} + */ + function init(data?: Data): void; + + /** + * Switch between panes. + * + * Should be called with an HTMLElement as its thisArg, + * because it's meant to be an event handler. + * + * @param {JQuery.Event} e + */ + function switchPane(e: JQuery.Event): void; + } + } +} + +export {}; diff --git a/mw/deflate.d.ts b/mw/deflate.d.ts new file mode 100644 index 0000000..5be4a4a --- /dev/null +++ b/mw/deflate.d.ts @@ -0,0 +1,22 @@ +declare global { + namespace mw { + /** + * Compress the content and add the 'rawdeflate,' prefix. + * + * @example + * ```js + * mw.loader.using( 'mediawiki.deflate' ).then( function () { + * var deflated = mw.deflate( myContent ); + * } ); + * ``` + * + * @param {string|ArrayBuffer} data Undeflated data + * @returns {string} Deflated data + * @see https://github.com/wikimedia/mediawiki/blob/master/resources/src/mediawiki.deflate/mw.deflate.js#L3 + * @see https://doc.wikimedia.org/mediawiki-core/master/php/classDeflate.html + */ + function deflate(data: string | ArrayBuffer): `rawdeflate,${string}`; + } +} + +export {}; diff --git a/mw/index.d.ts b/mw/index.d.ts index f7b395b..210d46d 100644 --- a/mw/index.d.ts +++ b/mw/index.d.ts @@ -1,11 +1,17 @@ import "./Api"; +import "./cldr"; import "./config"; +import "./confirmCloseWindow"; import "./cookie"; +import "./debug"; +import "./deflate"; import "./ForeignApi"; import "./ForeignRest"; +import "./ForeignUpload"; import "./global"; import "./hook"; import "./html"; +import "./inspect"; import "./language"; import "./loader"; import "./log"; @@ -14,9 +20,11 @@ import "./message"; import "./notification"; import "./RegExp"; import "./Rest"; +import "./searchSuggest"; import "./storage"; import "./template"; import "./Title"; +import "./Upload"; import "./Uri"; import "./user"; import "./util"; diff --git a/mw/inspect.d.ts b/mw/inspect.d.ts new file mode 100644 index 0000000..69895f9 --- /dev/null +++ b/mw/inspect.d.ts @@ -0,0 +1,157 @@ +import { ResourceLoaderStoreStats } from "./loader"; + +interface SelectorCounts { + /** + * Number of matched selectors. + */ + matched: number; + /** + * Total number of selectors. + */ + total: number; +} + +interface ResourceLoaderCSSReport { + allSelectors: number; + matchedSelectors: number; + module: string; + percentMatched: `${number}%` | null; +} + +interface ResourceLoaderSizeReport { + name: string; + size: string; + sizeInBytes: number; +} + +interface ResourceLoaderStoreReport extends ResourceLoaderStoreStats { + enabled: boolean; + totalSize: string; + totalSizeInBytes: number; +} + +interface ResourceLoaderTimeReport { + execute: number; + name: string; + script: number; + total: string; + totalInMs: number; +} + +type ResourceLoaderReport = keyof ResourceLoaderReportMap; + +interface ResourceLoaderReportMap { + /** + * For each module with styles, count the number of selectors, and + * count how many match against some element currently in the DOM. + */ + css: ResourceLoaderCSSReport; + + /** + * Generate a breakdown of all loaded modules and their size in + * kibibytes. Modules are ordered from largest to smallest. + */ + size: ResourceLoaderSizeReport; + + /** + * Report stats on mw.loader.store: the number of localStorage + * cache hits and misses, the number of items purged from the + * cache, and the total size of the module blob in localStorage. + */ + store: ResourceLoaderStoreReport; + + /** + * Generate a breakdown of all loaded modules and their time + * spent during initialisation (measured in milliseconds). + * + * This timing data is collected by mw.loader.profiler. + */ + time: ResourceLoaderTimeReport; +} + +interface Dependency { + requires: string[]; + requiredBy: string[]; +} + +declare global { + namespace mw { + /** + * Generate and print reports. + * + * When invoked without arguments, prints all available reports. + * + * @param {...string} [reports] One or more of "size", "css", "store", or "time". + */ + function inspect(...reports: ResourceLoaderReport[]): void; + + namespace inspect { + /** + * @private + */ + const reports: { [P in ResourceLoaderReport]: () => Array }; + + /** + * Given CSS source, count both the total number of selectors it + * contains and the number which match some element in the current + * document. + * + * @param {string} css CSS source + * @returns {SelectorCounts} Selector counts + */ + function auditSelectors(css: string): SelectorCounts; + + /** + * Print tabular data to the console using console.table. + * + * @param {any[]} data Tabular data represented as an array of objects + * with common properties. + */ + function dumpTable(data: any[]): void; + + /** + * Return a map of all dependency relationships between loaded modules. + * + * @returns {Object.} Maps module names to objects. Each sub-object has + * two properties, 'requires' and 'requiredBy'. + */ + function getDependencyGraph(): Record; + + /** + * Get a list of all loaded ResourceLoader modules. + * + * @returns {string[]} List of module names + */ + function getLoadedModules(): string[]; + + /** + * Calculate the byte size of a ResourceLoader module. + * + * @param {string} moduleName The name of the module + * @returns {number|null} Module size in bytes or null + */ + function getModuleSize(moduleName: string): number | null; + + /** + * Perform a string search across the JavaScript and CSS source code + * of all loaded modules and return an array of the names of the + * modules that matched. + * + * @param {string|RegExp} pattern String or regexp to match. + * @returns {string[]} Array of the names of modules that matched. + */ + function grep(pattern: string | RegExp): string[]; + + /** + * Generate and print reports. + * + * When invoked without arguments, prints all available reports. + * + * @param {...ResourceLoaderReport} [reports] One or more of "size", "css", "store", or "time". + */ + function runReports(...reports: ResourceLoaderReport[]): void; + } + } +} + +export {}; diff --git a/mw/loader.d.ts b/mw/loader.d.ts index 177c2ff..6932513 100644 --- a/mw/loader.d.ts +++ b/mw/loader.d.ts @@ -59,7 +59,7 @@ interface ModuleRegistryEntry { version: string; } -interface ResourceLoaderStoreStats { +export interface ResourceLoaderStoreStats { expired: number; failed: number; hits: number; diff --git a/mw/searchSuggest.d.ts b/mw/searchSuggest.d.ts new file mode 100644 index 0000000..68c31c6 --- /dev/null +++ b/mw/searchSuggest.d.ts @@ -0,0 +1,24 @@ +import { ApiResponse } from "./Api"; + +interface ResultInfo { + type: string; + searchId: string; + query: string; +} + +declare global { + namespace mw.searchSuggest { + /** + * Queries the wiki and calls response with the result. + */ + function request( + api: Api, + query: string, + response: (result: string[], info: ResultInfo) => void, + maxRows?: number | "max", + namespace?: number | number[] + ): JQuery.Promise; + } +} + +export {};