Skip to content

Commit

Permalink
feat(client): secondary sort tags by usage
Browse files Browse the repository at this point in the history
- add locally computed field `noteCount` on `Tag`
  • Loading branch information
neopostmodern committed Dec 3, 2022
1 parent 7f1ed24 commit 09f0e7c
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 35 deletions.
4 changes: 3 additions & 1 deletion client/codegen.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
overwrite: true
schema: "../schema.graphql"
schema:
- "../schema.graphql"
- "./local-schema.graphql"
documents: "src/**/*.{ts,tsx}"
config:
nonOptionalTypename: true
Expand Down
3 changes: 3 additions & 0 deletions client/local-schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extend type Tag {
noteCount: Int!
}
14 changes: 14 additions & 0 deletions client/src/renderer/apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ const cache = new InMemoryCache({
INote: ['Link', 'Text'],
},
typePolicies: {
Tag: {
fields: {
noteCount: {
read(_, args) {
const notesWithTag: readonly unknown[] | undefined =
args.readField('notes');
if (notesWithTag && 'length' in notesWithTag) {
return notesWithTag.length;
}
return 0;
},
},
},
},
Query: {
fields: {
link(_, { args: { linkId }, toReference }) {
Expand Down
23 changes: 11 additions & 12 deletions client/src/renderer/components/AddTagForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,24 @@ import {
AddTagByNameToNoteMutation,
AddTagByNameToNoteMutationVariables,
TagsQuery,
TagsWithCountsQuery,
} from '../generated/graphql';
import useIsOnline from '../hooks/useIsOnline';
import makeMousetrapGlobal from '../utils/mousetrapGlobal';
import { BASE_TAG_FRAGMENT } from '../utils/sharedQueriesAndFragments';
import {
ADD_TAG_BY_NAME_TO_NOTE_MUTATION,
BASE_TAG_FRAGMENT,
} from '../utils/sharedQueriesAndFragments';
import { DisplayOnlyTag } from '../utils/types';
import useDataState, { DataState } from '../utils/useDataState';
import ErrorSnackbar from './ErrorSnackbar';
import InlineTagForm from './InlineTagForm';

export const ADD_TAG_MUTATION = gql`
mutation AddTagByNameToNote($noteId: ID!, $tagName: String!) {
addTagByNameToNote(noteId: $noteId, name: $tagName) {
... on INote {
_id
updatedAt
tags {
...BaseTag
}
}
export const TAGS_WITH_COUNTS_QUERY = gql`
query TagsWithCounts {
tags {
...BaseTag
noteCount @client
}
}
${BASE_TAG_FRAGMENT}
Expand All @@ -52,7 +51,7 @@ const AddTagForm = ({
const [submittedTag, setSubmittedTag] = useState<string | null>(null);

const tagsQuery = useDataState(
useQuery<TagsQuery>(TAGS_QUERY, {
useQuery<TagsWithCountsQuery>(TAGS_WITH_COUNTS_QUERY, {
fetchPolicy: 'cache-only',
})
);
Expand Down
72 changes: 52 additions & 20 deletions client/src/renderer/components/InlineTagForm.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { Autocomplete, TextField } from '@mui/material';
import { Autocomplete, TextField, Typography } from '@mui/material';
import { matchSorter } from 'match-sorter';
import React from 'react';
import { TagsQuery } from '../generated/graphql';
import { TagsWithCountsQuery } from '../generated/graphql';
import Tag from './Tag';

type TagType = TagsQuery['tags'][number];
type TagType = TagsWithCountsQuery['tags'][number];
type TagOrNewTagType = TagType | { newTagName: string; title: string };

type InlineTagFormProps = {
tags: 'loading' | TagsQuery['tags'];
tags: 'loading' | Array<TagType>;
onAddTag: (tagName: string) => void;
onAbort: () => void;
};

const MAX_TAG_SUGGESTIONS = 20;
const TAG_OVERFLOW = 'TAG_OVERFLOW';

const InlineTagForm: React.FC<InlineTagFormProps> = ({
onAddTag,
onAbort,
Expand Down Expand Up @@ -53,9 +56,21 @@ const InlineTagForm: React.FC<InlineTagFormProps> = ({
filterOptions={(options, params) => {
const { inputValue } = params;

const filtered = matchSorter(options, inputValue, {
keys: ['name'],
});
let filtered: Array<TagOrNewTagType> = matchSorter(
options as Array<TagType>,
inputValue,
{
keys: [(tag) => tag.name.replace(/[:›>]/g, ' ')],
baseSort: ({ item: tagA }, { item: tagB }) =>
Math.sign(tagB.noteCount - tagA.noteCount),
}
);

let hiddenCount = 0;
if (filtered.length > MAX_TAG_SUGGESTIONS) {
hiddenCount = filtered.length - MAX_TAG_SUGGESTIONS;
filtered = filtered.slice(0, MAX_TAG_SUGGESTIONS);
}

// Suggest the creation of a new value
const isExisting = options.some(
Expand All @@ -68,6 +83,13 @@ const InlineTagForm: React.FC<InlineTagFormProps> = ({
});
}

if (hiddenCount) {
filtered.push({
newTagName: TAG_OVERFLOW,
title: `${hiddenCount} more matches, refine your search`,
});
}

return filtered;
}}
getOptionLabel={(option) => {
Expand All @@ -92,19 +114,29 @@ const InlineTagForm: React.FC<InlineTagFormProps> = ({
renderOption={(props, tag) => {
let tagElement;
if ('newTagName' in tag) {
tagElement = (
<>
<div style={{ marginRight: '0.5em' }}>Create&nbsp;tag</div>
<Tag
onClick={() => {}}
tag={{
_id: 'NEW_TAG',
name: tag.newTagName,
color: 'gray',
}}
/>
</>
);
if (tag.newTagName === TAG_OVERFLOW) {
return (
<li className={props.className} style={{ cursor: 'initial' }}>
<Typography variant="caption" key={TAG_OVERFLOW}>
{tag.title}
</Typography>
</li>
);
} else {
tagElement = (
<>
<div style={{ marginRight: '0.5em' }}>Create&nbsp;tag</div>
<Tag
onClick={() => {}}
tag={{
_id: 'NEW_TAG',
name: tag.newTagName,
color: 'gray',
}}
/>
</>
);
}
} else {
tagElement = <Tag onClick={() => {}} tag={tag} />;
}
Expand Down
8 changes: 8 additions & 0 deletions client/src/renderer/containers/NotesPage/NotesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ export const NOTES_QUERY = gql`
query NotesForList {
notes {
...BaseNote
... on INote {
tags {
_id
name
color
}
}
}
}
${BASE_NOTE_FRAGMENT}
Expand Down
3 changes: 1 addition & 2 deletions client/src/renderer/utils/sharedQueriesAndFragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,12 @@ export const BASE_NOTE_FRAGMENT = gql`
description
tags {
...BaseTag
_id
}
}
... on Link {
url
domain
}
}
${BASE_TAG_FRAGMENT}
`;

0 comments on commit 09f0e7c

Please sign in to comment.