Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to also filter by category #105

Merged
merged 13 commits into from
Jun 6, 2021
1 change: 1 addition & 0 deletions client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ params.theme = attributes.theme;
params.reactionsEnabled = attributes.reactionsEnabled || '1';
params.repo = attributes.repo;
params.repoId = attributes.repoId;
params.category = attributes.category || '';
params.categoryId = attributes.categoryId;
params.description = ogDescriptionMeta ? ogDescriptionMeta.content : '';

Expand Down
4 changes: 2 additions & 2 deletions components/CommentBox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MarkdownIcon } from '@primer/octicons-react';
import { ChangeEvent, useCallback, useContext, useEffect, useState } from 'react';
import { adaptComment, adaptReply, handleCommentClick, processCommentBody } from '../lib/adapter';
import { AuthContext, getLoginUrl } from '../lib/context';
import { AuthContext } from '../lib/context';
import { IComment, IReply, IUser } from '../lib/types/adapter';
import { resizeTextArea } from '../lib/utils';
import { addDiscussionComment } from '../services/github/addDiscussionComment';
Expand Down Expand Up @@ -34,7 +34,7 @@ export default function CommentBox({
const [isLoading, setIsLoading] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isReplyOpen, setIsReplyOpen] = useState(false);
const { token, origin } = useContext(AuthContext);
const { token, origin, getLoginUrl } = useContext(AuthContext);
const loginUrl = getLoginUrl(origin);
const isReply = !!replyToId;

Expand Down
221 changes: 150 additions & 71 deletions components/Configuration.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
import { CheckIcon, ClippyIcon, SyncIcon, XIcon } from '@primer/octicons-react';
import { useEffect, useState } from 'react';
import { ReactNode, useEffect, useState } from 'react';
import { handleClipboardCopy } from '../lib/adapter';
import { useDebounce } from '../lib/hooks';
import { ICategory } from '../lib/types/adapter';
import { themeOptions } from '../lib/variables';
import { getCategories } from '../services/giscus/categories';

const mappingOptions = [
interface IDirectConfig {
theme: string;
reactionsEnabled: boolean;
}

interface IConfigurationProps {
directConfig: IDirectConfig;
onDirectConfigChange: (
key: keyof IDirectConfig,
value: IDirectConfig[keyof IDirectConfig],
) => void;
}

type Mapping = 'pathname' | 'url' | 'title' | 'og:title' | 'specific' | 'number';

interface IConfig {
repository: string;
repositoryId: string;
mapping: Mapping;
term: string;
category: string;
categoryId: string;
useCategory: boolean;
}

const mappingOptions: Array<{
value: Mapping;
label: ReactNode;
description: ReactNode;
}> = [
{
value: 'pathname',
label: (
Expand Down Expand Up @@ -92,35 +121,28 @@ function ClipboardCopy() {
);
}

interface DirectConfig {
theme: string;
reactionsEnabled: boolean;
}

interface ConfigurationProps {
directConfig: DirectConfig;
onDirectConfigChange: (key: keyof DirectConfig, value: DirectConfig[keyof DirectConfig]) => void;
}

export default function Configuration({ directConfig, onDirectConfigChange }: ConfigurationProps) {
const [repository, setRepository] = useState('');
const [repositoryId, setRepositoryId] = useState('');
const [categoryId, setCategoryId] = useState('');
export default function Configuration({ directConfig, onDirectConfigChange }: IConfigurationProps) {
const [config, setConfig] = useState<IConfig>({
repository: '',
repositoryId: '',
mapping: 'pathname',
term: '',
category: '',
categoryId: '',
useCategory: true,
});
const [error, setError] = useState(false);
const [categories, setCategories] = useState<ICategory[]>([]);
const [mapping, setMapping] = useState('pathname');
const [term, setTerm] = useState('');
const dRepository = useDebounce(repository);
const dRepository = useDebounce(config.repository);

useEffect(() => {
setError(false);
setRepositoryId('');
setCategoryId('');
setConfig((current) => ({ ...current, repositoryId: '', category: '', categoryId: '' }));
setCategories([]);
if (dRepository) {
getCategories(dRepository)
.then(({ repositoryId, categories }) => {
setRepositoryId(repositoryId);
setConfig((current) => ({ ...current, repositoryId }));
setCategories(categories);
})
.catch(() => {
Expand Down Expand Up @@ -165,22 +187,24 @@ export default function Configuration({ directConfig, onDirectConfigChange }: Co
</label>
<input
id="repository"
value={repository}
onChange={(event) => setRepository(event.target.value)}
value={config.repository}
onChange={(event) =>
setConfig((current) => ({ ...current, repository: event.target.value }))
}
type="text"
className="my-2 px-[12px] py-[5px] min-w-[75%] sm:min-w-[50%] form-control border rounded-md placeholder-gray-500"
placeholder="owner/repo"
/>

{error || (repositoryId && !categories.length) ? (
{error || (config.repositoryId && !categories.length) ? (
<>
<XIcon className="inline-block ml-2 color-text-danger" />
<p className="text-xs color-text-danger">
Cannot use giscus in this repository. Make sure all of the above criteria has been
met.
</p>
</>
) : repositoryId && categories.length ? (
) : config.repositoryId && categories.length ? (
<>
<CheckIcon className="inline-block ml-2 color-text-success" />
<p className="text-xs color-text-success">
Expand All @@ -189,7 +213,7 @@ export default function Configuration({ directConfig, onDirectConfigChange }: Co
</>
) : (
<>
{!error && !repositoryId && dRepository ? (
{!error && !config.repositoryId && dRepository ? (
<SyncIcon className="inline-block ml-2 animate-spin" />
) : null}
<p className="text-xs color-text-secondary">
Expand All @@ -200,32 +224,6 @@ export default function Configuration({ directConfig, onDirectConfigChange }: Co
)}
</fieldset>

<h3>Discussion Category</h3>
<p>
Choose the discussion category where new discussions will be created. This is only used for
discussion creation and <strong>does not</strong> affect how giscus searches for
discussions.
</p>
<select
name="category"
id="category"
disabled={!categories.length}
value={categoryId}
onChange={(event) => setCategoryId(event.target.value)}
className={`px-[12px] py-[5px] pr-6 min-w-[200px] border rounded-md appearance-none bg-no-repeat form-control form-select color-border-primary color-bg-primary${
!categoryId ? ' color-text-secondary' : ''
}`}
>
<option value="" disabled selected={!categoryId}>
{categories.length ? 'Pick a category' : 'No categories found'}
</option>
{categories.map(({ id, emoji, name }) => (
<option key={id} value={id} className="color-text-primary">
{emoji} {name}
</option>
))}
</select>

<h3>Page ↔️ Discussions Mapping</h3>
<p>Choose the mapping between the embedding page and the embedded discussion.</p>
<fieldset>
Expand All @@ -237,32 +235,100 @@ export default function Configuration({ directConfig, onDirectConfigChange }: Co
type="radio"
name="mapping"
value={value}
checked={mapping === value}
onChange={(event) => {
setTerm('');
setMapping(event.target.value);
}}
checked={config.mapping === value}
onChange={(event) =>
setConfig((current) => ({
...current,
term: '',
mapping: event.target.value as Mapping,
}))
}
/>
<label className="cursor-pointer" htmlFor={value}>
<strong>{label}</strong>
</label>
<p className="mb-0 text-xs color-text-secondary">{description}</p>
{['specific', 'number'].includes(mapping) && mapping === value ? (
{['specific', 'number'].includes(config.mapping) && config.mapping === value ? (
<input
id="term"
value={term}
onChange={(event) => setTerm(event.target.value)}
type={mapping === 'number' ? 'number' : 'text'}
value={config.term}
onChange={(event) =>
setConfig((current) => ({ ...current, term: event.target.value }))
}
type={config.mapping === 'number' ? 'number' : 'text'}
className="px-[12px] py-[5px] mt-4 form-control border rounded-md placeholder-gray-500 min-w-[75%] sm:min-w-[50%]"
placeholder={
mapping === 'number' ? 'Enter discussion number here' : 'Enter term here'
config.mapping === 'number' ? 'Enter discussion number here' : 'Enter term here'
}
/>
) : null}
</div>
))}
</fieldset>

<h3>Discussion Category</h3>
<p>
Choose the discussion category where new discussions will be created.{' '}
{config.mapping === 'number' ? (
<>
This feature is not supported if you use the <strong>specific discussion number</strong>{' '}
mapping.
</>
) : (
<>
It is recommended to use a category with the <strong>Announcements</strong> type so that
new discussions can only be created by maintainers and giscus.
</>
)}
</p>
<select
name="category"
id="category"
disabled={!categories.length || config.mapping === 'number'}
value={config.categoryId}
onChange={(event) =>
setConfig((current) => ({
...current,
category: event.target.selectedOptions[0]?.dataset.category,
categoryId: event.target.value,
}))
}
className={`px-[12px] py-[5px] pr-6 min-w-[200px] border rounded-md appearance-none bg-no-repeat form-control form-select color-border-primary color-bg-primary${
!config.categoryId ? ' color-text-secondary' : ''
}`}
>
<option value="" disabled selected={!config.categoryId} data-category="">
{config.mapping === 'number'
? 'Not supported'
: categories.length
? 'Pick a category'
: 'No categories found'}
</option>
{categories.map(({ id, emoji, name }) => (
<option key={id} value={id} className="color-text-primary" data-category={name}>
{emoji} {name}
</option>
))}
</select>
<div className="form-checkbox">
<input
disabled={config.mapping === 'number'}
type="checkbox"
id="useCategory"
checked={config.useCategory}
value={config.category}
onChange={(event) =>
setConfig((current) => ({ ...current, useCategory: event.target.checked }))
}
></input>
<label htmlFor="useCategory">
<strong>Only search for discussions in this category</strong>
</label>
<p className="mb-0 text-xs color-text-secondary">
When searching for a matching discussion, giscus will only search in this category.
</p>
</div>

<h3>Features</h3>
<p>Choose whether specific features should be enabled.</p>
<div className="form-checkbox">
Expand Down Expand Up @@ -320,21 +386,34 @@ export default function Configuration({ directConfig, onDirectConfigChange }: Co
<span className="pl-s">https://giscus.app/client.js</span>
{'"\n '}
<span className="pl-c1">data-repo</span>={'"'}
<span className="pl-s">{repository || '[ENTER REPO HERE]'}</span>
<span className="pl-s">{config.repository || '[ENTER REPO HERE]'}</span>
{'"\n '}
<span className="pl-c1">data-repo-id</span>={'"'}
<span className="pl-s">{repositoryId || '[ENTER REPO ID HERE]'}</span>
{'"\n '}
<span className="pl-c1">data-category-id</span>={'"'}
<span className="pl-s">{categoryId || '[ENTER CATEGORY ID HERE]'}</span>
<span className="pl-s">{config.repositoryId || '[ENTER REPO ID HERE]'}</span>
{'"\n '}
{config.mapping !== 'number' ? (
<>
{config.useCategory ? (
<>
<span className="pl-c1">data-category</span>={'"'}
<span className="pl-s">{config.category || '[ENTER CATEGORY NAME HERE]'}</span>
{'"\n '}
</>
) : null}
<span className="pl-c1">data-category-id</span>={'"'}
<span className="pl-s">{config.categoryId || '[ENTER CATEGORY ID HERE]'}</span>
{'"\n '}
</>
) : null}
<span className="pl-c1">data-mapping</span>={'"'}
<span className="pl-s">{mapping}</span>
<span className="pl-s">{config.mapping}</span>
{'"\n '}
{['specific', 'number'].includes(mapping) ? (
{['specific', 'number'].includes(config.mapping) ? (
<>
<span className="pl-c1">data-term</span>={'"'}
<span className="pl-s">{term || '[ENTER TERM HERE]'}</span>
<span className="pl-s">
{config.term || `[ENTER ${config.mapping === 'number' ? 'NUMBER' : 'TERM'} HERE]`}
</span>
{'"\n '}
</>
) : null}
Expand Down
18 changes: 4 additions & 14 deletions components/Giscus.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { useCallback, useContext } from 'react';
import { AuthContext } from '../lib/context';
import { AuthContext, ConfigContext } from '../lib/context';
import { Reactions, updateDiscussionReaction } from '../lib/reactions';
import { useDiscussions } from '../services/giscus/discussions';
import Comment from './Comment';
import CommentBox from './CommentBox';
import ReactButtons from './ReactButtons';

interface IGiscusProps {
repo: string;
term?: string;
number?: number;
reactionsEnabled: boolean;
onDiscussionCreateRequest?: () => Promise<string>;
onError?: (message: string) => void;
}

export default function Giscus({
repo,
term,
number,
reactionsEnabled,
onDiscussionCreateRequest,
onError,
}: IGiscusProps) {
export default function Giscus({ onDiscussionCreateRequest, onError }: IGiscusProps) {
const { token } = useContext(AuthContext);
const query = { repo, term, number };
const { repo, term, number, category, reactionsEnabled } = useContext(ConfigContext);
const query = { repo, term, category, number };

const backComments = useDiscussions(query, token, { last: 15 });
const {
Expand Down
4 changes: 2 additions & 2 deletions components/ReactButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SmileyIcon } from '@primer/octicons-react';
import { useCallback, useContext, useState } from 'react';
import { AuthContext, getLoginUrl } from '../lib/context';
import { AuthContext } from '../lib/context';
import { useComponentVisible } from '../lib/hooks';
import { IReactionGroups } from '../lib/types/adapter';
import { Reactions } from '../lib/reactions';
Expand Down Expand Up @@ -47,7 +47,7 @@ export default function ReactButtons({
const [current, setCurrent] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [ref, isOpen, setIsOpen] = useComponentVisible<HTMLDivElement>(false);
const { token, origin } = useContext(AuthContext);
const { token, origin, getLoginUrl } = useContext(AuthContext);
const loginUrl = getLoginUrl(origin);

const togglePopover = useCallback(() => setIsOpen(!isOpen), [isOpen, setIsOpen]);
Expand Down
Loading