+ {show_search_intro &&
}
+
{mode === 'attention' &&
submode === 1 &&
local_attention_text === null && (
diff --git a/src/SearchIntro.js b/src/SearchIntro.js
new file mode 100644
index 0000000..6ea4b22
--- /dev/null
+++ b/src/SearchIntro.js
@@ -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)/** 。
+
+关注列表搜索基本只考虑主贴,只有评论极少的洞会也考虑评论。
+
+# 反馈
+
+如果你发现搜索结果不符合预期,例如有应该出现的内容没有出现,请带上 #搜索反馈 发帖反馈。如果你希望涉及你个人信息的内容不要出现在搜索结果中,请使用举报功能。
+`;
diff --git a/src/Title.js b/src/Title.js
index ebcd2dd..033ddde 100644
--- a/src/Title.js
+++ b/src/Title.js
@@ -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}
/>