interface ElementOffset { top: number; left: number; bottom: number; right: number; } interface PostValidatorMessage { type: "error" | "warning" | "error"; message: string; } type PostValidator = (postText: string) => [boolean, PostValidatorMessage[]]; interface ProcessedTag { id: number | string text: string desc: string synonyms?: string | QPixelTagSynonym[] } interface UserPreferences { community: Record; global: Record; } interface DelegatedListener { event: string; selector: string; callback: EventListener; } type ClassWatcherCallback = (element: HTMLElement) => void; interface MarkdownAction { apply($field: JQuery): void } interface QPixelDOM { // private properties _delegatedListeners?: DelegatedListener[]; _eventListeners?: Record void>; /** * Adds a delegated event listener. Use when an event listener is required that will fire for elements added to the * DOM dynamically after the delegated listener is added. * @param event An event name to listen for. * @param selector A CSS selector representing elements on which to apply the listener. * @param callback A callback function to pass to the event listener. */ addDelegatedListener?: (event: string, selector: string, callback: EventListener) => void; /** * Adds an event listener to _all_ elements that currently match a selector. * @param event An event name to listen for. * @param selector A CSS selector representing elements on which to apply the listener. * @param callback A callback function to pass to the event listener. */ addSelectorListener?: (event: string, selector: string, callback: EventListener) => void; /** * Smoothly fade an element out of view, then remove it. * @param element The element to fade out. * @param duration A duration for the effect in milliseconds. */ fadeOut?: (element: HTMLElement, duration: number) => void; /** * Formats a given {@link timestamp} with the standard format * @param timestamp timestamp to format */ formatTimestamp?: (timestamp: string | number | Date) => string; /** * Checks common modifier states on a given keyboard event * @param event */ getModifierState?: (event: KeyboardEvent | MouseEvent | JQuery.KeyboardEventBase) => boolean; /** * Is a given event target an HTMLElement? * @param target event target to check */ isHTMLElement?: (target: EventTarget) => target is HTMLElement; /** * Sets visibility of an element or array of elements. Uses display: none so should work with screen readers. * @param elements An element or array of elements to set visibility for. * @param visible Whether or not the elements should be visible. */ setVisible?: (elements: HTMLElement | HTMLElement[], visible: boolean) => void; /** * Registers a callback to run on class list change * @param selector CSS selector to watch for * @param callback callback to call on match */ watchClass?: (selector: string, callback: ClassWatcherCallback) => void; } interface StripMarkdownOptions { /** * Whether to strip away the leading quote ("> content"), if any * @default false */ removeLeadingQuote?: boolean } interface QPixelMD { /** * Inserts text around a given {@link $field}'s selection * @param $field field to insert text into * @param start text to insert at selection start * @param end text to insert at selection end, if any */ insertIntoField?: ($field: JQuery, start: string, end?: string | null) => void; /** * Replace the selected text in an input field with a provided replacement. * @param $field the field in which to replace text * @param text the text with which to replace the selection */ replaceSelection?: ($field: JQuery, text: string) => void; /** * Inserts text at a given {@link idx} in a given {@link str} * @param str text to insert into * @param idx position to insert at * @param insert text to insert */ stringInsert?: (str: string, idx: number, insert: string) => string; /** * See [strip_markdown](app/helpers/application_helper.rb) application helper */ stripMarkdown?: (content: string, options?: StripMarkdownOptions) => string; } interface QPixelStorageGetOptions { /** * Whether the value is supposed to be parsed after retrieval */ parse?: boolean } interface QPixelStorage { /** * Storage prefix to avoid collisions */ readonly prefix: string /** * Gets a value from storage by a given key * @param key key to get a value by * @param options optional configuration */ get(key: string, options: Omit & { parse: true }): object | null; get(key: string, options: Omit & { parse: false }): string | null; get(key: string): string | null; get(key: string, options?: QPixelStorageGetOptions): unknown; /** * Removes a value from storage by a given key * @param key key to remove a value by */ remove(key: string): this /** * Saves a given value to storage under a given key * @param key key to save the value under * @param value value to save (objects will be serialized) */ set(key: string, value: unknown): this } type QPixelKeyboardState = | "home" | "goto" | "tools" | "goto/category-tags" | "goto/category-edits" | "goto/category" | "tools/vote"; type SelectedItemType = "link" | "post"; interface QPixelKeyboard { is_mod: boolean; state: QPixelKeyboardState; selectedItem: HTMLElement | null; selectedItemData?: { type: SelectedItemType; post_id: string; }; user_id: number | null; categories: () => Record; dialog: (...elements: (HTMLElement | string)[]) => void; dialogClose: () => void; updateSelected: () => void; } type QPixelNotification = { community_id: number, community_name: string, content: string, created_at: string, id: number, is_read: boolean, link: string, updated_at: string, user_id: number } type NotificationType = "warning" | "success" | "danger"; type QPixelPopupCallback = (ev: JQuery.ClickEvent, popup: QPixelPopup) => void type QPixelPingablePopupCallback = (ev: JQuery.KeyUpEvent) => Promise declare class QPixelPopup { static destroyAll: () => void; static getPopup: ( items: JQuery[], field: HTMLInputElement | HTMLTextAreaElement, callback: QPixelPopupCallback ) => QPixelPopup; static isSpecialKey: (keyCode: number) => boolean; constructor( items: JQuery[], field: HTMLInputElement | HTMLTextAreaElement, callback: QPixelPopupCallback ); destroy: () => void; getActiveIdx: () => number | null; setActive: (index: number) => void; setCallback: (callback: QPixelPopupCallback) => void; getClickHandler: () => (ev: JQuery.Event) => void; getKeyHandler: () => (ev: JQuery.KeyboardEventBase) => void; updateItems: (items: JQuery[]) => void; updatePosition: () => void; } type QPixelSuccessResponseStatusJSON = 'success' | 'modified' type QPixelFailedResponseStatusJSON = 'failed' type QPixelResponseStatusJSON = QPixelSuccessResponseStatusJSON | QPixelFailedResponseStatusJSON type QPixelBaseResponseJSON = { status: QPixelResponseStatusJSON message?: string } type QPixelSuccessResponseJSON = QPixelBaseResponseJSON & { status: QPixelSuccessResponseStatusJSON } type QPixelFailedResponseJSON = QPixelBaseResponseJSON & { errors?: string[] } type QPixelResponseJSON< Success extends object = object > = (Success & QPixelSuccessResponseJSON) | QPixelFailedResponseJSON type QPixelUploadResponseJSON = QPixelResponseJSON<{ link: string }> type QPixelVoteResponseJSON = QPixelResponseJSON<{ vote_id: number upvotes: number downvotes: number score: number }> type QPixelRetractVoteResponseJSON = QPixelResponseJSON<{ score: number downvotes: number upvotes: number }> type QPixelComment = { id: number created_at: string updated_at: string post_id: number content: string deleted: boolean user_id: number community_id: number comment_thread_id: number has_reference: false reference_text: string | null references_comment_id: string | null } type QPixelFilterSource = 'any' | 'native' | 'imported' type QPixelFilterStatus = 'any' | 'closed' | 'open' type QPixelFilter = { exclude_tags: [string, number][] include_tags: [string, number][] max_answers: number | null max_score: number | null min_answers: number | null min_score: number | null source: QPixelFilterSource status: QPixelFilterStatus system: boolean } type QPixelSuggestedEditActionResult = { message?: string redirect_url?: string status: 'success' | 'error' } type QPixelTag = { community_id: number created_at: string excerpt: string id: number name: string parent_id: number | null tag_set_id: number tag_synonyms: QPixelTagSynonym[] updated_at: string wiki: string | null wiki_markdown: string } type QPixelTagSynonym = { name: string } type QPixelUser = { id: number is_standard: boolean is_moderator: boolean is_admin: boolean is_global_moderator: boolean is_global_admin: boolean se_acct_id: string | null trust_level: number username: string } type QPixelDraft = { body: string comment?: string excerpt?: string license?: string tag_name?: string tags?: string[] title?: string } type QPixelFlagData = { flag_type: number | null post_id: string post_type: 'Comment' | 'Post' reason?: string } interface GetThreadContentOptions { inline?: boolean, showDeleted?: boolean } type QPixelPostType = { id: number name: string description: string | null has_answers: boolean has_votes: boolean has_tags: boolean has_parent: boolean has_category: boolean has_license: boolean is_public_editable: boolean is_closeable: boolean is_top_level: boolean is_freely_editable: boolean icon_name: string | null has_reactions: boolean answer_type_id: number | null has_only_specific_reactions: boolean } interface QPixel { // constants /** * List of HTML tags allowed in posts, supplied by the server */ readonly ALLOWED_POST_TAGS?: readonly string[] /** * List of attributes allowed on HTML tags in posts, supplied by the server */ readonly ALLOWED_POST_ATTRS?: readonly string[] /** * Currently used locale (BCP 47 language tag), supplied by the server */ readonly LOCALE?: string /** * Maximum file upload size (in bytes), supplied by the server */ readonly MAX_UPLOAD_SIZE?: number // private properties _filters?: Record | null; _pendingUser?: Promise | null; _popups?: Record; _preferences?: UserPreferences | null; _user?: QPixelUser | null; // private methods /** * Call _fetchPreferences but only the first time to prevent redundant HTTP requests */ _cachedFetchPreferences?: () => Promise; /** * Update local variable _preferences and storage with an AJAX call for the user preferences */ _fetchPreferences?: () => Promise; /** * FIFO-style fetch wrapper for /users/me requests */ _fetchUser?: () => Promise; /** * Get an object containing the current user's preferences. Loads, in order of precedence, from local variable, * {@link QPixelStorage}, or Redis via AJAX. * @returns user preferences or `null` on failure */ _getPreferences?: () => Promise; /** * Get the key to use for storing user preferences in storage, to avoid conflating users * @returns string the storage key */ _preferencesLocalStorageKey?: () => string; /** * Set local variable _preferences and storage to new preferences data * @param data an object, containing the new preferences data */ _updatePreferencesLocally?: (data: UserPreferences) => void; // public methods /** * Add a button to the Markdown editor. * @param $buttonHtml the HTML content that the button should show - just text, if you like, or * something more complex if you want to. * @param shortName a short name for the action that will be used as the title and aria-label attributes. * @param callback a function that will be passed as the click event callback. */ addEditorButton?: ($buttonHtml: JQuery.htmlString, shortName: string, callback: () => void) => void; /** * Add a validator that will be called before creating a post. * callback should take one parameter, the post text, and should return an array in * the following format: * * [ * true | false, // is the post valid for this check? * [ * { type: 'warning', message: 'warning message - will not block posting' }, * { type: 'error', message: 'error message - will block posting' } * ] * ] */ addPrePostValidation?: (callback: PostValidator) => void; /** * Create a notification popup - not an inbox notification. * @param type the type to apply to the popup - warning, danger, etc. * @param message the message to show */ createNotification?: (type: NotificationType, message: string) => void; /** * Get the word in a string that the given position is in, and the position within that word. * @param splat an array, containing the string already split by however you define a "word" * @param posIdx the index to search for * @returns the word the given position is in, and the position within that word */ currentCaretSequence?: (splat: string[], posIdx: number) => [string, number]; /** * Fetches default user filter for a given category * @param categoryId id of the category to fetch */ defaultFilter?: (categoryId: string) => Promise; deleteFilter?: (name: string, system?: boolean) => Promise; filters?: () => Promise>; /** * Get a list of supported canonical locales for {@link Intl.NumberFormat} based on {@link QPixel.LOCALE}. */ supportedNumberLocales?: () => string[]; /** * Format a given {@link value} into a human-friendly representation. * @param value value (in bytes) to format */ numberToHumanSize?: (value: number) => string; /** * Get the absolute offset of an element. * @param element the element for which to find the offset. * @returns element offset information */ offset?: (element: HTMLElement) => ElementOffset; /** * Get a single user preference by name. * @param name the name of the requested preference * @param community is the requested preference community-local (true), or network-wide (false)? * @returns the value of the requested preference */ preference?: (name: string, community?: boolean) => Promise; setFilter?: (name: string, filter: QPixelFilter, category: string, isDefault: boolean) => Promise; /** * Set a user preference by name to the value provided. * @param name the name of the preference to set * @param value the value to set to - must respond to toString() for {@link QPixelStorage} and Redis * @param community is this preference community-local (true), or network-wide (false)? */ setPreference?: (name: string, value: unknown, community?: boolean) => Promise; /** * Get the user object for the current user. * @returns JSON object containing user details */ user?: () => Promise; /** * Internal. Called just before a post is sent to the server to validate that it passes * all custom checks. */ validatePost?: (postText: string) => [boolean, PostValidatorMessage[]]; /** * Wrapper around {@link fetch} to ensure credentials, CSRF token, and X-Requested-With are always sent * @param uri target URI of the request * @param options options to pass to {@link fetch} */ fetch?: (uri: string | URL, options?: RequestInit) => Promise /** * Send a request with JSON data, pre-authorized with QPixel credentials for the signed in user. * @param uri The URI to which to send the request. * @param data An object containing data to send as the request body. Must be acceptable by JSON.stringify. * @param options An optional {@link RequestInit} to override the defaults provided by this method. * @returns The Response promise returned from {@link fetch}. */ fetchJSON?: (uri: string, data: any, options?: RequestInit) => Promise; /** * @param uri The URI to which to send the request. * @param options An optional {@link RequestInit} to override the defaults provided by {@link fetchJSON} */ getJSON?: (uri: string, options?: Omit) => Promise; /** * Attempts to get a JSON reprentation of a comment * @param id id of the comment to get */ getComment?: (id: string) => Promise /** * Attempts to get a list of notifications for the current user */ getNotifications?: () => Promise /** * Attempts to dynamically load thread content * @param id id of the comment thread * @param options configuration options */ getThreadContent?: (id: string, options?: GetThreadContentOptions) => Promise /** * Attempts to dynamically load a list of comment threads for a given post * @param id id of the post to load */ getThreadsListContent?: (id: string) => Promise /** * Safely parses a JSON response from QPixel API * @param response API response to parse * @param errorMessage error to set on failure to parse */ parseJSONResponse?: (response: Response, errorMessage: string) => Promise /** * Processes JSON responses from QPixel API * @param data parsed response JSON body from the API * @param onSuccess callback to call for successful requests * @param onFinally callback to call for all requests */ handleJSONResponse?: (data: T, onSuccess: (data: Extract) => void, onFinally?: (data: T) => void) => boolean /** * Attempts to archive a comment thread * @param id id of the thread to archive * @returns result of the operation */ archiveThread?: (id: string) => Promise /** * Attempts to delete a comment * @param id id of the comment to delete * @returns result of the operation */ deleteComment?: (id: string) => Promise /** * Attempts to delete a comment thread * @param id id of the thread to delete * @returns result of the operation */ deleteThread?: (id: string) => Promise /** * Attempts to follow a comment thread * @param id id of the thread to follow * @returns result of the operation */ followThread?: (id: string) => Promise /** * Attempts to unfollow a comment thread * @param id id of the thread to unfollow * @returns result of the operation */ unfollowThread?: (id: string) => Promise /** * Attempts to start following comments on a given post * @param postId id of the post to follow comments on * @returns result of the operation */ followComments?: (postId: string) => Promise /** * Attempts to delete a given post draft * @returns result of the operation */ deleteDraft?: () => Promise /** * Attempts to save a post draft * @param draft draft to save * @returns result of the operation */ saveDraft?: (draft: QPixelDraft) => Promise /** * Attempts to undelete a comment * @param id id of the comment to undelete * @returns result of the operation */ undeleteComment?: (id: string) => Promise /** * Attempts to stop following comments on a given post * @param postId id of the post to stop following comments on * @returns result of the operation */ unfollowComments?: (postId: string) => Promise /** * Attempts to lock a comment thread * @param id id of the comment thread to lock * @param duration how long should the thread be locked for, in days * @returns result of the operation */ lockThread?: (id: string, duration?: number) => Promise /** * Attempts to rename a tag * @param categoryId id of the category to rename the tag in * @param tagId id of the tag to rename * @param name new tag name * @returns result of the operation */ renameTag?: (categoryId: string, tagId: string, name: string) => Promise /** * Attempts to retract a vote * @param id id of the vote to retract * @returns result of the operation */ retractVote?: (id: string) => Promise /** * Attempts to raise a flag * @param flag new flag data * @returns result of the operation */ flag?: (flag: QPixelFlagData) => Promise /** * Attempts to vote on a given post * @param postId id of the post to vote on * @param voteType type of the vote * @returns result of the operation */ vote?: (postId: string, voteType: string) => Promise /** * @param url upload endpoint URL (differs between routes) * @param form upload form to get the file from */ upload?: (url: string, form: HTMLFormElement) => Promise // qpixel_dom DOM?: QPixelDOM; // qpixel keyboard actions Keyboard?: QPixelKeyboard; // qpixel Markdown MD?: QPixelMD; // qpixel popups Popup?: typeof QPixelPopup; // qpixel storage management Storage?: QPixelStorage; // Stripe integration, TODO: types stripe?: any; } // Chartkick, TODO: types declare var Chartkick: any; // Highlight.js lib, TODO: types declare var hljs: any; // MathJax lib, TODO: types declare var MathJax: any; // Moment.js lib declare var moment: typeof import('moment'); // DOMPurify lib, TODO: types declare var DOMPurify: any; // Sefaria Linker (no known types), see https://developers.sefaria.org/docs/linker-v2 declare var sefaria: { link: (options?: { contentLang?: 'bilingual' | 'english' | 'hebrew', dynamic?: boolean, excludeFromLinking?: string, excludeFromTracking?: string, hidePopupsOnMobile?: boolean, interfaceLang?: 'english' | 'hebrew', mode?: 'link' | 'popup-click', parenthesesOnly?: boolean, popupStyles?: Record, selector?: string, quotationOnly?: boolean }) => void } | undefined; declare var QPixel: QPixel; declare var getCaretCoordinates: ( element: any, position: any, options: any ) => { top: number; left: number; height: number; }; interface Window { mozInnerScreenX?: number; // MarkdownIt lib, TODO: types converter?: any; markdownit?: (...args: any[]) => any; markdownitFootnote?: (...args: any[]) => any; }