Merge branch 'high'

This commit is contained in:
keyan 2022-02-03 16:40:47 -06:00
commit 80b8744b8e
19 changed files with 1320 additions and 104 deletions

View File

@ -11,7 +11,7 @@ async function comments (models, id, sort) {
switch (sort) { switch (sort) {
case 'top': case 'top':
orderBy = 'ORDER BY x.sats DESC NULLS LAST' orderBy = 'ORDER BY x.sats DESC NULLS LAST'
join = LEFT_JOIN_SATS join = LEFT_JOIN_WEIGHTED_SATS
break break
case 'recent': case 'recent':
orderBy = 'ORDER BY "Item".created_at DESC' orderBy = 'ORDER BY "Item".created_at DESC'
@ -19,7 +19,7 @@ async function comments (models, id, sort) {
break break
default: default:
orderBy = ORDER_BY_SATS orderBy = ORDER_BY_SATS
join = LEFT_JOIN_SATS join = LEFT_JOIN_WEIGHTED_SATS
break break
} }
@ -109,7 +109,7 @@ export default {
items = await models.$queryRaw(` items = await models.$queryRaw(`
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
${timedLeftJoinSats(1)} ${timedLeftJoinWeightedSats(1)}
WHERE "parentId" IS NULL AND created_at <= $1 AND created_at > $3 WHERE "parentId" IS NULL AND created_at <= $1 AND created_at > $3
AND "pinId" IS NULL AND "pinId" IS NULL
${timedOrderBySats(1)} ${timedOrderBySats(1)}
@ -121,7 +121,7 @@ export default {
items = await models.$queryRaw(` items = await models.$queryRaw(`
${SELECT} ${SELECT}
FROM "Item" FROM "Item"
${timedLeftJoinSats(1)} ${timedLeftJoinWeightedSats(1)}
WHERE "parentId" IS NULL AND created_at <= $1 WHERE "parentId" IS NULL AND created_at <= $1
AND "pinId" IS NULL AND "pinId" IS NULL
${timedOrderBySats(1)} ${timedOrderBySats(1)}
@ -143,17 +143,6 @@ export default {
) rank_filter WHERE RANK = 1`) ) rank_filter WHERE RANK = 1`)
} }
break break
case 'wot':
items = await models.$queryRaw(`
${SELECT}
FROM "Item"
${timedLeftJoinWeightedSats(1)}
WHERE "parentId" IS NULL AND created_at <= $1
AND "pinId" IS NULL
${timedOrderBySats(1)}
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
break
case 'top': case 'top':
items = await models.$queryRaw(` items = await models.$queryRaw(`
${SELECT} ${SELECT}
@ -290,6 +279,16 @@ export default {
must: { must: {
bool: { bool: {
should: [ should: [
{
// all terms are matched in fields
multi_match: {
query,
type: 'most_fields',
fields: ['title^20', 'text'],
minimum_should_match: '100%',
boost: 400
}
},
{ {
// all terms are matched in fields // all terms are matched in fields
multi_match: { multi_match: {
@ -299,7 +298,7 @@ export default {
fuzziness: 'AUTO', fuzziness: 'AUTO',
prefix_length: 3, prefix_length: 3,
minimum_should_match: '100%', minimum_should_match: '100%',
boost: 2 boost: 20
} }
}, },
{ {
@ -309,6 +308,7 @@ export default {
type: 'most_fields', type: 'most_fields',
fields: ['title^20', 'text'], fields: ['title^20', 'text'],
fuzziness: 'AUTO', fuzziness: 'AUTO',
prefix_length: 3,
minimum_should_match: '60%' minimum_should_match: '60%'
} }
} }
@ -325,6 +325,12 @@ export default {
} }
} }
} }
},
highlight: {
fields: {
title: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] },
text: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] }
}
} }
} }
}) })
@ -336,7 +342,16 @@ export default {
} }
} }
const items = sitems.body.hits.hits.map(e => e._source) // return highlights
const items = sitems.body.hits.hits.map(e => {
const item = e._source
item.searchTitle = (e.highlight.title && e.highlight.title[0]) || item.title
item.searchText = (e.highlight.text && e.highlight.text[0]) || item.text
return item
})
return { return {
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null, cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
items items
@ -730,12 +745,18 @@ function timedLeftJoinWeightedSats (num) {
) x ON "Item".id = x.id` ) x ON "Item".id = x.id`
} }
const LEFT_JOIN_SATS = const LEFT_JOIN_WEIGHTED_SATS =
`LEFT JOIN (${LEFT_JOIN_SATS_SELECT} `LEFT JOIN (${LEFT_JOIN_SATS_SELECT}
FROM "Item" i FROM "Item" i
JOIN "ItemAct" ON i.id = "ItemAct"."itemId" JOIN "ItemAct" ON i.id = "ItemAct"."itemId"
GROUP BY i.id) x ON "Item".id = x.id` GROUP BY i.id) x ON "Item".id = x.id`
// const LEFT_JOIN_SATS =
// `LEFT JOIN (${LEFT_JOIN_SATS_SELECT}
// FROM "Item" i
// JOIN "ItemAct" ON i.id = "ItemAct"."itemId"
// GROUP BY i.id) x ON "Item".id = x.id`
/* NOTE: because many items will have the same rank, we need to tie break with a unique field so pagination works */ /* NOTE: because many items will have the same rank, we need to tie break with a unique field so pagination works */
function timedOrderBySats (num) { function timedOrderBySats (num) {
return `ORDER BY (GREATEST(x.sats-1, 0)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.5) + return `ORDER BY (GREATEST(x.sats-1, 0)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.5) +

View File

@ -41,19 +41,33 @@ export default {
return me.name?.toUpperCase() === name?.toUpperCase() || !(await models.user.findUnique({ where: { name } })) return me.name?.toUpperCase() === name?.toUpperCase() || !(await models.user.findUnique({ where: { name } }))
}, },
topUsers: async (parent, { cursor, within }, { models, me }) => { topUsers: async (parent, { cursor, within, userType }, { models, me }) => {
const decodedCursor = decodeCursor(cursor) const decodedCursor = decodeCursor(cursor)
const users = await models.$queryRaw(` let users
SELECT users.name, users.created_at, sum("ItemAct".sats) as stacked if (userType === 'spent') {
users = await models.$queryRaw(`
SELECT users.name, users.created_at, sum("ItemAct".sats) as amount
FROM "ItemAct"
JOIN users on "ItemAct"."userId" = users.id
WHERE "ItemAct".created_at <= $1
${topClause(within)}
GROUP BY users.id, users.name
ORDER BY amount DESC NULLS LAST, users.created_at DESC
OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
} else {
users = await models.$queryRaw(`
SELECT users.name, users.created_at, sum("ItemAct".sats) as amount
FROM "ItemAct" FROM "ItemAct"
JOIN "Item" on "ItemAct"."itemId" = "Item".id JOIN "Item" on "ItemAct"."itemId" = "Item".id
JOIN users on "Item"."userId" = users.id JOIN users on "Item"."userId" = users.id
WHERE act <> 'BOOST' AND "ItemAct"."userId" <> users.id AND "ItemAct".created_at <= $1 WHERE act <> 'BOOST' AND "ItemAct"."userId" <> users.id AND "ItemAct".created_at <= $1
${topClause(within)} ${topClause(within)}
GROUP BY users.id, users.name GROUP BY users.id, users.name
ORDER BY stacked DESC NULLS LAST, users.created_at DESC ORDER BY amount DESC NULLS LAST, users.created_at DESC
OFFSET $2 OFFSET $2
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset) LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
}
return { return {
cursor: users.length === LIMIT ? nextCursorEncoded(decodedCursor) : null, cursor: users.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,

View File

@ -43,7 +43,9 @@ export default gql`
createdAt: String! createdAt: String!
updatedAt: String! updatedAt: String!
title: String title: String
searchTitle: String
url: String url: String
searchText: String
text: String text: String
parentId: Int parentId: Int
parent: Item parent: Item

View File

@ -6,7 +6,7 @@ export default gql`
user(name: String!): User user(name: String!): User
users: [User!] users: [User!]
nameAvailable(name: String!): Boolean! nameAvailable(name: String!): Boolean!
topUsers(cursor: String, within: String!): Users topUsers(cursor: String, within: String!, userType: String!): TopUsers
} }
type Users { type Users {
@ -14,6 +14,17 @@ export default gql`
users: [User!]! users: [User!]!
} }
type TopUsers {
cursor: String
users: [TopUser!]!
}
type TopUser {
name: String!
createdAt: String!
amount: Int!
}
extend type Mutation { extend type Mutation {
setName(name: String!): Boolean setName(name: String!): Boolean
setSettings(tipDefault: Int!): Boolean setSettings(tipDefault: Int!): Boolean

View File

@ -165,7 +165,7 @@ export default function Comment ({
: ( : (
<div className={styles.text}> <div className={styles.text}>
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}> <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>
{truncate ? truncateString(item.text) : item.text} {truncate ? truncateString(item.text) : item.searchText || item.text}
</Text> </Text>
</div> </div>
)} )}

View File

@ -42,9 +42,9 @@ function ItemEmbed ({ item }) {
return null return null
} }
function TopLevelItem ({ item, noReply }) { function TopLevelItem ({ item, noReply, ...props }) {
return ( return (
<Item item={item}> <Item item={item} {...props}>
{item.text && <ItemText item={item} />} {item.text && <ItemText item={item} />}
{item.url && <ItemEmbed item={item} />} {item.url && <ItemEmbed item={item} />}
{!noReply && <Reply parentId={item.id} replyOpen />} {!noReply && <Reply parentId={item.id} replyOpen />}
@ -53,7 +53,7 @@ function TopLevelItem ({ item, noReply }) {
} }
function ItemText ({ item }) { function ItemText ({ item }) {
return <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.text}</Text> return <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.searchText || item.text}</Text>
} }
export default function ItemFull ({ item, bio, ...props }) { export default function ItemFull ({ item, bio, ...props }) {

View File

@ -6,6 +6,13 @@ import { useEffect, useRef, useState } from 'react'
import Countdown from './countdown' import Countdown from './countdown'
import { NOFOLLOW_LIMIT } from '../lib/constants' import { NOFOLLOW_LIMIT } from '../lib/constants'
import Pin from '../svgs/pushpin-fill.svg' import Pin from '../svgs/pushpin-fill.svg'
import reactStringReplace from 'react-string-replace'
function SearchTitle ({ title }) {
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
return <mark key={`mark-${match}`}>{match}</mark>
})
}
export default function Item ({ item, rank, children }) { export default function Item ({ item, rank, children }) {
const mine = item.mine const mine = item.mine
@ -34,7 +41,9 @@ export default function Item ({ item, rank, children }) {
<div className={styles.hunk}> <div className={styles.hunk}>
<div className={`${styles.main} flex-wrap ${wrap ? 'd-inline' : ''}`}> <div className={`${styles.main} flex-wrap ${wrap ? 'd-inline' : ''}`}>
<Link href={`/items/${item.id}`} passHref> <Link href={`/items/${item.id}`} passHref>
<a ref={titleRef} className={`${styles.title} text-reset mr-2`}>{item.title}</a> <a ref={titleRef} className={`${styles.title} text-reset mr-2`}>
{item.searchTitle ? <SearchTitle title={item.searchTitle} /> : item.title}
</a>
</Link> </Link>
{item.url && {item.url &&
<> <>

View File

@ -36,13 +36,15 @@ export default function Search () {
}} }}
className={`w-auto ${styles.active}`} className={`w-auto ${styles.active}`}
onSubmit={async ({ q }) => { onSubmit={async ({ q }) => {
if (q.trim() !== '') {
router.push(`/search?q=${q}`) router.push(`/search?q=${q}`)
}
}} }}
> >
<Input <Input
name='q' name='q'
required required
autoFocus autoFocus={showSearch && !atBottom}
groupClassName='mr-3 mb-0 flex-grow-1' groupClassName='mr-3 mb-0 flex-grow-1'
className='w-100' className='w-100'
onChange={async (formik, e) => { onChange={async (formik, e) => {

View File

@ -5,6 +5,25 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
/* Use `…/dist/cjs/…` if youre not in ESM! */ /* Use `…/dist/cjs/…` if youre not in ESM! */
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism' import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
import mention from '../lib/remark-mention' import mention from '../lib/remark-mention'
import remarkDirective from 'remark-directive'
import { visit } from 'unist-util-visit'
function myRemarkPlugin () {
return (tree) => {
visit(tree, (node) => {
if (
node.type === 'textDirective' ||
node.type === 'leafDirective'
) {
if (node.name !== 'high') return
const data = node.data || (node.data = {})
data.hName = 'mark'
data.hProperties = {}
}
})
}
}
export default function Text ({ nofollow, children }) { export default function Text ({ nofollow, children }) {
return ( return (
@ -37,7 +56,7 @@ export default function Text ({ nofollow, children }) {
}, },
a: ({ node, ...props }) => <a target='_blank' rel={nofollow ? 'nofollow' : null} {...props} /> a: ({ node, ...props }) => <a target='_blank' rel={nofollow ? 'nofollow' : null} {...props} />
}} }}
remarkPlugins={[gfm, mention]} remarkPlugins={[gfm, mention, remarkDirective, myRemarkPlugin]}
> >
{children} {children}
</ReactMarkdown> </ReactMarkdown>

View File

@ -6,20 +6,20 @@ import { useRouter } from 'next/router'
export default function TopHeader ({ cat }) { export default function TopHeader ({ cat }) {
const router = useRouter() const router = useRouter()
const within = router.query.within const within = router.query.within
const userType = router.query.userType || 'stacked'
return ( return (
<> <>
<Navbar className='pt-0'> <Navbar className='pt-0'>
<Nav <Nav
className={`${styles.navbarNav} justify-content-around`} className={`${styles.navbarNav} justify-content-around`}
activeKey={cat} activeKey={cat.split('/')[0]}
> >
<Nav.Item> <Nav.Item>
<Link href={`/top/posts/${within}`} passHref> <Link href={`/top/posts/${within}`} passHref>
<Nav.Link <Nav.Link
eventKey='posts' eventKey='posts'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'day')}
> >
posts posts
</Nav.Link> </Nav.Link>
@ -30,18 +30,16 @@ export default function TopHeader ({ cat }) {
<Nav.Link <Nav.Link
eventKey='comments' eventKey='comments'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'week')}
> >
comments comments
</Nav.Link> </Nav.Link>
</Link> </Link>
</Nav.Item> </Nav.Item>
<Nav.Item> <Nav.Item>
<Link href={`/top/users/${within}`} passHref> <Link href={`/top/users/stacked/${within}`} passHref>
<Nav.Link <Nav.Link
eventKey='users' eventKey='users'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'month')}
> >
users users
</Nav.Link> </Nav.Link>
@ -49,6 +47,34 @@ export default function TopHeader ({ cat }) {
</Nav.Item> </Nav.Item>
</Nav> </Nav>
</Navbar> </Navbar>
{cat.split('/')[0] === 'users' &&
<Navbar className='pt-0'>
<Nav
className={`${styles.navbarNav} justify-content-around`}
activeKey={userType}
>
<Nav.Item>
<Link href={`/top/users/stacked/${within}`} passHref>
<Nav.Link
eventKey='stacked'
className={styles.navLink}
>
stacked
</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={`/top/users/spent/${within}`} passHref>
<Nav.Link
eventKey='spent'
className={styles.navLink}
>
spent
</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</Navbar>}
<Navbar className='pt-0'> <Navbar className='pt-0'>
<Nav <Nav
className={styles.navbarNav} className={styles.navbarNav}
@ -59,7 +85,6 @@ export default function TopHeader ({ cat }) {
<Nav.Link <Nav.Link
eventKey='day' eventKey='day'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'day')}
> >
day day
</Nav.Link> </Nav.Link>
@ -70,7 +95,6 @@ export default function TopHeader ({ cat }) {
<Nav.Link <Nav.Link
eventKey='week' eventKey='week'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'week')}
> >
week week
</Nav.Link> </Nav.Link>
@ -81,7 +105,6 @@ export default function TopHeader ({ cat }) {
<Nav.Link <Nav.Link
eventKey='month' eventKey='month'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'month')}
> >
month month
</Nav.Link> </Nav.Link>
@ -92,7 +115,6 @@ export default function TopHeader ({ cat }) {
<Nav.Link <Nav.Link
eventKey='year' eventKey='year'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.setItem('topWithin', 'year')}
> >
year year
</Nav.Link> </Nav.Link>
@ -103,7 +125,6 @@ export default function TopHeader ({ cat }) {
<Nav.Link <Nav.Link
eventKey='forever' eventKey='forever'
className={styles.navLink} className={styles.navLink}
onClick={() => localStorage.removeItem('topWithin')}
> >
forever forever
</Nav.Link> </Nav.Link>

View File

@ -98,6 +98,8 @@ export const ITEM_SEARCH = gql`
items { items {
...ItemFields ...ItemFields
text text
searchTitle
searchText
} }
} }
} }

View File

@ -39,11 +39,11 @@ export const USER_FIELDS = gql`
}` }`
export const TOP_USERS = gql` export const TOP_USERS = gql`
query TopUsers($cursor: String, $within: String!) { query TopUsers($cursor: String, $within: String!, $userType: String!) {
topUsers(cursor: $cursor, within: $within) { topUsers(cursor: $cursor, within: $within, userType: $userType) {
users { users {
name name
stacked amount
} }
cursor cursor
} }

1144
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -42,14 +42,17 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-longpressable": "^1.1.1", "react-longpressable": "^1.1.1",
"react-markdown": "^6.0.2", "react-markdown": "^6.0.2",
"react-string-replace": "^0.4.4",
"react-syntax-highlighter": "^15.4.3", "react-syntax-highlighter": "^15.4.3",
"react-textarea-autosize": "^8.3.3", "react-textarea-autosize": "^8.3.3",
"react-tweet-embed": "^1.3.1", "react-tweet-embed": "^1.3.1",
"remark-directive": "^2.0.1",
"remark-gfm": "^1.0.0", "remark-gfm": "^1.0.0",
"remove-markdown": "^0.3.0", "remove-markdown": "^0.3.0",
"sass": "^1.32.8", "sass": "^1.32.8",
"secp256k1": "^4.0.2", "secp256k1": "^4.0.2",
"swr": "^0.5.4", "swr": "^0.5.4",
"unist-util-visit": "^4.1.0",
"use-dark-mode": "^2.3.1", "use-dark-mode": "^2.3.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"webln": "^0.2.2", "webln": "^0.2.2",

View File

@ -1,19 +1,20 @@
import Layout from '../../../components/layout' import Layout from '../../../../components/layout'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { getGetServerSideProps } from '../../../api/ssrApollo' import { getGetServerSideProps } from '../../../../api/ssrApollo'
import TopHeader from '../../../components/top-header' import TopHeader from '../../../../components/top-header'
import { TOP_USERS } from '../../../fragments/users' import { TOP_USERS } from '../../../../fragments/users'
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import Link from 'next/link' import Link from 'next/link'
import MoreFooter from '../../../components/more-footer' import MoreFooter from '../../../../components/more-footer'
export const getServerSideProps = getGetServerSideProps(TOP_USERS) export const getServerSideProps = getGetServerSideProps(TOP_USERS)
export default function Index ({ data: { topUsers: { users, cursor } } }) { export default function Index ({ data: { topUsers: { users, cursor } } }) {
const router = useRouter() const router = useRouter()
const userType = router.query.userType
const { data, fetchMore } = useQuery(TOP_USERS, { const { data, fetchMore } = useQuery(TOP_USERS, {
variables: { within: router.query?.within } variables: { within: router.query?.within, userType: router.query?.userType }
}) })
if (data) { if (data) {
@ -22,12 +23,12 @@ export default function Index ({ data: { topUsers: { users, cursor } } }) {
return ( return (
<Layout> <Layout>
<TopHeader cat='users' /> <TopHeader cat={'users/' + userType} />
{users.map(user => ( {users.map(user => (
<Link href={`/${user.name}`} key={user.name}> <Link href={`/${user.name}`} key={user.name}>
<div className='d-flex align-items-center pointer'> <div className='d-flex align-items-center pointer'>
<h3 className='mb-0'>@{user.name}</h3> <h3 className='mb-0'>@{user.name}</h3>
<h2 className='ml-2 mb-0'><small className='text-success'>{user.stacked} stacked</small></h2> <h2 className='ml-2 mb-0'><small className='text-success'>{user.amount} {userType}</small></h2>
</div> </div>
</Link> </Link>
))} ))}

View File

@ -270,7 +270,7 @@ export function LnAddrWithdrawal () {
schema={LnAddrSchema} schema={LnAddrSchema}
initialError={error ? error.toString() : undefined} initialError={error ? error.toString() : undefined}
onSubmit={async ({ addr, amount, maxFee }) => { onSubmit={async ({ addr, amount, maxFee }) => {
const { data } = await sendToLnAddr({ variables: { addr, amount: Number(amount), maxFee } }) const { data } = await sendToLnAddr({ variables: { addr, amount: Number(amount), maxFee: Number(maxFee) } })
router.push(`/withdrawals/${data.sendToLnAddr.id}`) router.push(`/withdrawals/${data.sendToLnAddr.id}`)
}} }}
> >

View File

@ -1,18 +0,0 @@
import Layout from '../components/layout'
import Items from '../components/items'
import { getGetServerSideProps } from '../api/ssrApollo'
import { MORE_ITEMS } from '../fragments/items'
const variables = { sort: 'wot' }
export const getServerSideProps = getGetServerSideProps(MORE_ITEMS, variables)
export default function Index ({ data: { moreItems: { items, pins, cursor } } }) {
return (
<Layout>
<Items
items={items} pins={pins} cursor={cursor}
variables={variables} rank
/>
</Layout>
)
}

View File

@ -4,7 +4,7 @@ const Pageres = require('pageres')
async function captureUrl () { async function captureUrl () {
try { try {
const streams = await new Pageres({ crop: true, scale: 2, timeout: 10, launchOptions: { args: ['--single-process'] } }) const streams = await new Pageres({ crop: true, delay: 1, scale: 2, timeout: 10, launchOptions: { args: ['--single-process'] } })
.src(process.argv[2], ['600x315']) .src(process.argv[2], ['600x315'])
.run() .run()
process.stdout.write(streams[0], () => process.exit(0)) process.stdout.write(streams[0], () => process.exit(0))

View File

@ -65,6 +65,11 @@ $tooltip-bg: #5c8001;
} }
} }
mark {
background-color: var(--primary);
padding: 0 0.2rem;
}
.table-sm th, .table-sm td { .table-sm th, .table-sm td {
line-height: 1.2rem; line-height: 1.2rem;
} }