mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
chore: clear mechanical lint warnings (Phase 1) (#12198)
* chore: clear mechanical lint warnings Drops 44 oxlint warnings (559 → 515) by fixing easy mechanical rules across the codebase: no-useless-escape, no-duplicate-type-constituents, no-redundant-type-constituents, no-unused-expressions, no-meaningless-void-operator, require-array-sort-compare, await-thenable. * chore: drop callback parameter from useCallback deps The `open` argument is a parameter of the callback, not a closed-over variable, so it doesn't belong in the deps array. * chore: promote cleared lint rules to errors Promotes the rules cleared in this PR from warn to error so future violations fail the lint: - no-unused-expressions - typescript/await-thenable - typescript/no-duplicate-type-constituents - typescript/no-meaningless-void-operator - typescript/require-array-sort-compare Removes the override that suppressed no-useless-escape on source files (the global rule is already error) and fixes the 21 escape violations that this exposed in regex character classes and template literals. * chore: address PR review feedback - usePinnedDocuments: simplify UrlId to plain string instead of the intersection trick. - PlantUML embed: move - to end of character class so it's a literal hyphen rather than a range operator. - checkboxes: type token params as Token | undefined to match the actual call sites that pass tokens[index - 2] etc.
This commit is contained in:
+5
-1
@@ -73,9 +73,13 @@
|
|||||||
"eqeqeq": "error",
|
"eqeqeq": "error",
|
||||||
"curly": "error",
|
"curly": "error",
|
||||||
"no-console": "error",
|
"no-console": "error",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
"arrow-body-style": ["error", "as-needed"],
|
"arrow-body-style": ["error", "as-needed"],
|
||||||
"no-useless-escape": "off",
|
|
||||||
"react/react-in-jsx-scope": "off",
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"typescript/await-thenable": "error",
|
||||||
|
"typescript/no-duplicate-type-constituents": "error",
|
||||||
|
"typescript/no-meaningless-void-operator": "error",
|
||||||
|
"typescript/require-array-sort-compare": "error",
|
||||||
"react/self-closing-comp": [
|
"react/self-closing-comp": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const ContextMenu = observer(
|
|||||||
onClose?.();
|
onClose?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[open, onOpen, onClose]
|
[onOpen, onClose]
|
||||||
);
|
);
|
||||||
|
|
||||||
const enablePointerEvents = React.useCallback(() => {
|
const enablePointerEvents = React.useCallback(() => {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export const Suggestions = observer(
|
|||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
void fetchUsersByQuery(query);
|
fetchUsersByQuery(query);
|
||||||
}, [query, fetchUsersByQuery]);
|
}, [query, fetchUsersByQuery]);
|
||||||
|
|
||||||
function getListItemProps(suggestion: User | Group) {
|
function getListItemProps(suggestion: User | Group) {
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ type ContentProps = React.ComponentPropsWithoutRef<
|
|||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>;
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>;
|
||||||
|
|
||||||
const MenuContent = React.forwardRef<
|
const MenuContent = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Content>
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
|
||||||
ContentProps
|
ContentProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -120,8 +119,7 @@ type SubMenuTriggerProps = BaseItemProps &
|
|||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger>;
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger>;
|
||||||
|
|
||||||
const SubMenuTrigger = React.forwardRef<
|
const SubMenuTrigger = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
|
||||||
SubMenuTriggerProps
|
SubMenuTriggerProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -150,8 +148,7 @@ type SubMenuContentProps = React.ComponentPropsWithoutRef<
|
|||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>;
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>;
|
||||||
|
|
||||||
const SubMenuContent = React.forwardRef<
|
const SubMenuContent = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.SubContent>
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
|
||||||
SubMenuContentProps
|
SubMenuContentProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -203,8 +200,7 @@ type MenuGroupProps = {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const MenuGroup = React.forwardRef<
|
const MenuGroup = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Group>
|
React.ElementRef<typeof DropdownMenuPrimitive.Group>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Group>,
|
|
||||||
MenuGroupProps
|
MenuGroupProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -275,8 +271,7 @@ type MenuButtonProps = BaseItemProps & {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const MenuButton = React.forwardRef<
|
const MenuButton = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Item>
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
||||||
MenuButtonProps
|
MenuButtonProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -338,8 +333,7 @@ type MenuInternalLinkProps = BaseItemProps & {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const MenuInternalLink = React.forwardRef<
|
const MenuInternalLink = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Item>
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
||||||
MenuInternalLinkProps
|
MenuInternalLinkProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -375,8 +369,7 @@ type MenuExternalLinkProps = BaseItemProps & {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const MenuExternalLink = React.forwardRef<
|
const MenuExternalLink = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Item>
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
||||||
MenuExternalLinkProps
|
MenuExternalLinkProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -409,8 +402,7 @@ type MenuSeparatorProps = React.ComponentPropsWithoutRef<
|
|||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>;
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>;
|
||||||
|
|
||||||
const MenuSeparator = React.forwardRef<
|
const MenuSeparator = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Separator>
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
|
||||||
MenuSeparatorProps
|
MenuSeparatorProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
@@ -434,8 +426,7 @@ type MenuLabelProps = React.ComponentPropsWithoutRef<
|
|||||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label>;
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label>;
|
||||||
|
|
||||||
const MenuLabel = React.forwardRef<
|
const MenuLabel = React.forwardRef<
|
||||||
| React.ElementRef<typeof DropdownMenuPrimitive.Label>
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
| React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
|
||||||
MenuLabelProps
|
MenuLabelProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { variant } = useMenuContext();
|
const { variant } = useMenuContext();
|
||||||
|
|||||||
@@ -144,7 +144,11 @@ const LinkEditor: React.FC<Props> = ({
|
|||||||
|
|
||||||
if (selectedIndex >= 0 && results[selectedIndex]) {
|
if (selectedIndex >= 0 && results[selectedIndex]) {
|
||||||
const selectedDoc = results[selectedIndex];
|
const selectedDoc = results[selectedIndex];
|
||||||
!mark ? addLink(selectedDoc.url) : updateLink(selectedDoc.url);
|
if (!mark) {
|
||||||
|
addLink(selectedDoc.url);
|
||||||
|
} else {
|
||||||
|
updateLink(selectedDoc.url);
|
||||||
|
}
|
||||||
} else if (!trimmedQuery) {
|
} else if (!trimmedQuery) {
|
||||||
removeLink();
|
removeLink();
|
||||||
} else if (!mark) {
|
} else if (!mark) {
|
||||||
@@ -238,7 +242,11 @@ const LinkEditor: React.FC<Props> = ({
|
|||||||
{results.map((doc, index) => (
|
{results.map((doc, index) => (
|
||||||
<SuggestionsMenuItem
|
<SuggestionsMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
!mark ? addLink(doc.path) : updateLink(doc.path);
|
if (!mark) {
|
||||||
|
addLink(doc.path);
|
||||||
|
} else {
|
||||||
|
updateLink(doc.path);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onPointerMove={() => setSelectedIndex(index)}
|
onPointerMove={() => setSelectedIndex(index)}
|
||||||
selected={index === selectedIndex}
|
selected={index === selectedIndex}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { InputRule } from "@shared/editor/lib/InputRule";
|
|||||||
|
|
||||||
const rightArrow = new InputRule(/->$/, "→");
|
const rightArrow = new InputRule(/->$/, "→");
|
||||||
// Note that the suppression of pipe here prevents conflict with table creation rule.
|
// Note that the suppression of pipe here prevents conflict with table creation rule.
|
||||||
const emdash = new InputRule(/(?:^|[^\|])(--\s)$/, "— ");
|
const emdash = new InputRule(/(?:^|[^|])(--\s)$/, "— ");
|
||||||
const oneHalf = new InputRule(/(?:^|\s)(1\/2)$/, "½");
|
const oneHalf = new InputRule(/(?:^|\s)(1\/2)$/, "½");
|
||||||
const threeQuarters = new InputRule(/(?:^|\s)(3\/4)$/, "¾");
|
const threeQuarters = new InputRule(/(?:^|\s)(3\/4)$/, "¾");
|
||||||
const copyright = new InputRule(/\(c\)$/, "©️");
|
const copyright = new InputRule(/\(c\)$/, "©️");
|
||||||
@@ -12,17 +12,11 @@ const trademarked = new InputRule(/\(tm\)$/, "™️");
|
|||||||
const ellipsis = new InputRule(/\.\.\.$/, "…");
|
const ellipsis = new InputRule(/\.\.\.$/, "…");
|
||||||
|
|
||||||
// Double quotes
|
// Double quotes
|
||||||
const openDoubleQuote = new InputRule(
|
const openDoubleQuote = new InputRule(/(?:^|[\s{[(<'"\u2018\u201C])(")$/, "“");
|
||||||
/(?:^|[\s\{\[\(\<'"\u2018\u201C])(")$/,
|
|
||||||
"“"
|
|
||||||
);
|
|
||||||
const closeDoubleQuote = new InputRule(/^(?!.*`)[\s\S]*(")$/, "”");
|
const closeDoubleQuote = new InputRule(/^(?!.*`)[\s\S]*(")$/, "”");
|
||||||
|
|
||||||
// Single quotes
|
// Single quotes
|
||||||
const openSingleQuote = new InputRule(
|
const openSingleQuote = new InputRule(/(?:^|[\s{[(<'"\u2018\u201C])(')$/, "‘");
|
||||||
/(?:^|[\s\{\[\(\<'"\u2018\u201C])(')$/,
|
|
||||||
"‘"
|
|
||||||
);
|
|
||||||
const closeSingleQuote = new InputRule(/^(?!.*`)[\s\S]*(')$/, "’");
|
const closeSingleQuote = new InputRule(/^(?!.*`)[\s\S]*(')$/, "’");
|
||||||
|
|
||||||
export default class SmartText extends Extension {
|
export default class SmartText extends Extension {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default class Suggestion extends Extension {
|
|||||||
: `(?:${triggers.map(escapeRegExp).join("|")})`;
|
: `(?:${triggers.map(escapeRegExp).join("|")})`;
|
||||||
|
|
||||||
this.openRegex = new RegExp(
|
this.openRegex = new RegExp(
|
||||||
`(?:^|\\s|\\(|[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}])${triggerPattern}(${`[\\p{L}\/\\p{M}\\d${
|
`(?:^|\\s|\\(|[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}])${triggerPattern}(${`[\\p{L}/\\p{M}\\d${
|
||||||
this.options.allowSpaces ? "\\s{1}" : ""
|
this.options.allowSpaces ? "\\s{1}" : ""
|
||||||
}\\.\\-–_]+`})${this.options.requireSearchTerm ? "" : "?"}$`,
|
}\\.\\-–_]+`})${this.options.requireSearchTerm ? "" : "?"}$`,
|
||||||
"u"
|
"u"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const DEFAULT_LIMIT = 10;
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function usePaginatedRequest<T = unknown>(
|
export default function usePaginatedRequest<T = unknown>(
|
||||||
requestFn: (params?: PaginationParams | undefined) => Promise<T[]>,
|
requestFn: (params?: PaginationParams) => Promise<T[]>,
|
||||||
params: PaginationParams = {}
|
params: PaginationParams = {}
|
||||||
): RequestResponse<T> {
|
): RequestResponse<T> {
|
||||||
const [data, setData] = useState<T[]>();
|
const [data, setData] = useState<T[]>();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||||||
import usePersistedState from "~/hooks/usePersistedState";
|
import usePersistedState from "~/hooks/usePersistedState";
|
||||||
import useStores from "./useStores";
|
import useStores from "./useStores";
|
||||||
|
|
||||||
type UrlId = "home" | string;
|
type UrlId = string;
|
||||||
|
|
||||||
export const pinsCacheKey = (urlId: UrlId) => `pins-${urlId}`;
|
export const pinsCacheKey = (urlId: UrlId) => `pins-${urlId}`;
|
||||||
|
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ export function useDocumentSave({
|
|||||||
titleRef.current = value;
|
titleRef.current = value;
|
||||||
document.title = value;
|
document.title = value;
|
||||||
updateIsDirtyRef.current();
|
updateIsDirtyRef.current();
|
||||||
void autosave();
|
autosave();
|
||||||
},
|
},
|
||||||
[document, autosave]
|
[document, autosave]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class OAuthScopeHelper {
|
|||||||
return t("Write all data");
|
return t("Write all data");
|
||||||
}
|
}
|
||||||
|
|
||||||
const [namespace, method] = scope.replace("/api/", "").split(/[:\.]/g);
|
const [namespace, method] = scope.replace("/api/", "").split(/[:.]/g);
|
||||||
const readableMethod =
|
const readableMethod =
|
||||||
methodToReadable[method as keyof typeof methodToReadable] ?? method;
|
methodToReadable[method as keyof typeof methodToReadable] ?? method;
|
||||||
if (!readableMethod) {
|
if (!readableMethod) {
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ const LoadingState = observer(function LoadingState() {
|
|||||||
ui.addActiveModel(template);
|
ui.addActiveModel(template);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
template && ui.removeActiveModel(template);
|
if (template) {
|
||||||
|
ui.removeActiveModel(template);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [template, ui]);
|
}, [template, ui]);
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default class PinsStore extends Store<Pin> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
fetchPage = async (params?: FetchParams | undefined): Promise<Pin[]> => {
|
fetchPage = async (params?: FetchParams): Promise<Pin[]> => {
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ export default class StarsStore extends Store<Star> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
fetchPage = async (
|
fetchPage = async (params?: PaginationParams): Promise<Star[]> => {
|
||||||
params?: PaginationParams | undefined
|
|
||||||
): Promise<Star[]> => {
|
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ export default class UserMembershipsStore extends Store<UserMembership> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
fetchPage = async (
|
fetchPage = async (params?: PaginationParams): Promise<UserMembership[]> => {
|
||||||
params?: PaginationParams | undefined
|
|
||||||
): Promise<UserMembership[]> => {
|
|
||||||
this.isFetching = true;
|
this.isFetching = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ export default abstract class Store<T extends Model> {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
fetchPage = async (
|
fetchPage = async (
|
||||||
params?: FetchPageParams | undefined
|
params?: FetchPageParams
|
||||||
): Promise<PaginatedResponse<T>> => {
|
): Promise<PaginatedResponse<T>> => {
|
||||||
if (!this.actions.includes(RPCAction.List)) {
|
if (!this.actions.includes(RPCAction.List)) {
|
||||||
throw new Error(`Cannot list ${this.modelName}`);
|
throw new Error(`Cannot list ${this.modelName}`);
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class ApiClient {
|
|||||||
|
|
||||||
post = <T = any>(
|
post = <T = any>(
|
||||||
path: string,
|
path: string,
|
||||||
data?: JSONObject | FormData | undefined,
|
data?: JSONObject | FormData,
|
||||||
options?: FetchOptions
|
options?: FetchOptions
|
||||||
): Promise<T> => {
|
): Promise<T> => {
|
||||||
if (data instanceof FormData) {
|
if (data instanceof FormData) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class ApiKey extends ParanoidModel<
|
|||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/** A list of scopes that this API key has access to */
|
/** A list of scopes that this API key has access to */
|
||||||
@Matches(/[\/\.\w\s]*/, {
|
@Matches(/[/.\w\s]*/, {
|
||||||
each: true,
|
each: true,
|
||||||
})
|
})
|
||||||
@Column(DataType.ARRAY(DataType.STRING))
|
@Column(DataType.ARRAY(DataType.STRING))
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class OAuthAuthentication extends ParanoidModel<
|
|||||||
grantId: string | null;
|
grantId: string | null;
|
||||||
|
|
||||||
/** A list of scopes that this authentication has access to */
|
/** A list of scopes that this authentication has access to */
|
||||||
@Matches(/[\/\.\w\s]*/, {
|
@Matches(/[/.\w\s]*/, {
|
||||||
each: true,
|
each: true,
|
||||||
})
|
})
|
||||||
@Column(DataType.ARRAY(DataType.STRING))
|
@Column(DataType.ARRAY(DataType.STRING))
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class OAuthAuthorizationCode extends IdModel<
|
|||||||
grantId: string | null;
|
grantId: string | null;
|
||||||
|
|
||||||
/** A list of scopes that this authorization code has access to */
|
/** A list of scopes that this authorization code has access to */
|
||||||
@Matches(/[\/\.\w\s]*/, {
|
@Matches(/[/.\w\s]*/, {
|
||||||
each: true,
|
each: true,
|
||||||
})
|
})
|
||||||
@Column(DataType.ARRAY(DataType.STRING))
|
@Column(DataType.ARRAY(DataType.STRING))
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export default class ImportJSONTask extends ImportTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Object.values(item.attachments).length) {
|
if (Object.values(item.attachments).length) {
|
||||||
await mapAttachments(item.attachments);
|
mapAttachments(item.attachments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,11 +228,11 @@ export default class ImportMarkdownZipTask extends ImportTask {
|
|||||||
document.text = document.text
|
document.text = document.text
|
||||||
.replace(new RegExp(escapeRegExp(encodedPath), "g"), reference)
|
.replace(new RegExp(escapeRegExp(encodedPath), "g"), reference)
|
||||||
.replace(
|
.replace(
|
||||||
new RegExp(`\\\.?/?${escapeRegExp(normalizedAttachmentPath)}`, "g"),
|
new RegExp(`\\.?/?${escapeRegExp(normalizedAttachmentPath)}`, "g"),
|
||||||
reference
|
reference
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
new RegExp(`\\\.?/?${escapeRegExp(genericNormalizedPath)}`, "g"),
|
new RegExp(`\\.?/?${escapeRegExp(genericNormalizedPath)}`, "g"),
|
||||||
reference
|
reference
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,9 +397,9 @@ describe("#comments.list", () => {
|
|||||||
|
|
||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
expect(body.data.length).toEqual(2);
|
expect(body.data.length).toEqual(2);
|
||||||
expect([body.data[0].id, body.data[1].id].sort()).toEqual(
|
expect(
|
||||||
[comment1.id, comment2.id].sort()
|
[body.data[0].id, body.data[1].id].sort((a, b) => a.localeCompare(b))
|
||||||
);
|
).toEqual([comment1.id, comment2.id].sort((a, b) => a.localeCompare(b)));
|
||||||
expect(body.policies.length).toEqual(2);
|
expect(body.policies.length).toEqual(2);
|
||||||
expect(body.policies[0].abilities.read).toBeTruthy();
|
expect(body.policies[0].abilities.read).toBeTruthy();
|
||||||
expect(body.policies[1].abilities.read).toBeTruthy();
|
expect(body.policies[1].abilities.read).toBeTruthy();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ describe("#ValidateKey.sanitize", () => {
|
|||||||
const uuid1 = randomUUID();
|
const uuid1 = randomUUID();
|
||||||
const uuid2 = randomUUID();
|
const uuid2 = randomUUID();
|
||||||
expect(
|
expect(
|
||||||
ValidateKey.sanitize(`public/${uuid1}/${uuid2}/~\.\u0000\malicious_key`)
|
ValidateKey.sanitize(`public/${uuid1}/${uuid2}/~.\u0000malicious_key`)
|
||||||
).toEqual(`public/${uuid1}/${uuid2}/~.malicious_key`);
|
).toEqual(`public/${uuid1}/${uuid2}/~.malicious_key`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -183,9 +183,7 @@ const embeds: EmbedDescriptor[] = [
|
|||||||
id: "canva",
|
id: "canva",
|
||||||
title: "Canva",
|
title: "Canva",
|
||||||
keywords: "design",
|
keywords: "design",
|
||||||
regexMatch: [
|
regexMatch: [/^https:\/\/(?:www\.)?canva\.com\/design\/([/a-zA-Z0-9_-]*)$/],
|
||||||
/^https:\/\/(?:www\.)?canva\.com\/design\/([\/a-zA-Z0-9_\-]*)$/,
|
|
||||||
],
|
|
||||||
transformMatch: (matches: RegExpMatchArray) => {
|
transformMatch: (matches: RegExpMatchArray) => {
|
||||||
const input = matches.input ?? matches[0];
|
const input = matches.input ?? matches[0];
|
||||||
|
|
||||||
@@ -634,7 +632,7 @@ const embeds: EmbedDescriptor[] = [
|
|||||||
id: "tella",
|
id: "tella",
|
||||||
title: "Tella",
|
title: "Tella",
|
||||||
keywords: "video",
|
keywords: "video",
|
||||||
regexMatch: [/^https?:\/\/(?:www\.)?tella\.tv\/video\/([^\/]+)(?:.*)?$/],
|
regexMatch: [/^https?:\/\/(?:www\.)?tella\.tv\/video\/([^/]+)(?:.*)?$/],
|
||||||
transformMatch: (matches: RegExpMatchArray) =>
|
transformMatch: (matches: RegExpMatchArray) =>
|
||||||
`https://www.tella.tv/video/${matches[1]}/embed?b=0&title=1&a=0&loop=0&t=0&muted=0&wt=1`,
|
`https://www.tella.tv/video/${matches[1]}/embed?b=0&title=1&a=0&loop=0&t=0&muted=0&wt=1`,
|
||||||
icon: <Img src="/images/tella.png" alt="Tella" />,
|
icon: <Img src="/images/tella.png" alt="Tella" />,
|
||||||
@@ -719,7 +717,7 @@ const embeds: EmbedDescriptor[] = [
|
|||||||
title: "YouTube",
|
title: "YouTube",
|
||||||
keywords: "google video",
|
keywords: "google video",
|
||||||
regexMatch: [
|
regexMatch: [
|
||||||
/(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([a-zA-Z0-9_-]{11})([\&\?](.*))?$/i,
|
/(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([a-zA-Z0-9_-]{11})([&?](.*))?$/i,
|
||||||
],
|
],
|
||||||
icon: <Img src="/images/youtube.png" alt="YouTube" />,
|
icon: <Img src="/images/youtube.png" alt="YouTube" />,
|
||||||
component: YouTube,
|
component: YouTube,
|
||||||
@@ -729,7 +727,7 @@ const embeds: EmbedDescriptor[] = [
|
|||||||
title: "Plant UML",
|
title: "Plant UML",
|
||||||
keywords: "plant plantuml uml",
|
keywords: "plant plantuml uml",
|
||||||
regexMatch: [
|
regexMatch: [
|
||||||
/(?:https?:\/\/)?(?:www\.)?editor\.plantuml\.com\/uml\/([a-zA-Z0-9\-_]+)([\&\?].*)?$/i,
|
/(?:https?:\/\/)?(?:www\.)?editor\.plantuml\.com\/uml\/([a-zA-Z0-9_-]+)([&?].*)?$/i,
|
||||||
],
|
],
|
||||||
icon: <Img src="/images/plantuml.png" alt="PlantUml" />,
|
icon: <Img src="/images/plantuml.png" alt="PlantUml" />,
|
||||||
component: PlantUmlDiagrams,
|
component: PlantUmlDiagrams,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function safeSlugify(text: string) {
|
|||||||
|
|
||||||
const slug = `h-${escape(
|
const slug = `h-${escape(
|
||||||
slugify(text, {
|
slugify(text, {
|
||||||
remove: /[!"#$%&'\.()*+,\/:;<=>?@\[\]\\^_`{|}~]/g,
|
remove: /[!"#$%&'.()*+,/:;<=>?@[\]\\^_`{|}~]/g,
|
||||||
lower: true,
|
lower: true,
|
||||||
})
|
})
|
||||||
)}`;
|
)}`;
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ this is code
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("returns true for latex fence", () => {
|
test("returns true for latex fence", () => {
|
||||||
expect(isMarkdown(`\$i\$`)).toBe(true);
|
expect(isMarkdown(`$i$`)).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
isMarkdown(`\$0.00
|
isMarkdown(`$0.00
|
||||||
random content
|
random content
|
||||||
\$1.00`)
|
$1.00`)
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,19 @@ import type MarkdownIt from "markdown-it";
|
|||||||
|
|
||||||
const CHECKBOX_REGEX = /\[(X|\s|_|-)\]\s(.*)?/i;
|
const CHECKBOX_REGEX = /\[(X|\s|_|-)\]\s(.*)?/i;
|
||||||
|
|
||||||
function matches(token: Token | void) {
|
function matches(token: Token | undefined) {
|
||||||
return token && token.content.match(CHECKBOX_REGEX);
|
return token && token.content.match(CHECKBOX_REGEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInline(token: Token | void): boolean {
|
function isInline(token: Token | undefined): boolean {
|
||||||
return !!token && token.type === "inline";
|
return !!token && token.type === "inline";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isParagraph(token: Token | void): boolean {
|
function isParagraph(token: Token | undefined): boolean {
|
||||||
return !!token && token.type === "paragraph_open";
|
return !!token && token.type === "paragraph_open";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isListItem(token: Token | void): boolean {
|
function isListItem(token: Token | undefined): boolean {
|
||||||
// Only match list_item_open, not checkbox_item_open - items that are already
|
// Only match list_item_open, not checkbox_item_open - items that are already
|
||||||
// checkbox_item_open have been processed (e.g., by the tables rule for
|
// checkbox_item_open have been processed (e.g., by the tables rule for
|
||||||
// checkboxes in table cells) and should not be processed again.
|
// checkboxes in table cells) and should not be processed again.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { full as emojiPlugin } from "markdown-it-emoji";
|
|||||||
import { isUUID } from "validator";
|
import { isUUID } from "validator";
|
||||||
import { nameToEmoji } from "../lib/emoji";
|
import { nameToEmoji } from "../lib/emoji";
|
||||||
|
|
||||||
type Options = MarkdownIt.Options & {
|
type Options = {
|
||||||
emoji: boolean;
|
emoji: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export default class AuthenticationHelper {
|
|||||||
const [namespace, method] = resource.split(".");
|
const [namespace, method] = resource.split(".");
|
||||||
|
|
||||||
return scopes.some((scope) => {
|
return scopes.some((scope) => {
|
||||||
const [scopeNamespace, scopeMethod] = scope.match(/[:\.]/g)
|
const [scopeNamespace, scopeMethod] = scope.match(/[:.]/g)
|
||||||
? scope.replace("/api/", "").split(/[:\.]/g)
|
? scope.replace("/api/", "").split(/[:.]/g)
|
||||||
: ["*", scope];
|
: ["*", scope];
|
||||||
const isRouteScope = scope.startsWith("/api/");
|
const isRouteScope = scope.startsWith("/api/");
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function isCurrency(value: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove digits, separators, whitespace, and negative indicators
|
// Remove digits, separators, whitespace, and negative indicators
|
||||||
remaining = remaining.replace(/[\d.,\s()\-]/g, "");
|
remaining = remaining.replace(/[\d.,\s()-]/g, "");
|
||||||
|
|
||||||
// If anything remains, it's not a valid currency
|
// If anything remains, it's not a valid currency
|
||||||
return remaining.length === 0;
|
return remaining.length === 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user