feat: Improved search results when finding links in document editor (#1573)
* feat: Improved search results when finding links in document editor * chore(deps): Bump RME for smoother link search
This commit is contained in:
parent
6f1f855083
commit
f1a95e5e79
@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import distanceInWordsToNow from "date-fns/distance_in_words_to_now";
|
||||
import invariant from "invariant";
|
||||
import { deburr, sortBy } from "lodash";
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@ -97,20 +98,26 @@ class DataLoader extends React.Component<Props> {
|
||||
}
|
||||
|
||||
// default search for anything that doesn't look like a URL
|
||||
const results = await this.props.documents.search(term);
|
||||
const results = await this.props.documents.searchTitles(term);
|
||||
|
||||
return results
|
||||
.filter((result) => result.document.title)
|
||||
.map((result) => {
|
||||
const time = distanceInWordsToNow(result.document.updatedAt, {
|
||||
return sortBy(
|
||||
results.map((document) => {
|
||||
const time = distanceInWordsToNow(document.updatedAt, {
|
||||
addSuffix: true,
|
||||
});
|
||||
return {
|
||||
title: result.document.title,
|
||||
title: document.title,
|
||||
subtitle: `Updated ${time}`,
|
||||
url: result.document.url,
|
||||
url: document.url,
|
||||
};
|
||||
});
|
||||
}),
|
||||
(document) =>
|
||||
deburr(document.title)
|
||||
.toLowerCase()
|
||||
.startsWith(deburr(term).toLowerCase())
|
||||
? -1
|
||||
: 1
|
||||
);
|
||||
};
|
||||
|
||||
onCreateLink = async (title: string) => {
|
||||
|
@ -312,12 +312,25 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
return this.fetchNamedPage("list", options);
|
||||
};
|
||||
|
||||
@action
|
||||
searchTitles = async (query: string, options: PaginationParams = {}) => {
|
||||
const res = await client.get("/documents.search_titles", {
|
||||
query,
|
||||
...options,
|
||||
});
|
||||
invariant(res && res.data, "Search response should be available");
|
||||
|
||||
// add the documents and associated policies to the store
|
||||
res.data.forEach(this.add);
|
||||
this.addPolicies(res.policies);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@action
|
||||
search = async (
|
||||
query: string,
|
||||
options: PaginationParams = {}
|
||||
): Promise<SearchResult[]> => {
|
||||
// $FlowFixMe
|
||||
const compactedOptions = omitBy(options, (o) => !o);
|
||||
const res = await client.get("/documents.search", {
|
||||
...compactedOptions,
|
||||
@ -464,7 +477,7 @@ export default class DocumentsStore extends BaseStore<Document> {
|
||||
{ key: "title", value: title },
|
||||
{ key: "publish", value: options.publish },
|
||||
{ key: "file", value: file },
|
||||
].map((info) => {
|
||||
].forEach((info) => {
|
||||
if (typeof info.value === "string" && info.value) {
|
||||
formData.append(info.key, info.value);
|
||||
}
|
||||
|
@ -140,7 +140,7 @@
|
||||
"react-portal": "^4.0.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-waypoint": "^9.0.2",
|
||||
"rich-markdown-editor": "^11.0.0-8",
|
||||
"rich-markdown-editor": "^11.0.0-9",
|
||||
"semver": "^7.3.2",
|
||||
"sequelize": "^6.3.4",
|
||||
"sequelize-cli": "^6.2.0",
|
||||
|
@ -551,6 +551,52 @@ router.post("documents.restore", auth(), async (ctx) => {
|
||||
};
|
||||
});
|
||||
|
||||
router.post("documents.search_titles", auth(), pagination(), async (ctx) => {
|
||||
const { query } = ctx.body;
|
||||
const { offset, limit } = ctx.state.pagination;
|
||||
const user = ctx.state.user;
|
||||
ctx.assertPresent(query, "query is required");
|
||||
|
||||
const collectionIds = await user.collectionIds();
|
||||
|
||||
const documents = await Document.scope(
|
||||
{
|
||||
method: ["withViews", user.id],
|
||||
},
|
||||
{
|
||||
method: ["withCollection", user.id],
|
||||
}
|
||||
).findAll({
|
||||
where: {
|
||||
title: {
|
||||
[Op.iLike]: `%${query}%`,
|
||||
},
|
||||
collectionId: collectionIds,
|
||||
archivedAt: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
},
|
||||
order: [["updatedAt", "DESC"]],
|
||||
include: [
|
||||
{ model: User, as: "createdBy", paranoid: false },
|
||||
{ model: User, as: "updatedBy", paranoid: false },
|
||||
],
|
||||
offset,
|
||||
limit,
|
||||
});
|
||||
|
||||
const policies = presentPolicies(user, documents);
|
||||
const data = await Promise.all(
|
||||
documents.map((document) => presentDocument(document))
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
pagination: ctx.state.pagination,
|
||||
data,
|
||||
policies,
|
||||
};
|
||||
});
|
||||
|
||||
router.post("documents.search", auth(), pagination(), async (ctx) => {
|
||||
const {
|
||||
query,
|
||||
|
@ -628,6 +628,56 @@ describe("#documents.drafts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.search_titles", () => {
|
||||
it("should return case insensitive results for partial query", async () => {
|
||||
const user = await buildUser();
|
||||
const document = await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
title: "Super secret",
|
||||
});
|
||||
|
||||
const res = await server.post("/api/documents.search_titles", {
|
||||
body: { token: user.getJwtToken(), query: "SECRET" },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(1);
|
||||
expect(body.data[0].id).toEqual(document.id);
|
||||
});
|
||||
|
||||
it("should not include archived or deleted documents", async () => {
|
||||
const user = await buildUser();
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
title: "Super secret",
|
||||
archivedAt: new Date(),
|
||||
});
|
||||
|
||||
await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
title: "Super secret",
|
||||
deletedAt: new Date(),
|
||||
});
|
||||
|
||||
const res = await server.post("/api/documents.search_titles", {
|
||||
body: { token: user.getJwtToken(), query: "SECRET" },
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("should require authentication", async () => {
|
||||
const res = await server.post("/api/documents.search_titles");
|
||||
expect(res.status).toEqual(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#documents.search", () => {
|
||||
it("should return results", async () => {
|
||||
const { user } = await seed();
|
||||
|
@ -9887,10 +9887,10 @@ retry-as-promised@^3.2.0:
|
||||
dependencies:
|
||||
any-promise "^1.3.0"
|
||||
|
||||
rich-markdown-editor@^11.0.0-8:
|
||||
version "11.0.0-8"
|
||||
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.0.0-8.tgz#542a33963147077a3c01ba2f689f67714056bbb9"
|
||||
integrity sha512-TTMVMr33CFlk+AkHKE2/nOZ4VRSwq0aOLodCYotztSwGAQ1a01znZmue1gn/VbIrF7/AtxZ/cnIs3p2hJr82bw==
|
||||
rich-markdown-editor@^11.0.0-9:
|
||||
version "11.0.0-9"
|
||||
resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.0.0-9.tgz#a7a4bfa09fca3cdf3168027e11fd9af46c708680"
|
||||
integrity sha512-B1q6VbRF/6yjHsYMQEXjgjwPJCSU3mNEmLGsJOF+PZACII5ojg8bV51jGd4W1rTvbIzqnLK4iPWlAbn+hrMtXw==
|
||||
dependencies:
|
||||
copy-to-clipboard "^3.0.8"
|
||||
lodash "^4.17.11"
|
||||
|
Reference in New Issue
Block a user