beautiful all-topics and all-authors, some icons

This commit is contained in:
tonyrewin 2022-11-27 11:15:03 +03:00
parent ae91ce27df
commit 9b4cede871
14 changed files with 102 additions and 92 deletions

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M20.5,15.1a1,1,0,0,0-1.34.45A8,8,0,1,1,12,4a7.93,7.93,0,0,1,7.16,4.45,1,1,0,0,0,1.8-.9,10,10,0,1,0,0,8.9A1,1,0,0,0,20.5,15.1ZM21,11H11.41l2.3-2.29a1,1,0,1,0-1.42-1.42l-4,4a1,1,0,0,0-.21.33,1,1,0,0,0,0,.76,1,1,0,0,0,.21.33l4,4a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42L11.41,13H21a1,1,0,0,0,0-2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12.59,13l-2.3,2.29a1,1,0,0,0,0,1.42,1,1,0,0,0,1.42,0l4-4a1,1,0,0,0,.21-.33,1,1,0,0,0,0-.76,1,1,0,0,0-.21-.33l-4-4a1,1,0,1,0-1.42,1.42L12.59,11H3a1,1,0,0,0,0,2ZM12,2A10,10,0,0,0,3,7.55a1,1,0,0,0,1.8.9A8,8,0,1,1,12,20a7.93,7.93,0,0,1-7.16-4.45,1,1,0,0,0-1.8.9A10,10,0,1,0,12,2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@ -1,3 +1,4 @@
<svg width="14" height="19" viewBox="0 0 14 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H14V18.6667L7 14.9333L0 18.6667V0ZM2 2V15.3333L7 12.6667L12 15.3333V2H2Z" fill="#141414"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M16,2H8A3,3,0,0,0,5,5V21a1,1,0,0,0,.5.87,1,1,0,0,0,1,0L12,18.69l5.5,3.18A1,1,0,0,0,18,22a1,1,0,0,0,.5-.13A1,1,0,0,0,19,21V5A3,3,0,0,0,16,2Zm1,17.27-4.5-2.6a1,1,0,0,0-1,0L7,19.27V5A1,1,0,0,1,8,4h8a1,1,0,0,1,1,1Z" />
</svg>

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 304 B

View File

@ -1,3 +1,4 @@
<svg width="14" height="19" viewBox="0 0 14 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H14V18.6667L7 14.9333L0 18.6667V0ZM2 2V15.3333L7 12.6667L12 15.3333V2H2Z" fill="#141414"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#141414"
d="M16,2H8A3,3,0,0,0,5,5V21a1,1,0,0,0,.5.87,1,1,0,0,0,1,0L12,18.69l5.5,3.18A1,1,0,0,0,18,22a1,1,0,0,0,.5-.13A1,1,0,0,0,19,21V5A3,3,0,0,0,16,2Zm1,17.27-4.5-2.6a1,1,0,0,0-1,0L7,19.27V5A1,1,0,0,1,8,4h8a1,1,0,0,1,1,1Z" />
</svg>

Before

Width:  |  Height:  |  Size: 247 B

After

Width:  |  Height:  |  Size: 359 B

View File

@ -122,7 +122,7 @@ export default (props: {
containerCssClass={stylesHeader.control}
trigger={
<button class={clsx(styles.commentControl, styles.commentControlShare)}>
<Icon name="share" class={styles.icon} />
<Icon name="share-new" class={styles.icon} />
{t('Share')}
</button>
}

View File

@ -5,20 +5,28 @@ import { t } from '../../utils/intl'
import { showModal } from '../../stores/ui'
import styles from '../../styles/Article.module.scss'
import { useReactionsStore } from '../../stores/zine/reactions'
import { createEffect, createMemo, createSignal, onMount, Suspense } from 'solid-js'
import { createMemo, createSignal, onMount } from 'solid-js'
import type { Reaction } from '../../graphql/types.gen'
import { clsx } from 'clsx'
import { byCreated, byStat } from '../../utils/sortby'
import { Loading } from '../Loading'
const ARTICLE_COMMENTS_PAGE_SIZE = 50
const MAX_COMMENT_LEVEL = 6
export const CommentsTree = (props: { shout: string; reactions?: Reaction[] }) => {
const [getCommentsPage, setCommentsPage] = createSignal(0)
const [commentsOrder, setCommentsOrder] = createSignal<'rating' | 'createdAt'>('createdAt')
const [isCommentsLoading, setIsCommentsLoading] = createSignal(false)
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { session } = useSession()
const { sortedReactions, loadReactionsBy } = useReactionsStore({ reactions: props.reactions })
const reactions = createMemo<Reaction[]>(() => sortedReactions()) // .filter(r => r.shout.slug === props.shout) )
const reactions = createMemo<Reaction[]>(() =>
sortedReactions()
.sort(commentsOrder() === 'rating' ? byStat('rating') : byCreated)
.filter((r) => r.shout.slug === props.shout)
)
const loadMore = async () => {
try {
const page = getCommentsPage()
@ -44,29 +52,38 @@ export const CommentsTree = (props: { shout: string; reactions?: Reaction[] }) =
onMount(async () => await loadMore())
return (
<>
<Show when={reactions()}>
<Show when={!isCommentsLoading()} fallback={<Loading />}>
<div class={styles.commentsHeaderWrapper}>
<h2 id="comments" class={styles.commentsHeader}>
{t('Comments')} {reactions().length.toString() || ''}
</h2>
<ul class={clsx(styles.commentsViewSwitcher, 'view-switcher')}>
<li class="selected">
<a href="#">По порядку</a>
<li classList={{ selected: commentsOrder() === 'createdAt' || !commentsOrder() }}>
<a
href="#"
onClick={(ev) => {
ev.preventDefault()
setCommentsOrder('createdAt')
}}
>
По порядку
</a>
</li>
<li>
<a href="#">По рейтингу</a>
<li classList={{ selected: commentsOrder() === 'rating' }}>
<a
href="#"
onClick={(ev) => {
ev.preventDefault()
setCommentsOrder('rating')
}}
>
По рейтингу
</a>
</li>
</ul>
</div>
<form class={styles.commentForm}>
<div class="pretty-form__item">
<input type="text" id="new-comment" placeholder="Коментарий" />
<label for="new-comment">Коментарий</label>
</div>
</form>
<For each={reactions()}>
{(reaction: Reaction) => (
<Comment
@ -84,7 +101,14 @@ export const CommentsTree = (props: { shout: string; reactions?: Reaction[] }) =
<Show
when={!session()?.user?.slug}
fallback={<textarea class={styles.writeComment} rows="1" placeholder={t('Write comment')} />}
fallback={
<form class={styles.commentForm}>
<div class="pretty-form__item">
<input type="text" id="new-comment" placeholder={t('Write comment')} />
<label for="new-comment">{t('Write comment')}</label>
</div>
</form>
}
>
<div class={styles.commentWarning} id="comments">
{t('To leave a comment you please')}

View File

@ -99,7 +99,7 @@ export const FullArticle = (props: ArticleProps) => {
setIsSharePopupVisible(isVisible)
}}
containerCssClass={stylesHeader.control}
trigger={<Icon name="share" class={styles.icon} />}
trigger={<Icon name="share-new" class={styles.icon} />}
/>
</div>
<div class={styles.shoutStatsItem}>

View File

@ -37,14 +37,14 @@ export const Beside = (props: BesideProps) => {
<Show when={props.wrapper === 'author'}>
<a href="/authors">
{t('All authors')}
<Icon name="arrow-right" class={styles.icon} />
<Icon name="arrow-circle-right" class={styles.icon} />
</a>
</Show>
<Show when={props.wrapper === 'topic'}>
<a href="/topics">
{t('All topics')}
<Icon name="arrow-right" class={styles.icon} />
<Icon name="arrow-circle-right" class={styles.icon} />
</a>
</Show>
</div>

View File

@ -12,7 +12,10 @@
padding: 0 divide($container-padding-x, 2);
}
}
.icon {
height: 1.2em;
width: 1.2em;
}
a:hover {
.icon {
filter: invert(1);

View File

@ -127,7 +127,7 @@ export const Header = (props: Props) => {
setIsSharePopupVisible(isVisible)
}}
containerCssClass={styles.control}
trigger={<Icon name="share-outline" class={styles.icon} />}
trigger={<Icon name="share-new" class={styles.icon} />}
/>
<a href={getPagePath(router, 'inbox')} class={styles.control}>
<Icon name="comments-outline" class={styles.icon} />

View File

@ -41,7 +41,8 @@ export const AllAuthorsView = (props: Props) => {
}
})
createEffect(() => {
setAuthorsSort(searchParams().by || 'name')
setAuthorsSort(searchParams().by || 'shouts')
setFilteredAuthors(sortedAuthors())
setLimit(PAGE_SIZE)
})
@ -81,23 +82,23 @@ export const AllAuthorsView = (props: Props) => {
<a href="/authors?by=name">{t('By name')}</a>
</li>
<li class="view-switcher__search">
<SearchField onChange={searchAuthors} />
<li class="view-switcher__search">
<SearchField onChange={filterAuthors} />
</li>
</li>
</ul>
</div>
</div>
)
const [searchResults, setSearchResults] = createSignal<Author[]>([])
const [filteredAuthors, setFilteredAuthors] = createSignal<Author[]>([])
// eslint-disable-next-line sonarjs/cognitive-complexity
const searchAuthors = (value) => {
const filterAuthors = (value) => {
/* very stupid search algorithm with no deps */
let q = value.toLowerCase()
if (q.length > 0) {
console.debug(q)
setSearchResults([])
setFilteredAuthors([])
if (locale() === 'ru') q = translit(q, 'ru')
const aaa: Author[] = []
const aaa: Author[] = sortedAuthors()
sortedAuthors().forEach((a) => {
let flag = false
a.slug.split('-').forEach((w) => {
@ -112,16 +113,17 @@ export const AllAuthorsView = (props: Props) => {
})
}
if (flag && !aaa.includes(a)) aaa.push(a)
if (!flag && aaa.includes(a)) {
const idx = aaa.indexOf(a)
aaa.splice(idx, 1)
}
})
setSearchResults((sr: Author[]) => [...sr, ...aaa])
changeSearchParam('by', '')
setFilteredAuthors(aaa)
}
}
return (
<div class={clsx(styles.allTopicsPage, 'wide-container')}>
<Show when={sortedAuthors().length > 0 || searchResults().length > 0}>
<Show when={sortedAuthors().length > 0 || filteredAuthors().length > 0}>
<div class="shift-content">
<AllAuthorsHead />
@ -174,29 +176,10 @@ export const AllAuthorsView = (props: Props) => {
</For>
</Show>
<Show when={searchResults().length > 0}>
<For each={searchResults().slice(0, limit())}>
{(author) => (
<>
<AuthorCard
author={author}
compact={false}
hasLink={true}
subscribed={subscribed(author.slug)}
noSocialButtons={true}
isAuthorsList={true}
truncateBio={true}
/>
<StatMetrics fields={['shouts', 'followers', 'comments']} stat={author.stat} />
</>
)}
</For>
</Show>
<Show when={searchParams().by && searchParams().by !== 'name'}>
<div class={clsx(styles.stats, 'row')}>
<div class="col-lg-10 col-xl-9">
<For each={sortedAuthors().slice(0, limit())}>
<For each={filteredAuthors().slice(0, limit())}>
{(author) => (
<>
<AuthorCard
@ -216,7 +199,7 @@ export const AllAuthorsView = (props: Props) => {
</div>
</Show>
<Show when={searchParams().by !== 'name' && sortedAuthors().length > limit()}>
<Show when={sortedAuthors().length > limit() && searchParams().by !== 'name'}>
<div class="row">
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>

View File

@ -43,6 +43,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
})
createEffect(() => {
setTopicsSort(searchParams().by || 'shouts')
setFilterResults(sortedTopics())
setLimit(PAGE_SIZE)
})
@ -65,17 +66,16 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
const subscribed = (s) => Boolean(session()?.news?.topics && session()?.news?.topics?.includes(s || ''))
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
const [searchResults, setSearchResults] = createSignal<Topic[]>([])
const [filterResults, setFilterResults] = createSignal<Topic[]>([])
// eslint-disable-next-line sonarjs/cognitive-complexity
const searchTopics = (value) => {
/* very stupid search algorithm with no deps */
const filterTopics = (value) => {
/* very stupid filter by string algorithm with no deps */
let q = value.toLowerCase()
if (q.length > 0) {
console.debug(q)
setSearchResults([])
setFilterResults([])
if (locale() === 'ru') q = translit(q, 'ru')
const ttt: Topic[] = []
const ttt: Topic[] = sortedTopics()
sortedTopics().forEach((topic) => {
let flag = false
topic.slug.split('-').forEach((w) => {
@ -90,11 +90,12 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
})
}
if (flag && !ttt.includes(topic)) ttt.push(topic)
if (!flag && ttt.includes(topic)) {
const idx = ttt.indexOf(topic)
ttt.splice(idx, 1)
}
})
setSearchResults((sr: Topic[]) => [...sr, ...ttt])
changeSearchParam('by', '')
setFilterResults(ttt)
}
}
@ -114,9 +115,11 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
<li classList={{ selected: searchParams().by === 'title' }}>
<a href="/topics?by=title">{t('By title')}</a>
</li>
<Show when={searchParams().by !== 'title'}>
<li class="view-switcher__search">
<SearchField onChange={searchTopics} />
<SearchField onChange={filterTopics} />
</li>
</Show>
</ul>
</div>
</div>
@ -127,7 +130,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
<div class="shift-content">
<AllTopicsHead />
<Show when={sortedTopics().length > 0 || searchResults().length > 0}>
<Show when={filterResults().length > 0}>
<Show when={searchParams().by === 'title'}>
<div class="col-lg-10 col-xl-9">
<ul class={clsx('nodash', styles.alphabet)}>
@ -173,21 +176,8 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
</For>
</Show>
<Show when={searchResults().length > 1}>
<For each={searchResults().slice(0, limit())}>
{(topic) => (
<TopicCard
topic={topic}
compact={false}
subscribed={subscribed(topic.slug)}
showPublications={true}
/>
)}
</For>
</Show>
<Show when={searchParams().by && searchParams().by !== 'title'}>
<For each={sortedTopics().slice(0, limit())}>
<For each={filterResults().slice(0, limit())}>
{(topic) => (
<>
<TopicCard
@ -202,7 +192,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
</For>
</Show>
<Show when={sortedTopics().length > limit()}>
<Show when={sortedTopics().length > limit() && searchParams().by !== 'title'}>
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10 offset-md-1')}>
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
{t('Load more')}

View File

@ -12,9 +12,9 @@ export default gql`
# id
# kind
#}
#shout {
# slug
#}
shout {
slug
}
createdBy {
name
slug

View File

@ -139,6 +139,7 @@ img {
vertical-align: baseline;
.icon {
height: 1.5em;
display: inline-block;
margin-right: 0.2em;
transition: filter 0.2s;
@ -183,6 +184,7 @@ img {
.icon {
opacity: 0.4;
height: 2rem;
}
@include media-breakpoint-down(sm) {