diff --git a/app/hooks/useLastVisitedPath.tsx b/app/hooks/useLastVisitedPath.tsx index 9d2b615e23..e9fcefa74e 100644 --- a/app/hooks/useLastVisitedPath.tsx +++ b/app/hooks/useLastVisitedPath.tsx @@ -1,6 +1,8 @@ import { useCallback, useRef } from "react"; import { getCookie, removeCookie, setCookie } from "tiny-cookie"; -import usePersistedState, { setPersistedState } from "~/hooks/usePersistedState"; +import usePersistedState, { + setPersistedState, +} from "~/hooks/usePersistedState"; import Logger from "~/utils/Logger"; import history from "~/utils/history"; import { isAllowedLoginRedirect } from "~/utils/urls"; @@ -39,9 +41,12 @@ export function useLastVisitedPath(): [string, (path: string) => void] { */ export function useTrackLastVisitedPath(currentPath: string): void { const prevPathRef = useRef(); - + // Update localStorage directly if path has changed - if (prevPathRef.current !== currentPath && isAllowedLoginRedirect(currentPath)) { + if ( + prevPathRef.current !== currentPath && + isAllowedLoginRedirect(currentPath) + ) { prevPathRef.current = currentPath; setPersistedState("lastVisitedPath", currentPath); } diff --git a/app/hooks/useShareMenuActions.tsx b/app/hooks/useShareMenuActions.tsx index 8853333831..8849d6a532 100644 --- a/app/hooks/useShareMenuActions.tsx +++ b/app/hooks/useShareMenuActions.tsx @@ -11,7 +11,7 @@ import { useMenuAction } from "~/hooks/useMenuAction"; /** * Hook that constructs the action menu for share management operations. - * + * * @param targetShare - the share to build actions for, or null to skip. * @returns action with children for use in menus, or undefined if share is null. */ diff --git a/app/hooks/useUserMenuActions.tsx b/app/hooks/useUserMenuActions.tsx index 462a176e6f..bd1b633ffe 100644 --- a/app/hooks/useUserMenuActions.tsx +++ b/app/hooks/useUserMenuActions.tsx @@ -24,7 +24,7 @@ import { /** * Hook that constructs the action menu for user management operations. - * + * * @param targetUser - the user to build actions for, or null to skip. * @returns action with children for use in menus, or undefined if user is null. */ diff --git a/plugins/gitlab/server/GitLabIssueProvider.ts b/plugins/gitlab/server/GitLabIssueProvider.ts index ebe47c4034..390a376a70 100644 --- a/plugins/gitlab/server/GitLabIssueProvider.ts +++ b/plugins/gitlab/server/GitLabIssueProvider.ts @@ -5,7 +5,26 @@ import { Integration, IntegrationAuthentication } from "@server/models"; import { BaseIssueProvider } from "@server/utils/BaseIssueProvider"; import { GitLab } from "./gitlab"; import { sequelize } from "@server/storage/database"; -import { Op } from "sequelize"; +import { Op, type WhereOptions } from "sequelize"; + +interface GitLabWebhookPayload { + event_name?: string; + old_full_path?: string; + old_username?: string; + full_path?: string; + username?: string; + group_id?: string; + user_id?: string; + project_id?: string | number; + project_namespace_id?: string | number; + name?: string; + path_with_namespace?: string; + changes?: { before: string }[]; + project?: { + name: string; + path_with_namespace: string; + }; +} export class GitLabIssueProvider extends BaseIssueProvider { constructor() { @@ -64,7 +83,8 @@ export class GitLabIssueProvider extends BaseIssueProvider { headers: Record; }) { const hookId = headers["x-gitlab-webhook-uuid"] as string; - const eventName = payload.event_name as string; + const typedPayload = payload as GitLabWebhookPayload; + const eventName = typedPayload.event_name; if (!eventName) { Logger.warn( @@ -77,28 +97,28 @@ export class GitLabIssueProvider extends BaseIssueProvider { case "project_update": case "project_transfer": case "project_rename": - await this.updateProject(payload); + await this.updateProject(typedPayload); break; case "repository_update": - await this.createProject(payload); + await this.createProject(typedPayload); break; case "project_destroy": - await this.destroyProject(payload); + await this.destroyProject(typedPayload); break; case "group_rename": case "user_rename": - await this.updateNamespace(payload); + await this.updateNamespace(typedPayload); break; case "user_destroy": case "group_destroy": - await this.destroyNamespace(payload); + await this.destroyNamespace(typedPayload); break; default: break; } } - private async updateNamespace(payload: Record) { + private async updateNamespace(payload: GitLabWebhookPayload) { const name = payload.old_full_path ?? payload.old_username; const where = { service: IntegrationService.GitLab, @@ -119,6 +139,12 @@ export class GitLabIssueProvider extends BaseIssueProvider { return; } + const newName = payload.full_path ?? payload.username; + if (!newName) { + Logger.warn(`GitLab namespace_update event without new name`); + return; + } + const sources = integration.issueSources ?? []; const updatedSources = sources.map((source) => { if (source.owner.name === name) { @@ -126,7 +152,7 @@ export class GitLabIssueProvider extends BaseIssueProvider { ...source, owner: { id: payload.group_id || source.owner.id, - name: payload.full_path ?? payload.username, + name: newName, }, }; } @@ -139,19 +165,29 @@ export class GitLabIssueProvider extends BaseIssueProvider { }); } - private async destroyNamespace(payload: Record) { + private async destroyNamespace(payload: GitLabWebhookPayload) { + if (!payload.user_id && !payload.full_path) { + Logger.warn( + `GitLab namespace_destroy event without user_id or full_path` + ); + return; + } + let replacements = {}; - const whereCondition: any = { + const whereCondition: WhereOptions = { service: IntegrationService.GitLab, + ...(payload.user_id && { + "settings.gitlab.installation.account.id": payload.user_id, + }), + ...(!payload.user_id && + payload.full_path && { + [Op.and]: sequelize.literal( + `"issueSources"::jsonb @> :jsonCondition` + ), + }), }; - if (payload.user_id) { - whereCondition["settings.gitlab.installation.account.id"] = - payload.user_id; - } else if (payload.full_path) { - whereCondition[Op.and] = sequelize.literal( - `"issueSources"::jsonb @> :jsonCondition` - ); + if (!payload.user_id && payload.full_path) { replacements = { jsonCondition: JSON.stringify([{ owner: { name: payload.full_path } }]), }; @@ -187,7 +223,7 @@ export class GitLabIssueProvider extends BaseIssueProvider { }); } - private async destroyProject(payload: Record) { + private async destroyProject(payload: GitLabWebhookPayload) { await sequelize.transaction(async (transaction) => { const integrations = await Integration.findAll({ where: { @@ -223,14 +259,20 @@ export class GitLabIssueProvider extends BaseIssueProvider { }); } - private async createProject(payload: Record) { - const createEvent = payload.changes.some((p: { before: string }) => + private async createProject(payload: GitLabWebhookPayload) { + const createEvent = payload.changes?.some((p: { before: string }) => /^0{40}$/.test(p.before) ); if (!createEvent) { return; } + + const project = payload.project; + if (!project || !payload.project_id) { + return; + } + await sequelize.transaction(async (transaction) => { const integration = (await Integration.findOne({ where: { @@ -245,7 +287,6 @@ export class GitLabIssueProvider extends BaseIssueProvider { return; } - const project = payload.project; const owner = { id: "", // namespace.id is not provided in this webhook payload name: project.path_with_namespace.split("/").slice(0, -1).join("/"), @@ -264,7 +305,13 @@ export class GitLabIssueProvider extends BaseIssueProvider { }); } - private async updateProject(payload: Record) { + private async updateProject(payload: GitLabWebhookPayload) { + if (!payload.name || !payload.path_with_namespace) { + return; + } + const newName = payload.name; + const pathWithNamespace = payload.path_with_namespace; + await sequelize.transaction(async (transaction) => { const integrations = await Integration.findAll({ where: { @@ -293,8 +340,8 @@ export class GitLabIssueProvider extends BaseIssueProvider { ); if (source) { - source.name = payload.name; - source.owner.name = payload.path_with_namespace + source.name = newName; + source.owner.name = pathWithNamespace .split("/") .slice(0, -1) .join("/"); diff --git a/plugins/iframely/server/iframely.ts b/plugins/iframely/server/iframely.ts index 2b62c325f8..13f7930578 100644 --- a/plugins/iframely/server/iframely.ts +++ b/plugins/iframely/server/iframely.ts @@ -46,7 +46,14 @@ class Iframely { return { error: data.error } as UnfurlError; // In addition to our custom UnfurlError, sometimes iframely returns error in the response body. } - const parsedData = data as Record; + const parsedData = data as { + url: string; + meta: { title: string; description: string; site: string }; + links: { + thumbnail?: { href: string }[]; + icon?: { href: string }[]; + }; + }; return { type: UnfurlResourceType.URL, diff --git a/plugins/oidc/server/auth/OIDCStrategy.ts b/plugins/oidc/server/auth/OIDCStrategy.ts index 5584caaef4..96d15d6a7f 100644 --- a/plugins/oidc/server/auth/OIDCStrategy.ts +++ b/plugins/oidc/server/auth/OIDCStrategy.ts @@ -1,7 +1,13 @@ +import type { Request } from "express"; import { HttpsProxyAgent } from "https-proxy-agent"; import type OAuth2Strategy from "passport-oauth2"; import { Strategy } from "passport-oauth2"; +interface AuthenticateOptions { + originalQuery?: Request["query"]; + [key: string]: unknown; +} + export class OIDCStrategy extends Strategy { constructor( options: OAuth2Strategy.StrategyOptionsWithRequest, @@ -15,12 +21,12 @@ export class OIDCStrategy extends Strategy { } } - authenticate(req: any, options: any) { + authenticate(req: Request, options: AuthenticateOptions) { options.originalQuery = req.query; super.authenticate(req, options); } - authorizationParams(options: any) { + authorizationParams(options: AuthenticateOptions) { return { ...options.originalQuery, ...super.authorizationParams?.(options), diff --git a/plugins/passkeys/client/Settings.tsx b/plugins/passkeys/client/Settings.tsx index 3cb5129d64..4b96b1f0ef 100644 --- a/plugins/passkeys/client/Settings.tsx +++ b/plugins/passkeys/client/Settings.tsx @@ -1,4 +1,5 @@ import { startRegistration } from "@simplewebauthn/browser"; +import type { JSONObject } from "@shared/types"; import { observer } from "mobx-react"; import { KeyIcon, PlusIcon } from "outline-icons"; import * as React from "react"; @@ -66,9 +67,13 @@ function PasskeysSettings() { } ); const attResp = await startRegistration(resp.data); - await client.post("/passkeys.verifyRegistration", attResp as any, { - baseUrl: "/auth", - }); + await client.post( + "/passkeys.verifyRegistration", + attResp as unknown as JSONObject, + { + baseUrl: "/auth", + } + ); toast.success(t("Passkey added successfully")); await loadPasskeys(); } catch (err) { diff --git a/plugins/passkeys/server/auth/passkeys.test.ts b/plugins/passkeys/server/auth/passkeys.test.ts index 2fc91522de..9adb78d6cf 100644 --- a/plugins/passkeys/server/auth/passkeys.test.ts +++ b/plugins/passkeys/server/auth/passkeys.test.ts @@ -8,19 +8,20 @@ describe("getExpectedOrigin", () => { hostname: string; host: string; forwardedPort?: string; - }): APIContext => ({ - protocol: options.protocol, - request: { - hostname: options.hostname, - host: options.host, - get: (header: string) => { - if (header === "X-Forwarded-Port" && options.forwardedPort) { - return options.forwardedPort; - } - return undefined; - }, - } as unknown, - }) as unknown as APIContext; + }): APIContext => + ({ + protocol: options.protocol, + request: { + hostname: options.hostname, + host: options.host, + get: (header: string) => { + if (header === "X-Forwarded-Port" && options.forwardedPort) { + return options.forwardedPort; + } + return undefined; + }, + } as unknown, + }) as unknown as APIContext; it("should construct origin with non-standard HTTPS port from X-Forwarded-Port", () => { const ctx = createMockContext({ diff --git a/plugins/search-postgres/server/PostgresSearchProvider.ts b/plugins/search-postgres/server/PostgresSearchProvider.ts index ca73077181..1361beafa3 100644 --- a/plugins/search-postgres/server/PostgresSearchProvider.ts +++ b/plugins/search-postgres/server/PostgresSearchProvider.ts @@ -236,13 +236,13 @@ export default class PostgresSearchProvider extends BaseSearchProvider { where, limit, offset, - }) as any as Promise; + }) as unknown as Promise; const countQuery = Document.unscoped().count({ // @ts-expect-error Types are incorrect for count replacements: findOptions.replacements, where, - }) as any as Promise; + }) as unknown as Promise; const [results, count] = await Promise.all([resultsQuery, countQuery]); // Final query to get associated document data @@ -428,7 +428,7 @@ export default class PostgresSearchProvider extends BaseSearchProvider { where, limit, offset, - })) as any as RankedDocument[]; + })) as unknown as RankedDocument[]; const countQuery = Document.unscoped().count({ // @ts-expect-error Types are incorrect for count @@ -436,7 +436,7 @@ export default class PostgresSearchProvider extends BaseSearchProvider { include, replacements: findOptions.replacements, where, - }) as any as Promise; + }) as unknown as Promise; // Final query to get associated document data const [documents, count] = await Promise.all([ diff --git a/plugins/slack/server/api/hooks.ts b/plugins/slack/server/api/hooks.ts index aceaf31b75..4614b25f8c 100644 --- a/plugins/slack/server/api/hooks.ts +++ b/plugins/slack/server/api/hooks.ts @@ -238,7 +238,8 @@ router.post( return; } - const { results, total } = await SearchProviderManager.getProvider().searchForUser(user, options); + const { results, total } = + await SearchProviderManager.getProvider().searchForUser(user, options); await SearchQuery.create({ userId: user ? user.id : null, diff --git a/plugins/slack/server/slack.ts b/plugins/slack/server/slack.ts index d4f2a9ced5..2bb6c984a3 100644 --- a/plugins/slack/server/slack.ts +++ b/plugins/slack/server/slack.ts @@ -1,4 +1,4 @@ -import querystring from "node:querystring"; +import querystring, { type ParsedUrlQueryInput } from "node:querystring"; import { InvalidRequestError } from "@server/errors"; import fetch from "@server/utils/fetch"; import { SlackUtils } from "../shared/SlackUtils"; @@ -13,7 +13,10 @@ const SLACK_API_URL = "https://slack.com/api"; * @param body - the request body containing token and other parameters. * @returns the parsed JSON response from Slack. */ -export async function post(endpoint: string, body: Record) { +export async function post( + endpoint: string, + body: { token: string } & Record +) { let data; const { token, ...bodyWithoutToken } = body; @@ -44,7 +47,10 @@ export async function post(endpoint: string, body: Record) { * @param body - the request parameters. * @returns the parsed JSON response from Slack. */ -export async function request(endpoint: string, body: Record) { +export async function request( + endpoint: string, + body: { client_id?: string; client_secret?: string } & ParsedUrlQueryInput +) { let data; const { client_id, client_secret, ...params } = body; diff --git a/plugins/slack/shared/SlackUtils.ts b/plugins/slack/shared/SlackUtils.ts index cad7d4f624..68cf9232c9 100644 --- a/plugins/slack/shared/SlackUtils.ts +++ b/plugins/slack/shared/SlackUtils.ts @@ -16,7 +16,7 @@ export class SlackUtils { static createState( teamId: string, type: IntegrationType, - data?: Record + data?: Record ) { return JSON.stringify({ type, teamId, ...data }); } diff --git a/server/errors.test.ts b/server/errors.test.ts index d94c05ae40..b40b388898 100644 --- a/server/errors.test.ts +++ b/server/errors.test.ts @@ -16,7 +16,10 @@ describe("errors", () => { describe("User input errors", () => { const userInputErrors = [ { name: "AuthenticationError", fn: errors.AuthenticationError }, - { name: "InvalidAuthenticationError", fn: errors.InvalidAuthenticationError }, + { + name: "InvalidAuthenticationError", + fn: errors.InvalidAuthenticationError, + }, { name: "AuthorizationError", fn: errors.AuthorizationError }, { name: "CSRFError", fn: errors.CSRFError }, { name: "RateLimitExceededError", fn: errors.RateLimitExceededError }, @@ -33,12 +36,24 @@ describe("errors", () => { { name: "FileImportError", fn: errors.FileImportError }, { name: "OAuthStateMismatchError", fn: errors.OAuthStateMismatchError }, { name: "TeamPendingDeletionError", fn: errors.TeamPendingDeletionError }, - { name: "EmailAuthenticationRequiredError", fn: errors.EmailAuthenticationRequiredError }, + { + name: "EmailAuthenticationRequiredError", + fn: errors.EmailAuthenticationRequiredError, + }, { name: "MicrosoftGraphError", fn: errors.MicrosoftGraphError }, { name: "TeamDomainRequiredError", fn: errors.TeamDomainRequiredError }, - { name: "GmailAccountCreationError", fn: errors.GmailAccountCreationError }, - { name: "OIDCMalformedUserInfoError", fn: errors.OIDCMalformedUserInfoError }, - { name: "AuthenticationProviderDisabledError", fn: errors.AuthenticationProviderDisabledError }, + { + name: "GmailAccountCreationError", + fn: errors.GmailAccountCreationError, + }, + { + name: "OIDCMalformedUserInfoError", + fn: errors.OIDCMalformedUserInfoError, + }, + { + name: "AuthenticationProviderDisabledError", + fn: errors.AuthenticationProviderDisabledError, + }, { name: "UnprocessableEntityError", fn: errors.UnprocessableEntityError }, { name: "ClientClosedRequestError", fn: errors.ClientClosedRequestError }, ]; @@ -53,7 +68,9 @@ describe("errors", () => { describe("UserSuspendedError", () => { it("should not be marked for Sentry reporting", () => { - const error = errors.UserSuspendedError({ adminEmail: "test@example.com" }); + const error = errors.UserSuspendedError({ + adminEmail: "test@example.com", + }); expect(error.isReportable).toBe(false); }); }); diff --git a/server/queues/processors/IntegrationCreatedProcessor.ts b/server/queues/processors/IntegrationCreatedProcessor.ts index 41a56a8bd8..42c9720cbd 100644 --- a/server/queues/processors/IntegrationCreatedProcessor.ts +++ b/server/queues/processors/IntegrationCreatedProcessor.ts @@ -26,6 +26,8 @@ export default class IntegrationCreatedProcessor extends BaseProcessor { }); // Clear the cache of unfurled data for the team as it may be stale now. - await CacheHelper.clearData(RedisPrefixHelper.getUnfurlKey(integration.teamId)); + await CacheHelper.clearData( + RedisPrefixHelper.getUnfurlKey(integration.teamId) + ); } } diff --git a/server/queues/processors/IntegrationDeletedProcessor.ts b/server/queues/processors/IntegrationDeletedProcessor.ts index 6147316a93..33067233fe 100644 --- a/server/queues/processors/IntegrationDeletedProcessor.ts +++ b/server/queues/processors/IntegrationDeletedProcessor.ts @@ -27,7 +27,9 @@ export default class IntegrationDeletedProcessor extends BaseProcessor { // Clear the cache of unfurled data for the team as it may be stale now. if (integration.type === IntegrationType.Embed) { - await CacheHelper.clearData(RedisPrefixHelper.getUnfurlKey(integration.teamId)); + await CacheHelper.clearData( + RedisPrefixHelper.getUnfurlKey(integration.teamId) + ); } await integration.destroy({ force: true }); diff --git a/server/routes/api/suggestions/suggestions.ts b/server/routes/api/suggestions/suggestions.ts index 5ee394bda6..57e92c992a 100644 --- a/server/routes/api/suggestions/suggestions.ts +++ b/server/routes/api/suggestions/suggestions.ts @@ -74,7 +74,11 @@ router.post( offset, limit, }), - SearchProviderManager.getProvider().searchCollectionsForUser(actor, { query, offset, limit }), + SearchProviderManager.getProvider().searchCollectionsForUser(actor, { + query, + offset, + limit, + }), ]); ctx.body = { diff --git a/server/routes/app.ts b/server/routes/app.ts index b598752f29..fe07b8aeac 100644 --- a/server/routes/app.ts +++ b/server/routes/app.ts @@ -341,7 +341,7 @@ export const renderShare = async (ctx: Context, next: Next) => { content, shortcutIcon: publicBranding && team?.avatarUrl - ? (await team.publicAvatarUrl()) ?? undefined + ? ((await team.publicAvatarUrl()) ?? undefined) : undefined, analytics, isShare: true, diff --git a/server/utils/getInstallationInfo.test.ts b/server/utils/getInstallationInfo.test.ts index 40e7df7e2d..7b3c3d03a3 100644 --- a/server/utils/getInstallationInfo.test.ts +++ b/server/utils/getInstallationInfo.test.ts @@ -19,11 +19,7 @@ describe("getVersionInfo", () => { it("should return version info when Docker Hub is accessible", async () => { fetchMock.mockResponseOnce( JSON.stringify({ - results: [ - { name: "0.81.0" }, - { name: "0.80.0" }, - { name: "0.79.0" }, - ], + results: [{ name: "0.81.0" }, { name: "0.80.0" }, { name: "0.79.0" }], next: null, }) ); diff --git a/shared/editor/queries/getDocumentHighlightColors.test.ts b/shared/editor/queries/getDocumentHighlightColors.test.ts index 00febc2950..a00dff9a0b 100644 --- a/shared/editor/queries/getDocumentHighlightColors.test.ts +++ b/shared/editor/queries/getDocumentHighlightColors.test.ts @@ -5,9 +5,9 @@ describe("getDocumentHighlightColors", () => { it("returns empty array when no highlights exist", () => { const testDoc = doc([p("Plain text without highlights")]); const state = createEditorState(testDoc); - + const colors = getDocumentHighlightColors(state); - + expect(colors).toEqual([]); }); @@ -15,18 +15,18 @@ describe("getDocumentHighlightColors", () => { // Create text with highlight marks const highlightMark1 = schema.marks.highlight.create({ color: "#FDEA9B" }); const highlightMark2 = schema.marks.highlight.create({ color: "#FED46A" }); - + const text1 = schema.text("Highlighted text 1", [highlightMark1]); const text2 = schema.text(" and more ", [highlightMark2]); const text3 = schema.text("and again", [highlightMark1]); - + const testDoc = doc([ - schema.nodes.paragraph.create(null, [text1, text2, text3]) + schema.nodes.paragraph.create(null, [text1, text2, text3]), ]); const state = createEditorState(testDoc); - + const colors = getDocumentHighlightColors(state); - + expect(colors).toHaveLength(2); expect(colors).toContain("#FDEA9B"); expect(colors).toContain("#FED46A"); @@ -34,18 +34,18 @@ describe("getDocumentHighlightColors", () => { it("deduplicates colors used multiple times", () => { const highlightMark = schema.marks.highlight.create({ color: "#FDEA9B" }); - + const text1 = schema.text("First highlight", [highlightMark]); const text2 = schema.text("Second highlight", [highlightMark]); - + const testDoc = doc([ schema.nodes.paragraph.create(null, [text1]), - schema.nodes.paragraph.create(null, [text2]) + schema.nodes.paragraph.create(null, [text2]), ]); const state = createEditorState(testDoc); - + const colors = getDocumentHighlightColors(state); - + expect(colors).toHaveLength(1); expect(colors).toContain("#FDEA9B"); }); @@ -54,20 +54,20 @@ describe("getDocumentHighlightColors", () => { const highlightMark1 = schema.marks.highlight.create({ color: "#FDEA9B" }); const highlightMark2 = schema.marks.highlight.create({ color: "#FED46A" }); const highlightMark3 = schema.marks.highlight.create({ color: "#FA551E" }); - + const text1 = schema.text("First paragraph", [highlightMark1]); const text2 = schema.text("Second paragraph", [highlightMark2]); const text3 = schema.text("Third paragraph", [highlightMark3]); - + const testDoc = doc([ schema.nodes.paragraph.create(null, [text1]), schema.nodes.paragraph.create(null, [text2]), - schema.nodes.paragraph.create(null, [text3]) + schema.nodes.paragraph.create(null, [text3]), ]); const state = createEditorState(testDoc); - + const colors = getDocumentHighlightColors(state); - + expect(colors).toHaveLength(3); expect(colors).toContain("#FDEA9B"); expect(colors).toContain("#FED46A"); @@ -77,17 +77,17 @@ describe("getDocumentHighlightColors", () => { it("ignores text with other marks but no highlight", () => { const boldMark = schema.marks.strong.create(); const highlightMark = schema.marks.highlight.create({ color: "#FDEA9B" }); - + const boldText = schema.text("Bold text", [boldMark]); const highlightedText = schema.text("Highlighted text", [highlightMark]); - + const testDoc = doc([ - schema.nodes.paragraph.create(null, [boldText, highlightedText]) + schema.nodes.paragraph.create(null, [boldText, highlightedText]), ]); const state = createEditorState(testDoc); - + const colors = getDocumentHighlightColors(state); - + expect(colors).toHaveLength(1); expect(colors).toContain("#FDEA9B"); }); diff --git a/shared/editor/queries/getDocumentHighlightColors.ts b/shared/editor/queries/getDocumentHighlightColors.ts index d4533b4baf..1be68c121d 100644 --- a/shared/editor/queries/getDocumentHighlightColors.ts +++ b/shared/editor/queries/getDocumentHighlightColors.ts @@ -11,7 +11,9 @@ export function getDocumentHighlightColors(state: EditorState): string[] { state.doc.descendants((node) => { if (node.isText) { - const highlightMark = node.marks.find((mark) => mark.type.name === "highlight"); + const highlightMark = node.marks.find( + (mark) => mark.type.name === "highlight" + ); if (highlightMark?.attrs.color) { colors.add(highlightMark.attrs.color); } diff --git a/shared/editor/rules/alphaLists.ts b/shared/editor/rules/alphaLists.ts index 827ae8edd5..47bf449d5d 100644 --- a/shared/editor/rules/alphaLists.ts +++ b/shared/editor/rules/alphaLists.ts @@ -54,14 +54,17 @@ export default function markdownItAlphaLists(md: MarkdownIt): void { // Post-process tokens to add the listStyle attribute md.core.ruler.after("block", "alpha_lists_postprocess", (state) => { - if (!state.env.alphaListMarkers || state.env.alphaListMarkers.length === 0) { + if ( + !state.env.alphaListMarkers || + state.env.alphaListMarkers.length === 0 + ) { return; } const markers = state.env.alphaListMarkers; // Build a map of line numbers to markers for more reliable matching - const lineToMarkerMap = new Map(); + const lineToMarkerMap = new Map(); for (const marker of markers) { lineToMarkerMap.set(marker.lineIndex, marker); }