Assorted cleanup, minor bug fixes, styling fixes, eslint rules (#5165

* fix: Logic error in toast
fix: Remove useless component

* fix: Logout not clearing all stores

* Add icons to notification settings

* Add eslint rule to enforce spaced comment

* Add eslint rule for arrow-body-style

* Add eslint rule to enforce self-closing components

* Add menu to api key settings
Fix: Deleting webhook subscription does not remove from UI
Split webhook subscriptions into active and inactive
Styling updates
This commit is contained in:
Tom Moor
2023-04-08 08:25:20 -04:00
committed by GitHub
parent 422bdc32d9
commit db73879918
116 changed files with 792 additions and 1088 deletions

View File

@@ -106,8 +106,8 @@ async function teamProvisioner({
}
// We cannot find an existing team, so we create a new one
const team = await sequelize.transaction((transaction) => {
return teamCreator({
const team = await sequelize.transaction((transaction) =>
teamCreator({
name,
domain,
subdomain,
@@ -115,8 +115,8 @@ async function teamProvisioner({
authenticationProviders: [authenticationProvider],
ip,
transaction,
});
});
})
);
return {
team,

View File

@@ -115,9 +115,10 @@ const teamUpdater = async ({ params, user, team, ip }: TeamUpdaterProps) => {
transaction,
});
if (changes) {
const data = changes.reduce((acc, curr) => {
return { ...acc, [curr]: team[curr] };
}, {});
const data = changes.reduce(
(acc, curr) => ({ ...acc, [curr]: team[curr] }),
{}
);
await Event.create(
{

View File

@@ -5,26 +5,24 @@ import EmptySpace from "./EmptySpace";
const url = env.CDN_URL ?? env.URL;
export default () => {
return (
<Table width="100%">
<TBody>
<TR>
<TD>
<EmptySpace height={40} />
<img
alt={env.APP_NAME}
src={
env.isCloudHosted()
? `${url}/email/header-logo.png`
: "cid:header-image"
}
height="48"
width="48"
/>
</TD>
</TR>
</TBody>
</Table>
);
};
export default () => (
<Table width="100%">
<TBody>
<TR>
<TD>
<EmptySpace height={40} />
<img
alt={env.APP_NAME}
src={
env.isCloudHosted()
? `${url}/email/header-logo.png`
: "cid:header-image"
}
height="48"
width="48"
/>
</TD>
</TR>
</TBody>
</Table>
);

View File

@@ -139,22 +139,25 @@ async function start(id: number, disconnect: () => void) {
server.listen(normalizedPortFlag || env.PORT || "3000");
server.setTimeout(env.REQUEST_TIMEOUT);
ShutdownHelper.add("server", ShutdownOrder.last, () => {
return new Promise((resolve, reject) => {
// Calling stop prevents new connections from being accepted and waits for
// existing connections to close for the grace period before forcefully
// closing them.
server.stop((err, gracefully) => {
disconnect();
ShutdownHelper.add(
"server",
ShutdownOrder.last,
() =>
new Promise((resolve, reject) => {
// Calling stop prevents new connections from being accepted and waits for
// existing connections to close for the grace period before forcefully
// closing them.
server.stop((err, gracefully) => {
disconnect();
if (err) {
reject(err);
} else {
resolve(gracefully);
}
});
});
});
if (err) {
reject(err);
} else {
resolve(gracefully);
}
});
})
);
// Handle shutdown signals
process.once("SIGTERM", () => ShutdownHelper.execute());

View File

@@ -126,9 +126,9 @@ class Logger {
}
if (request) {
scope.addEventProcessor((event) => {
return Sentry.Handlers.parseRequest(event, request);
});
scope.addEventProcessor((event) =>
Sentry.Handlers.parseRequest(event, request)
);
}
Sentry.captureException(error);

View File

@@ -123,14 +123,13 @@ class AuthenticationProvider extends Model {
}
};
enable = (options?: SaveOptions<AuthenticationProvider>) => {
return this.update(
enable = (options?: SaveOptions<AuthenticationProvider>) =>
this.update(
{
enabled: true,
},
options
);
};
}
export default AuthenticationProvider;

View File

@@ -477,12 +477,10 @@ class Collection extends ParanoidModel {
id: string
) => {
children = await Promise.all(
children.map(async (childDocument) => {
return {
...childDocument,
children: await removeFromChildren(childDocument.children, id),
};
})
children.map(async (childDocument) => ({
...childDocument,
children: await removeFromChildren(childDocument.children, id),
}))
);
const match = find(children, {
id,
@@ -562,8 +560,8 @@ class Collection extends ParanoidModel {
const { id } = updatedDocument;
const updateChildren = (documents: NavigationNode[]) => {
return Promise.all(
const updateChildren = (documents: NavigationNode[]) =>
Promise.all(
documents.map(async (document) => {
if (document.id === id) {
document = {
@@ -577,7 +575,6 @@ class Collection extends ParanoidModel {
return document;
})
);
};
this.documentStructure = await updateChildren(this.documentStructure);
// Sequelize doesn't seem to set the value with splice on JSONB field
@@ -619,8 +616,8 @@ class Collection extends ParanoidModel {
);
} else {
// Recursively place document
const placeDocument = (documentList: NavigationNode[]) => {
return documentList.map((childDocument) => {
const placeDocument = (documentList: NavigationNode[]) =>
documentList.map((childDocument) => {
if (document.parentDocumentId === childDocument.id) {
childDocument.children.splice(
index !== undefined ? index : childDocument.children.length,
@@ -633,7 +630,6 @@ class Collection extends ParanoidModel {
return childDocument;
});
};
this.documentStructure = placeDocument(this.documentStructure);
}

View File

@@ -668,8 +668,8 @@ class Document extends ParanoidModel {
};
// Delete a document, archived or otherwise.
delete = (userId: string) => {
return this.sequelize.transaction(async (transaction: Transaction) => {
delete = (userId: string) =>
this.sequelize.transaction(async (transaction: Transaction) => {
if (!this.archivedAt && !this.template && this.collectionId) {
// delete any children and remove from the document structure
const collection = await Collection.findByPk(this.collectionId, {
@@ -699,11 +699,8 @@ class Document extends ParanoidModel {
);
return this;
});
};
getTimestamp = () => {
return Math.round(new Date(this.updatedAt).getTime() / 1000);
};
getTimestamp = () => Math.round(new Date(this.updatedAt).getTime() / 1000);
getSummary = () => {
const plainText = DocumentHelper.toPlainText(this);

View File

@@ -34,39 +34,31 @@ import Fix from "./decorators/Fix";
],
}))
@Scopes(() => ({
withCollectionPermissions: (userId: string) => {
return {
include: [
{
model: Document.scope("withDrafts"),
paranoid: true,
as: "document",
include: [
{
attributes: [
"id",
"permission",
"sharing",
"teamId",
"deletedAt",
],
model: Collection.scope({
method: ["withMembership", userId],
}),
as: "collection",
},
],
},
{
association: "user",
paranoid: false,
},
{
association: "team",
},
],
};
},
withCollectionPermissions: (userId: string) => ({
include: [
{
model: Document.scope("withDrafts"),
paranoid: true,
as: "document",
include: [
{
attributes: ["id", "permission", "sharing", "teamId", "deletedAt"],
model: Collection.scope({
method: ["withMembership", userId],
}),
as: "collection",
},
],
},
{
association: "user",
paranoid: false,
},
{
association: "team",
},
],
}),
}))
@Table({ tableName: "shares", modelName: "share" })
@Fix

View File

@@ -202,9 +202,8 @@ class Team extends ParanoidModel {
* @param fallback An optional fallback value, defaults to false.
* @returns The preference value if set, else undefined
*/
public getPreference = (preference: TeamPreference, fallback = false) => {
return this.preferences?.[preference] ?? fallback;
};
public getPreference = (preference: TeamPreference, fallback = false) =>
this.preferences?.[preference] ?? fallback;
provisionFirstCollection = async (userId: string) => {
await this.sequelize!.transaction(async (transaction) => {

View File

@@ -286,13 +286,8 @@ class User extends ParanoidModel {
* @param type The type of notification event
* @returns The current preference
*/
public subscribedToEventType = (type: NotificationEventType) => {
return (
this.notificationSettings[type] ??
NotificationEventDefaults[type] ??
false
);
};
public subscribedToEventType = (type: NotificationEventType) =>
this.notificationSettings[type] ?? NotificationEventDefaults[type] ?? false;
/**
* User flags are for storing information on a user record that is not visible
@@ -321,9 +316,7 @@ class User extends ParanoidModel {
* @param flag The flag to retrieve
* @returns The flag value
*/
public getFlag = (flag: UserFlag) => {
return this.flags?.[flag] ?? 0;
};
public getFlag = (flag: UserFlag) => this.flags?.[flag] ?? 0;
/**
* User flags are for storing information on a user record that is not visible
@@ -367,9 +360,8 @@ class User extends ParanoidModel {
* @param fallback An optional fallback value, defaults to false.
* @returns The preference value if set, else undefined
*/
public getPreference = (preference: UserPreference, fallback = false) => {
return this.preferences?.[preference] ?? fallback;
};
public getPreference = (preference: UserPreference, fallback = false) =>
this.preferences?.[preference] ?? fallback;
collectionIds = async (options = {}) => {
const collectionStubs = await Collection.scope({
@@ -448,8 +440,8 @@ class User extends ParanoidModel {
* @param expiresAt The time the token will expire at
* @returns The session token
*/
getJwtToken = (expiresAt?: Date) => {
return JWT.sign(
getJwtToken = (expiresAt?: Date) =>
JWT.sign(
{
id: this.id,
expiresAt: expiresAt ? expiresAt.toISOString() : undefined,
@@ -457,7 +449,6 @@ class User extends ParanoidModel {
},
this.jwtSecret
);
};
/**
* Returns a temporary token that is only used for transferring a session
@@ -466,8 +457,8 @@ class User extends ParanoidModel {
*
* @returns The transfer token
*/
getTransferToken = () => {
return JWT.sign(
getTransferToken = () =>
JWT.sign(
{
id: this.id,
createdAt: new Date().toISOString(),
@@ -476,7 +467,6 @@ class User extends ParanoidModel {
},
this.jwtSecret
);
};
/**
* Returns a temporary token that is only used for logging in from an email
@@ -484,8 +474,8 @@ class User extends ParanoidModel {
*
* @returns The email signin token
*/
getEmailSigninToken = () => {
return JWT.sign(
getEmailSigninToken = () =>
JWT.sign(
{
id: this.id,
createdAt: new Date().toISOString(),
@@ -493,15 +483,14 @@ class User extends ParanoidModel {
},
this.jwtSecret
);
};
/**
* Returns a list of teams that have a user matching this user's email.
*
* @returns A promise resolving to a list of teams
*/
availableTeams = async () => {
return Team.findAll({
availableTeams = async () =>
Team.findAll({
include: [
{
model: this.constructor as typeof User,
@@ -510,7 +499,6 @@ class User extends ParanoidModel {
},
],
});
};
demote = async (to: UserRole, options?: SaveOptions<User>) => {
const res = await (this.constructor as typeof User).findAndCountAll({
@@ -560,12 +548,11 @@ class User extends ParanoidModel {
}
};
promote = () => {
return this.update({
promote = () =>
this.update({
isAdmin: true,
isViewer: false,
});
};
// hooks

View File

@@ -192,9 +192,8 @@ export default class DocumentHelper {
const dom = new JSDOM(html);
const doc = dom.window.document;
const containsDiffElement = (node: Element | null) => {
return node && node.innerHTML.includes("data-operation-index");
};
const containsDiffElement = (node: Element | null) =>
node && node.innerHTML.includes("data-operation-index");
// We use querySelectorAll to get a static NodeList as we'll be modifying
// it as we iterate, rather than getting content.childNodes.

View File

@@ -90,7 +90,7 @@ export default class ProsemirrorHelper {
: "article";
const rtl = isRTL(node.textContent);
const content = <div id="content" className="ProseMirror"></div>;
const content = <div id="content" className="ProseMirror" />;
const children = (
<>
{options?.title && <h1 dir={rtl ? "rtl" : "ltr"}>{options.title}</h1>}

View File

@@ -8,7 +8,6 @@ export default class BacklinksProcessor extends BaseProcessor {
static applicableEvents: Event["name"][] = [
"documents.publish",
"documents.update",
//"documents.title_change",
"documents.delete",
];
@@ -90,17 +89,6 @@ export default class BacklinksProcessor extends BaseProcessor {
break;
}
case "documents.title_change": {
// might as well check
const { title, previousTitle } = event.data;
if (!previousTitle || title === previousTitle) {
break;
}
// TODO: Handle re-writing of titles into CRDT
break;
}
case "documents.delete": {
await Backlink.destroy({
where: {

View File

@@ -5,13 +5,11 @@ import { getTestServer } from "@server/test/support";
const mockTeamInSessionId = "1e023d05-951c-41c6-9012-c9fa0402e1c3";
jest.mock("@server/utils/authentication", () => {
return {
getSessionsInCookie() {
return { [mockTeamInSessionId]: {} };
},
};
});
jest.mock("@server/utils/authentication", () => ({
getSessionsInCookie() {
return { [mockTeamInSessionId]: {} };
},
}));
const server = getTestServer();

View File

@@ -567,16 +567,16 @@ router.post(
}).findByPk(id);
authorize(user, "read", collection);
const fileOperation = await sequelize.transaction(async (transaction) => {
return collectionExporter({
const fileOperation = await sequelize.transaction(async (transaction) =>
collectionExporter({
collection,
user,
team,
format,
ip: ctx.request.ip,
transaction,
});
});
})
);
ctx.body = {
success: true,
@@ -599,15 +599,15 @@ router.post(
assertIn(format, Object.values(FileOperationFormat), "Invalid format");
const fileOperation = await sequelize.transaction(async (transaction) => {
return collectionExporter({
const fileOperation = await sequelize.transaction(async (transaction) =>
collectionExporter({
user,
team,
format,
ip: ctx.request.ip,
transaction,
});
});
})
);
ctx.body = {
success: true,

View File

@@ -1300,8 +1300,8 @@ router.post(
authorize(user, "read", templateDocument);
}
const document = await sequelize.transaction(async (transaction) => {
return documentCreator({
const document = await sequelize.transaction(async (transaction) =>
documentCreator({
title,
text,
publish,
@@ -1313,8 +1313,8 @@ router.post(
editorVersion,
ip: ctx.request.ip,
transaction,
});
});
})
);
document.collection = collection;

View File

@@ -58,12 +58,10 @@ router.post(
authorize(user, "createTeam", existingTeam);
const authenticationProviders = existingTeam.authenticationProviders.map(
(provider) => {
return {
name: provider.name,
providerId: provider.providerId,
};
}
(provider) => ({
name: provider.name,
providerId: provider.providerId,
})
);
invariant(

View File

@@ -6,8 +6,8 @@ import { User, Document, Collection, Team } from "@server/models";
import onerror from "@server/onerror";
import webService from "@server/services/web";
export const seed = async () => {
return sequelize.transaction(async (transaction) => {
export const seed = async () =>
sequelize.transaction(async (transaction) => {
const team = await Team.create(
{
name: "Team",
@@ -97,7 +97,6 @@ export const seed = async () => {
team,
};
});
};
export function getTestServer() {
const app = webService();

View File

@@ -30,8 +30,8 @@ export function initI18n() {
i18n.use(backend).init({
compatibilityJSON: "v3",
backend: {
loadPath: (language: string) => {
return path.resolve(
loadPath: (language: string) =>
path.resolve(
path.join(
__dirname,
"..",
@@ -42,8 +42,7 @@ export function initI18n() {
unicodeBCP47toCLDR(language),
"translation.json"
)
);
},
),
},
preload: languages.map(unicodeCLDRtoBCP47),
interpolation: {

View File

@@ -1,5 +1,4 @@
export const opensearchResponse = (baseUrl: string): string => {
return `
export const opensearchResponse = (baseUrl: string): string => `
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
<ShortName>Outline</ShortName>
<Description>Search Outline</Description>
@@ -9,4 +8,3 @@ export const opensearchResponse = (baseUrl: string): string => {
<moz:SearchForm>${baseUrl}/search</moz:SearchForm>
</OpenSearchDescription>
`;
};

View File

@@ -23,14 +23,12 @@ if (isProduction) {
const returnFileAndImportsFromManifest = (
manifest: ManifestStructure,
file: string
): string[] => {
return [
manifest[file]["file"],
...manifest[file]["imports"].map((entry: string) => {
return manifest[entry]["file"];
}),
];
};
): string[] => [
manifest[file]["file"],
...manifest[file]["imports"].map(
(entry: string) => manifest[entry]["file"]
),
];
Array.from([
...returnFileAndImportsFromManifest(manifest, "app/index.tsx"),

View File

@@ -150,14 +150,13 @@ export const uploadToS3FromUrl = async (
}
};
export const deleteFromS3 = (key: string) => {
return s3
export const deleteFromS3 = (key: string) =>
s3
.deleteObject({
Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key,
})
.promise();
};
export const getSignedUrl = async (key: string, expiresInMs = 60) => {
const isDocker = AWS_S3_UPLOAD_BUCKET_URL.match(/http:\/\/s3:/);