Skip to content

Commit

Permalink
$ yarn eslint --fix src/**/*.vue src/**/*.ts src/**/*.js @ fe
Browse files Browse the repository at this point in the history
  • Loading branch information
n0099 committed Dec 18, 2023
1 parent f500a45 commit 8ec64b0
Show file tree
Hide file tree
Showing 21 changed files with 102 additions and 67 deletions.
2 changes: 1 addition & 1 deletion fe/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<GlobalNavBar />
<HorizontalMobileMessage />
<img id="loadingBlocks" :src="iconLoadingBlocks" class="d-none" />
<img :src="iconLoadingBlocks" class="d-none" id="loadingBlocks" />
<ConfigProvider :locale="AntdZhCn">
<div class="container">
<RouterView />
Expand Down
4 changes: 2 additions & 2 deletions fe/src/components/GlobalNavBar.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<template>
<nav class="navbar navbar-expand-lg shadow-sm bg-light">
<div id="nav" class="container-fluid">
<div class="container-fluid" id="nav">
<RouterLink to="/" class="navbar-brand">open-tbm @ {{ envInstanceName }}</RouterLink>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon" />
</button>
<div id="navbar" class="navbar-collapse collapse">
<div class="navbar-collapse collapse" id="navbar">
<ul class="navbar-nav">
<template v-for="(nav, _k) in navs" :key="_k">
<li v-if="'routes' in nav" class="nav-item dropdown" :class="{ active: nav.isActive }">
Expand Down
9 changes: 7 additions & 2 deletions fe/src/components/Post/NavSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ import type { MenuClickEventHandler } from 'ant-design-vue/lib/menu/src/interfac
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import _ from 'lodash';
const props = defineProps<{ postPages: ApiPostsQuery[] }>();
const route = useRoute();
const router = useRouter();
const props = defineProps<{ postPages: ApiPostsQuery[] }>();
const expandedPages = ref<string[]>([]);
const selectedThread = ref<string[]>([]);
const firstPostInViewDefault = { cursor: '', tid: 0, pid: 0 };
Expand All @@ -77,6 +77,7 @@ const scrollStop = _.debounce(() => {
const reduceFindTopmostElementInView = (topOffset: number) =>
(result: { top: number, el: Element }, curEl: Element) => {
const elTop = curEl.getBoundingClientRect().top - topOffset;
// ignore element which its y coord is ahead of the top of viewport
if (elTop >= 0 && result.top > elTop)
return { top: elTop, el: curEl };
Expand All @@ -90,11 +91,13 @@ const scrollStop = _.debounce(() => {
const currentFirstPostInView = {
t: findFirstDomInView('.thread-title'),
// 80px (5rem) is the top offset of .reply-title, aka `.reply-title { top: 5rem; }`
p: findFirstDomInView('.reply-title', 80)
};
const firstPostIDInView = _.mapValues(currentFirstPostInView, i =>
Number(i.parentElement?.getAttribute('data-post-id')));
// when there's no thread or reply item in the viewport
// currentFirstPostInView.* will be the initial <null> element and firstPostIDInView.* will be NaN
if (Number.isNaN(firstPostIDInView.t)) {
Expand All @@ -113,6 +116,7 @@ const scrollStop = _.debounce(() => {
? { hash, name: removeEnd(route.name, routeNameSuffix.cursor), params: _.omit(route.params, 'cursor') }
: { hash, name: routeNameWithCursor(route.name), params: { ...route.params, cursor } });
};
// is the first reply belonged to the first thread, true when the first thread has no reply,
// the first reply will belong to another thread that comes after the first thread in view
if (_.chain(props.postPages)
Expand Down Expand Up @@ -149,9 +153,10 @@ watchEffect(() => {
isScrollTriggeredByNavigate = false;
return;
}
// scroll menu to the link to reply in <ViewList>
// which is the topmost one in the viewport (nearest to top border of viewport)
const replyEl = document.querySelector(`.posts-nav-reply-link[data-pid='${pid}']`) as HTMLElement | null;
const replyEl = document.querySelector(`.posts-nav-reply-link[data-pid='${pid}']`);
const navMenuEl = replyEl?.closest('.posts-nav');
if (replyEl !== null && navMenuEl
&& navMenuEl.getBoundingClientRect().top === 0) // is navMenuEl sticking to the top border of viewport
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<template>
<a :href="tiebaPostLink(post.tid, postTypeID === 'tid' ? undefined : postIDSelector())"
<a :href="tiebaPostLink(post.tid, postTypeID === 'tid'
? undefined
: postIDSelector())"
target="_blank" class="badge bg-light rounded-pill link-dark">
<FontAwesomeIcon icon="link" size="lg" class="align-bottom" />
</a>
Expand Down
36 changes: 20 additions & 16 deletions fe/src/components/Post/queryForm/QueryForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<div class="col-3">
<div class="input-group">
<span class="input-group-text"><FontAwesomeIcon icon="filter" /></span>
<select v-model.number="uniqueParams.fid.value" id="paramFid"
:class="{ 'is-invalid': isFidInvalid }" class="form-select form-control">
<select v-model.number="uniqueParams.fid.value" :class="{ 'is-invalid': isFidInvalid }"
class="form-select form-control" id="paramFid">
<option value="0">未指定</option>
<option v-for="forum in forumList"
:key="forum.fid" :value="forum.fid">{{ forum.name }}</option>
Expand All @@ -17,26 +17,26 @@
<div class="col my-auto">
<div class="input-group">
<div class="form-check form-check-inline">
<input v-model="uniqueParams.postTypes.value" id="paramPostTypesThread"
type="checkbox" value="thread" class="form-check-input" />
<input v-model="uniqueParams.postTypes.value" type="checkbox"
value="thread" class="form-check-input" id="paramPostTypesThread" />
<label class="form-check-label" for="paramPostTypesThread">主题帖</label>
</div>
<div class="form-check form-check-inline">
<input v-model="uniqueParams.postTypes.value" id="paramPostTypesReply"
type="checkbox" value="reply" class="form-check-input" />
<input v-model="uniqueParams.postTypes.value" type="checkbox"
value="reply" class="form-check-input" id="paramPostTypesReply" />
<label class="form-check-label" for="paramPostTypesReply">回复帖</label>
</div>
<div class="form-check form-check-inline">
<input v-model="uniqueParams.postTypes.value" id="paramPostTypesSubReply"
type="checkbox" value="subReply" class="form-check-input" />
<input v-model="uniqueParams.postTypes.value" type="checkbox"
value="subReply" class="form-check-input" id="paramPostTypesSubReply" />
<label class="form-check-label" for="paramPostTypesSubReply">楼中楼</label>
</div>
</div>
</div>
</div>
<div class="row mt-2 mb-3">
<label class="col-1 col-form-label" for="paramOrder">排序方式</label>
<div id="paramOrder" class="col-8">
<div class="col-8" id="paramOrder">
<div class="input-group">
<span class="input-group-text"><FontAwesomeIcon icon="sort-amount-down" /></span>
<select v-model="uniqueParams.orderBy.value"
Expand Down Expand Up @@ -69,8 +69,8 @@
}" />
<div class="param-input-group-text input-group-text">
<div class="form-check">
<input v-model="p.subParam.not" :id="`param${_.upperFirst(p.name)}Not-${pI}`"
type="checkbox" value="good" class="form-check-input" />
<input v-model="p.subParam.not" type="checkbox" value="good" class="form-check-input"
:id="`param${_.upperFirst(p.name)}Not-${pI}`" />
<label :for="`param${_.upperFirst(p.name)}Not-${pI}`"
class="text-secondary fw-bold form-check-label">非</label>
</div>
Expand Down Expand Up @@ -102,16 +102,16 @@
<div v-if="p.name === 'threadProperties'">
<div class="param-input-group-text input-group-text">
<div class="form-check">
<input v-model="p.value" :id="`paramThreadPropertiesGood-${pI}`"
type="checkbox" value="good" class="form-check-input" />
<input v-model="p.value" type="checkbox" value="good" class="form-check-input"
:id="`paramThreadPropertiesGood-${pI}`" />
<label :for="`paramThreadPropertiesGood-${pI}`"
class="text-danger fw-normal form-check-label">精品</label>
</div>
</div>
<div class="param-input-group-text input-group-text">
<div class="form-check">
<input v-model="p.value" :id="`paramThreadPropertiesSticky-${pI}`"
type="checkbox" value="sticky" class="form-check-input" />
<input v-model="p.value" type="checkbox" value="sticky" class="form-check-input"
:id="`paramThreadPropertiesSticky-${pI}`" />
<label :for="`paramThreadPropertiesSticky-${pI}`"
class="text-primary fw-normal form-check-label">置顶</label>
</div>
Expand Down Expand Up @@ -182,11 +182,11 @@ import { RangePicker } from 'ant-design-vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import _ from 'lodash';
const router = useRouter();
defineProps<{
isLoading: boolean,
forumList: ApiForumList
}>();
const router = useRouter();
const {
uniqueParams,
params,
Expand Down Expand Up @@ -235,10 +235,13 @@ const getCurrentQueryType = () => {
return 'fid';
}
}
// is there no other params except post id params
if (_.isEmpty(_.reject(clearedParams, isPostIDParam))
// is there only one post id param
&& _.filter(clearedParams, isPostIDParam).length === 1
// is all post ID params doesn't own any sub param
&& _.chain(clearedParams).map('subParam').filter().isEmpty().value())
return 'postID';
Expand Down Expand Up @@ -357,6 +360,7 @@ const parseRoute = (route: RouteLocationNormalizedLoaded) => {
uniqueParams.value = _.mapValues(uniqueParams.value, _.unary(fillParamDefaultValue)) as KnownUniqueParams;
params.value = [];
const routeName = removeEnd(route.name, routeNameSuffix.page);
// parse route path to params
if (routeName === 'post/param' && _.isArray(route.params.pathMatch)) {
parseParamRoute(route.params.pathMatch); // omit the page param from route full path
Expand Down
1 change: 1 addition & 0 deletions fe/src/components/Post/queryForm/queryParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const useQueryFormDeps: Parameters<typeof useQueryForm>[0] = {
}
}
};

// must get invoked with in the setup() of component
export const useQueryFormWithUniqueParams = () => {
const ret = useQueryForm<KnownUniqueParams, KnownParams>(useQueryFormDeps);
Expand Down
9 changes: 8 additions & 1 deletion fe/src/components/Post/queryForm/useQueryForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default <
type UniqueParam = ObjValues<UniqueParams>;
type Param = ObjValues<Params>;
const router = useRouter();

// [{ name: '', value: '', subParam: { name: value } },...]
const uniqueParams = ref<UniqueParams>({} as UniqueParams) as Ref<UniqueParams>;
const params = ref<Param[]>([]) as Ref<Param[]>;
Expand Down Expand Up @@ -49,6 +50,7 @@ export default <
};
const deleteParam = (paramIndex: number) => {
_.pull(invalidParamsIndex.value, paramIndex);

// move forward index of all params, which after current
invalidParamsIndex.value = invalidParamsIndex.value.map(invalidParamIndex =>
(invalidParamIndex > paramIndex ? invalidParamIndex - 1 : invalidParamIndex));
Expand All @@ -58,13 +60,15 @@ export default <
const defaultParam = _.cloneDeep(deps.paramsDefaultValue[param.name]);
if (defaultParam === undefined)
throw Error(`Param ${param.name} not found in paramsDefaultValue`);

// remove subParam.not: false, which previously added by fillParamDefaultValue()
if (defaultParam.subParam !== undefined)
defaultParam.subParam.not ??= false;
const newParam: Partial<UnknownParam> = _.cloneDeep(param); // prevent mutating origin param
// number will consider as empty in isEmpty(), to prevent this we use complex short circuit evaluate expression
if (!(_.isNumber(newParam.value) || !_.isEmpty(newParam.value))
|| (_.isArray(newParam.value) && _.isArray(defaultParam.value)

// sort array type param value for comparing
? _.isEqual(_.sortBy(newParam.value), _.sortBy(defaultParam.value))
: newParam.value === defaultParam.value))
Expand All @@ -73,6 +77,7 @@ export default <
_.each(defaultParam.subParam, (value, name) => {
if (newParam.subParam === undefined)
return;

// undefined means this sub param must get deleted and merge into parent, as part of the parent param value
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
if (newParam.subParam[name] === value || value === undefined)
Expand All @@ -85,9 +90,11 @@ export default <
return _.isEmpty(_.omit(newParam, 'name')) ? null : newParam; // return null for further filter()
};
const clearedParamsDefaultValue = (): Array<Partial<Param>> =>

// filter() will remove falsy values like null
_.filter(params.value.map(clearParamDefaultValue)) as Array<Partial<Param>>;
const clearedUniqueParamsDefaultValue = (): Partial<UniqueParams> =>

// mapValues() return object which remains keys, pickBy() like filter() for objects
_.pickBy(_.mapValues(uniqueParams.value, clearParamDefaultValue)) as Partial<UniqueParams>;
const removeUndefinedFromPartialObjectValues = <T extends Partial<T>, R>(object: Partial<T>) =>
Expand Down Expand Up @@ -147,7 +154,7 @@ export default <
if (isUniqueParam(param)) { // is unique param
uniqueParams.value[param.name as keyof UniqueParams] = param;
} else {
params.value.push(param as Param);
params.value.push(param);
}
})
.value();
Expand Down
21 changes: 10 additions & 11 deletions fe/src/components/Post/queryForm/widgets/InputTextMatchParam.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,30 @@
<div class="input-group-text">
<div class="form-check form-check-inline">
<input @input="emitModelChange('matchBy', ($event.target as HTMLInputElement).value as 'regex')"
:id="inputID('Regex')"
:checked="modelValue.subParam.matchBy === 'regex'" :name="inputName"
value="regex" type="radio" class="form-check-input" />
:checked="modelValue.subParam.matchBy === 'regex'"
:name="inputName" value="regex"
type="radio" class="form-check-input" :id="inputID('Regex')" />
<label :for="inputID('Regex')" class="form-check-label">正则</label>
</div>
<div class="form-check form-check-inline">
<input @input="emitModelChange('matchBy', ($event.target as HTMLInputElement).value as 'implicit')"
:id="inputID('Implicit')"
:checked="modelValue.subParam.matchBy === 'implicit'" :name="inputName"
value="implicit" type="radio" class="form-check-input" />
:checked="modelValue.subParam.matchBy === 'implicit'"
:name="inputName" value="implicit"
type="radio" class="form-check-input" :id="inputID('Implicit')" />
<label :for="inputID('Implicit')" class="form-check-label">模糊</label>
</div>
<div class="form-check form-check-inline">
<input @input="emitModelChange('matchBy', ($event.target as HTMLInputElement).value as 'explicit')"
:id="inputID('Explicit')"
:checked="modelValue.subParam.matchBy === 'explicit'" :name="inputName"
value="explicit" type="radio" class="form-check-input" />
:checked="modelValue.subParam.matchBy === 'explicit'"
:name="inputName" value="explicit"
type="radio" class="form-check-input" :id="inputID('Explicit')" />
<label :for="inputID('Explicit')" class="form-check-label">精确</label>
</div>
<div class="form-check form-check-inline">
<input @input="emitModelChange('spaceSplit', ($event.target as HTMLInputElement).checked)"
:id="inputID('SpaceSplit')"
:checked="modelValue.subParam.spaceSplit"
:disabled="modelValue.subParam.matchBy === 'regex'"
type="checkbox" class="form-check-input" />
type="checkbox" class="form-check-input" :id="inputID('SpaceSplit')" />
<label :for="inputID('SpaceSplit')" class="form-check-label">空格分隔</label>
</div>
</div>
Expand Down
7 changes: 4 additions & 3 deletions fe/src/components/Post/views/ViewList.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div :data-cursor="posts.pages.currentCursor" class="post-render-list pb-3">
<div v-for="thread in posts.threads" :key="thread.tid"
:id="`t${thread.tid}`" :data-post-id="thread.tid" class="mt-3 card">
:data-post-id="thread.tid" class="mt-3 card" :id="`t${thread.tid}`">
<div class="thread-title shadow-sm card-header sticky-top">
<div class="row justify-content-between">
<div class="col-auto">
Expand Down Expand Up @@ -70,7 +70,7 @@
</div>
</div>
</div>
<div v-for="reply in thread.replies" :key="reply.pid" :id="String(reply.pid)" :data-post-id="reply.pid">
<div v-for="reply in thread.replies" :key="reply.pid" :data-post-id="reply.pid" :id="reply.pid">
<div class="reply-title sticky-top card-header">
<div class="d-inline-flex gap-1 h5">
<span class="badge bg-secondary">{{ reply.floor }}楼</span>
Expand Down Expand Up @@ -128,7 +128,7 @@
</RouterLink>
<div class="float-end badge bg-light">
<div class="d-inline"
:class="{ 'invisible': hoveringSubReplyID !== subReply.spid }">
:class="{ invisible: hoveringSubReplyID !== subReply.spid }">
<PostCommonMetadataIconLinks :post="subReply"
postTypeID="spid"
:postIDSelector="() => subReply.spid" />
Expand Down Expand Up @@ -199,6 +199,7 @@ const posts = computed(() => {
return [subReply]; // useless guard since subReply will never be an array
// group sub replies item by continuous and same post author
const previousSubReply = subReplies[index - 1] as SubReply | undefined;
// https://github.com/microsoft/TypeScript/issues/13778
if (previousSubReply !== undefined
&& subReply.authorUid === previousSubReply.authorUid)
Expand Down
Loading

0 comments on commit 8ec64b0

Please sign in to comment.