Show search intro
This commit is contained in:
11
src/App.js
11
src/App.js
@@ -33,11 +33,13 @@ class App extends Component {
|
|||||||
sidebar_stack: [[null, null]], // list of [status, content]
|
sidebar_stack: [[null, null]], // list of [status, content]
|
||||||
mode: 'list', // list, single, search, attention
|
mode: 'list', // list, single, search, attention
|
||||||
search_text: null,
|
search_text: null,
|
||||||
|
show_search_intro: false,
|
||||||
flow_render_key: +new Date(),
|
flow_render_key: +new Date(),
|
||||||
token: localStorage['TOKEN'] || null,
|
token: localStorage['TOKEN'] || null,
|
||||||
};
|
};
|
||||||
this.show_sidebar_bound = this.show_sidebar.bind(this);
|
this.show_sidebar_bound = this.show_sidebar.bind(this);
|
||||||
this.set_mode_bound = this.set_mode.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);
|
this.on_pressure_bound = this.on_pressure.bind(this);
|
||||||
|
|
||||||
window.BACKEND =
|
window.BACKEND =
|
||||||
@@ -120,10 +122,17 @@ class App extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
mode: mode,
|
mode: mode,
|
||||||
search_text: search_text,
|
search_text: search_text,
|
||||||
|
show_search_intro: false,
|
||||||
flow_render_key: +new Date(),
|
flow_render_key: +new Date(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_search_intro(show_search_intro) {
|
||||||
|
this.setState({
|
||||||
|
show_search_intro: show_search_intro,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<TokenCtx.Provider
|
<TokenCtx.Provider
|
||||||
@@ -142,6 +151,7 @@ class App extends Component {
|
|||||||
<Title
|
<Title
|
||||||
show_sidebar={this.show_sidebar_bound}
|
show_sidebar={this.show_sidebar_bound}
|
||||||
set_mode={this.set_mode_bound}
|
set_mode={this.set_mode_bound}
|
||||||
|
set_search_intro={this.set_search_intro_bound}
|
||||||
mode={this.state.mode}
|
mode={this.state.mode}
|
||||||
/>
|
/>
|
||||||
<TokenCtx.Consumer>
|
<TokenCtx.Consumer>
|
||||||
@@ -168,6 +178,7 @@ class App extends Component {
|
|||||||
show_sidebar={this.show_sidebar_bound}
|
show_sidebar={this.show_sidebar_bound}
|
||||||
mode={this.state.mode}
|
mode={this.state.mode}
|
||||||
search_text={this.state.search_text}
|
search_text={this.state.search_text}
|
||||||
|
show_search_intro={this.state.show_search_intro}
|
||||||
token={token.value}
|
token={token.value}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
.root-dark-mode .box {
|
.root-dark-mode .box {
|
||||||
background-color: var(--box-bgcolor-dark);
|
background-color: var(--box-bgcolor-dark);
|
||||||
color: var(--foreground-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 {
|
.box-tip {
|
||||||
@@ -92,9 +94,12 @@
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
color: white;
|
color: white;
|
||||||
text-shadow: /* copied from .black-outline */ -1px -1px 0 rgba(0, 0, 0, 0.6),
|
text-shadow: /* copied from .black-outline */
|
||||||
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),
|
||||||
-1px 1px 0 rgba(0, 0, 0, 0.6), 0 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);
|
1px 1px 0 rgba(0, 0, 0, 0.6);
|
||||||
font-family: 'Consolas', 'Courier', monospace;
|
font-family: 'Consolas', 'Courier', monospace;
|
||||||
}
|
}
|
||||||
@@ -206,6 +211,10 @@
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.left-container .search-intro .box-content {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
.box-id {
|
.box-id {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
@@ -430,7 +439,6 @@
|
|||||||
stroke: #777;
|
stroke: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.vote-icon-active {
|
.vote-icon-active {
|
||||||
fill: #333;
|
fill: #333;
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/Flows.js
29
src/Flows.js
@@ -18,6 +18,7 @@ import { API, parse_replies } from './flows_api';
|
|||||||
import { cache } from './cache';
|
import { cache } from './cache';
|
||||||
import { save_attentions } from './Attention';
|
import { save_attentions } from './Attention';
|
||||||
import Poll from 'react-polls';
|
import Poll from 'react-polls';
|
||||||
|
import { SEARCH_INTRO_MARKDOWN } from './SearchIntro';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const IMAGE_BASE = 'https://thimg.yecdn.com/';
|
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" />
|
||||||
|
搜索指南
|
||||||
|
</div>
|
||||||
|
<div className="box-content">
|
||||||
|
<HighlightedMarkdown text={SEARCH_INTRO_MARKDOWN} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export class Flow extends PureComponent {
|
export class Flow extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -1473,7 +1494,8 @@ export class Flow extends PureComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { submode, announcement } = this.state;
|
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);
|
const submode_names = this.get_submode_names(mode);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -1503,6 +1525,7 @@ export class Flow extends PureComponent {
|
|||||||
mode={mode}
|
mode={mode}
|
||||||
submode={submode}
|
submode={submode}
|
||||||
search_text={search_text}
|
search_text={search_text}
|
||||||
|
show_search_intro={show_search_intro}
|
||||||
token={token}
|
token={token}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -1809,9 +1832,11 @@ class SubFlow extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const should_deletion_detect = localStorage['DELETION_DETECT'] === 'on';
|
const should_deletion_detect = localStorage['DELETION_DETECT'] === 'on';
|
||||||
const { mode, chunks, local_attention_text, search_param } = this.state;
|
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 (
|
return (
|
||||||
<div className="flow-container">
|
<div className="flow-container">
|
||||||
|
{show_search_intro && <SearchIntro />}
|
||||||
|
|
||||||
{mode === 'attention' &&
|
{mode === 'attention' &&
|
||||||
submode === 1 &&
|
submode === 1 &&
|
||||||
local_attention_text === null && (
|
local_attention_text === null && (
|
||||||
|
|||||||
37
src/SearchIntro.js
Normal file
37
src/SearchIntro.js
Normal 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)/** 。
|
||||||
|
|
||||||
|
关注列表搜索基本只考虑主贴,只有评论极少的洞会也考虑评论。
|
||||||
|
|
||||||
|
# 反馈
|
||||||
|
|
||||||
|
如果你发现搜索结果不符合预期,例如有应该出现的内容没有出现,请带上 #搜索反馈 发帖反馈。如果你希望涉及你个人信息的内容不要出现在搜索结果中,请使用举报功能。
|
||||||
|
`;
|
||||||
57
src/Title.js
57
src/Title.js
@@ -12,15 +12,26 @@ class ControlBar extends PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
search_text: '',
|
search_text: '',
|
||||||
|
search_focused: false,
|
||||||
|
search_submitted: false,
|
||||||
};
|
};
|
||||||
this.set_mode = props.set_mode;
|
this.set_mode = props.set_mode;
|
||||||
|
|
||||||
this.on_change_bound = this.on_change.bind(this);
|
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.on_keypress_bound = this.on_keypress.bind(this);
|
||||||
this.do_refresh_bound = this.do_refresh.bind(this);
|
this.do_refresh_bound = this.do_refresh.bind(this);
|
||||||
this.do_attention_bound = this.do_attention.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() {
|
componentDidMount() {
|
||||||
if (window.location.hash) {
|
if (window.location.hash) {
|
||||||
let text = decodeURIComponent(window.location.hash).substr(1);
|
let text = decodeURIComponent(window.location.hash).substr(1);
|
||||||
@@ -60,13 +71,50 @@ class ControlBar extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
on_change(event) {
|
on_change(event) {
|
||||||
|
const search_text = event.target.value;
|
||||||
|
const search_submitted =
|
||||||
|
search_text.length === 0 ? false : this.state.search_submitted;
|
||||||
this.setState({
|
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) {
|
on_keypress(event) {
|
||||||
if (event.key === 'Enter') {
|
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);
|
let flag_res = flag_re.exec(this.state.search_text);
|
||||||
if (flag_res) {
|
if (flag_res) {
|
||||||
if (flag_res[2]) {
|
if (flag_res[2]) {
|
||||||
@@ -102,7 +150,9 @@ class ControlBar extends PureComponent {
|
|||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState({
|
this.setState({
|
||||||
search_text: '',
|
search_text: '',
|
||||||
|
search_submitted: false,
|
||||||
});
|
});
|
||||||
|
this.props.set_search_intro(false);
|
||||||
this.set_mode('list', null);
|
this.set_mode('list', null);
|
||||||
window.location.hash = '';
|
window.location.hash = '';
|
||||||
}
|
}
|
||||||
@@ -111,7 +161,9 @@ class ControlBar extends PureComponent {
|
|||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
this.setState({
|
this.setState({
|
||||||
search_text: '',
|
search_text: '',
|
||||||
|
search_submitted: false,
|
||||||
});
|
});
|
||||||
|
this.props.set_search_intro(false);
|
||||||
this.set_mode('attention', null);
|
this.set_mode('attention', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +199,8 @@ class ControlBar extends PureComponent {
|
|||||||
: '关键词 / tag / #树洞号'
|
: '关键词 / tag / #树洞号'
|
||||||
}
|
}
|
||||||
onChange={this.on_change_bound}
|
onChange={this.on_change_bound}
|
||||||
|
onFocus={this.on_focus_bound}
|
||||||
|
onBlur={this.on_blur_bound}
|
||||||
onKeyPress={this.on_keypress_bound}
|
onKeyPress={this.on_keypress_bound}
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
@@ -215,6 +269,7 @@ export function Title(props) {
|
|||||||
<ControlBar
|
<ControlBar
|
||||||
show_sidebar={props.show_sidebar}
|
show_sidebar={props.show_sidebar}
|
||||||
set_mode={props.set_mode}
|
set_mode={props.set_mode}
|
||||||
|
set_search_intro={props.set_search_intro}
|
||||||
mode={props.mode}
|
mode={props.mode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user