Show search intro

This commit is contained in:
2026-06-26 02:05:47 +08:00
parent 351592d437
commit f79d53401a
5 changed files with 144 additions and 8 deletions

View File

@@ -33,11 +33,13 @@ class App extends Component {
sidebar_stack: [[null, null]], // list of [status, content]
mode: 'list', // list, single, search, attention
search_text: null,
show_search_intro: false,
flow_render_key: +new Date(),
token: localStorage['TOKEN'] || null,
};
this.show_sidebar_bound = this.show_sidebar.bind(this);
this.set_mode_bound = this.set_mode.bind(this);
this.set_search_intro_bound = this.set_search_intro.bind(this);
this.on_pressure_bound = this.on_pressure.bind(this);
window.BACKEND =
@@ -120,10 +122,17 @@ class App extends Component {
this.setState({
mode: mode,
search_text: search_text,
show_search_intro: false,
flow_render_key: +new Date(),
});
}
set_search_intro(show_search_intro) {
this.setState({
show_search_intro: show_search_intro,
});
}
render() {
return (
<TokenCtx.Provider
@@ -142,6 +151,7 @@ class App extends Component {
<Title
show_sidebar={this.show_sidebar_bound}
set_mode={this.set_mode_bound}
set_search_intro={this.set_search_intro_bound}
mode={this.state.mode}
/>
<TokenCtx.Consumer>
@@ -168,6 +178,7 @@ class App extends Component {
show_sidebar={this.show_sidebar_bound}
mode={this.state.mode}
search_text={this.state.search_text}
show_search_intro={this.state.show_search_intro}
token={token.value}
/>
) : (

View File

@@ -15,7 +15,9 @@
.root-dark-mode .box {
background-color: var(--box-bgcolor-dark);
color: var(--foreground-dark);
box-shadow: 0 0 2px rgba(255, 255, 255, 0.25), 0 0 7px rgba(0, 0, 0, 0.15);
box-shadow:
0 0 2px rgba(255, 255, 255, 0.25),
0 0 7px rgba(0, 0, 0, 0.15);
}
.box-tip {
@@ -92,9 +94,12 @@
left: 10px;
margin-top: 1.5em;
color: white;
text-shadow: /* copied from .black-outline */ -1px -1px 0 rgba(0, 0, 0, 0.6),
0 -1px 0 rgba(0, 0, 0, 0.6), 1px -1px 0 rgba(0, 0, 0, 0.6),
-1px 1px 0 rgba(0, 0, 0, 0.6), 0 1px 0 rgba(0, 0, 0, 0.6),
text-shadow: /* copied from .black-outline */
-1px -1px 0 rgba(0, 0, 0, 0.6),
0 -1px 0 rgba(0, 0, 0, 0.6),
1px -1px 0 rgba(0, 0, 0, 0.6),
-1px 1px 0 rgba(0, 0, 0, 0.6),
0 1px 0 rgba(0, 0, 0, 0.6),
1px 1px 0 rgba(0, 0, 0, 0.6);
font-family: 'Consolas', 'Courier', monospace;
}
@@ -206,6 +211,10 @@
overflow-y: hidden;
}
.left-container .search-intro .box-content {
max-height: none;
}
.box-id {
color: #666666;
}
@@ -430,7 +439,6 @@
stroke: #777;
}
.vote-icon-active {
fill: #333;
}

View File

@@ -18,6 +18,7 @@ import { API, parse_replies } from './flows_api';
import { cache } from './cache';
import { save_attentions } from './Attention';
import Poll from 'react-polls';
import { SEARCH_INTRO_MARKDOWN } from './SearchIntro';
/*
const IMAGE_BASE = 'https://thimg.yecdn.com/';
@@ -1425,6 +1426,26 @@ function Announcement(props) {
);
}
function SearchIntro() {
return (
<div className="flow-item-row search-intro">
<div className="flow-item">
<div className="box box-post">
<div className="box-post-main">
<div className="box-header">
<span className="icon icon-about" />
&nbsp;搜索指南
</div>
<div className="box-content">
<HighlightedMarkdown text={SEARCH_INTRO_MARKDOWN} />
</div>
</div>
</div>
</div>
</div>
);
}
export class Flow extends PureComponent {
constructor(props) {
super(props);
@@ -1473,7 +1494,8 @@ export class Flow extends PureComponent {
render() {
const { submode, announcement } = this.state;
const { mode, show_sidebar, search_text, token } = this.props;
const { mode, show_sidebar, search_text, show_search_intro, token } =
this.props;
const submode_names = this.get_submode_names(mode);
return (
<>
@@ -1503,6 +1525,7 @@ export class Flow extends PureComponent {
mode={mode}
submode={submode}
search_text={search_text}
show_search_intro={show_search_intro}
token={token}
/>
</>
@@ -1809,9 +1832,11 @@ class SubFlow extends PureComponent {
render() {
const should_deletion_detect = localStorage['DELETION_DETECT'] === 'on';
const { mode, chunks, local_attention_text, search_param } = this.state;
const { submode, show_sidebar } = this.props;
const { submode, show_sidebar, show_search_intro } = this.props;
return (
<div className="flow-container">
{show_search_intro && <SearchIntro />}
{mode === 'attention' &&
submode === 1 &&
local_attention_text === null && (

37
src/SearchIntro.js Normal file
View File

@@ -0,0 +1,37 @@
export const SEARCH_INTRO_MARKDOWN = `
树洞提供了较为丰富的搜索功能,可以支持复杂的搜索条件。搜索分为两个大类,线上全局搜索,和在已关注的帖子中过滤。
# 线上全局搜索
直接点击搜索框进入全局搜索。有三个子模式Tag搜索全文搜索和头衔。Tag搜索会查询所有匹配的 #hashtag 和折叠警告,包括 #举报 。搜索头衔会查询对应头衔发布的内容。全文搜索会全文查找匹配的搜索词,并支持一些高级语法。
## 全文搜索的基本语法
全文搜索支持一个或多个关键词,多个关键词用空格分隔。支持 AND OR 语法,空格默认视为 OR 。搜索 **电子 直博** 等效于 **电子 OR 直博** 。支持括号,例如 **(贵系 OR 雷系) AND 好工作** 。支持单双引号,例如 **"and me"** **'"鹅腿"'** 。
此外,可以直接使用 + - 符号标记必须包含和必须排除。例如,可以搜索 **+(贵系 雷系) +好工作** 。
## 字段过滤与帖子范围筛选
支持按照字段进行更复杂的筛选包括content comment post_id 三个字段。例如 **content:避雷 AND comment:cy** 。可以使用 IN 语法,例如可以用 **content: IN [微积分 线代]** 替代 **content:微积分 OR content:线代**。
可以使用post_id字段限定帖子范围例如 **+(毕业 出二手 二手) +post_id:[700000 TO \\*]** 。
## 搜索结果的排序
单个关键词的简单搜索会按照帖子从新到旧排序。复杂的搜索会按匹配程度排序,与出现次数、全文长度、搜索词稀有度等有关。主贴和评论分别记分,且主贴的权重更大。可以在单关键词的结尾加空格来改为匹配程度排序。
如需提高某个关键词的权重,可以使用 ^,例如 **微积分^4.0 线代** 会给微积分更高的权重。**+微积分 线代** 则只返回包含微积分的结果,且优先返回同时包含线代的结果。
# 在关注列表中搜索
展示关注列表的情况下,点击搜索框,进入关注列表搜索。
关注列表搜索在本地进行过滤,支持多关键词和正则表达式。请将正则包裹在/ / 中。例如, **/\\d{2,}(万|w)/** 。
关注列表搜索基本只考虑主贴,只有评论极少的洞会也考虑评论。
# 反馈
如果你发现搜索结果不符合预期,例如有应该出现的内容没有出现,请带上 #搜索反馈 发帖反馈。如果你希望涉及你个人信息的内容不要出现在搜索结果中,请使用举报功能。
`;

View File

@@ -12,15 +12,26 @@ class ControlBar extends PureComponent {
super(props);
this.state = {
search_text: '',
search_focused: false,
search_submitted: false,
};
this.set_mode = props.set_mode;
this.on_change_bound = this.on_change.bind(this);
this.on_focus_bound = this.on_focus.bind(this);
this.on_blur_bound = this.on_blur.bind(this);
this.on_keypress_bound = this.on_keypress.bind(this);
this.do_refresh_bound = this.do_refresh.bind(this);
this.do_attention_bound = this.do_attention.bind(this);
}
update_search_intro(search_text, search_focused, search_submitted) {
const has_search_text = search_text.trim().length > 0;
this.props.set_search_intro(
!search_submitted && (has_search_text || search_focused),
);
}
componentDidMount() {
if (window.location.hash) {
let text = decodeURIComponent(window.location.hash).substr(1);
@@ -60,13 +71,50 @@ class ControlBar extends PureComponent {
}
on_change(event) {
const search_text = event.target.value;
const search_submitted =
search_text.length === 0 ? false : this.state.search_submitted;
this.setState({
search_text: event.target.value,
search_text: search_text,
search_submitted: search_submitted,
});
this.update_search_intro(
search_text,
this.state.search_focused,
search_submitted,
);
}
on_focus() {
this.setState({
search_focused: true,
});
this.update_search_intro(
this.state.search_text,
true,
this.state.search_submitted,
);
}
on_blur(event) {
if (event.relatedTarget?.closest('.search-intro')) return;
this.setState({
search_focused: false,
});
this.update_search_intro(
this.state.search_text,
false,
this.state.search_submitted,
);
}
on_keypress(event) {
if (event.key === 'Enter') {
this.setState({
search_submitted: true,
});
this.props.set_search_intro(false);
let flag_res = flag_re.exec(this.state.search_text);
if (flag_res) {
if (flag_res[2]) {
@@ -102,7 +150,9 @@ class ControlBar extends PureComponent {
window.scrollTo(0, 0);
this.setState({
search_text: '',
search_submitted: false,
});
this.props.set_search_intro(false);
this.set_mode('list', null);
window.location.hash = '';
}
@@ -111,7 +161,9 @@ class ControlBar extends PureComponent {
window.scrollTo(0, 0);
this.setState({
search_text: '',
search_submitted: false,
});
this.props.set_search_intro(false);
this.set_mode('attention', null);
}
@@ -147,6 +199,8 @@ class ControlBar extends PureComponent {
: '关键词 / tag / #树洞号'
}
onChange={this.on_change_bound}
onFocus={this.on_focus_bound}
onBlur={this.on_blur_bound}
onKeyPress={this.on_keypress_bound}
/>
<a
@@ -215,6 +269,7 @@ export function Title(props) {
<ControlBar
show_sidebar={props.show_sidebar}
set_mode={props.set_mode}
set_search_intro={props.set_search_intro}
mode={props.mode}
/>
</div>