Skip to content

Commit

Permalink
feat: add tabs on quick serach (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
joonas-yoon committed Jan 7, 2022
1 parent 1caeab3 commit f0c5522
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 31 deletions.
49 changes: 48 additions & 1 deletion css/common.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* Problem Timer */
#problem-timer .dropdown-menu {
width: 200px;
padding: 5px;
Expand All @@ -13,6 +14,7 @@
margin: 3px;
}

/* Quick Search */
#quick-search {
display: none;
position: fixed;
Expand All @@ -23,7 +25,7 @@
height: 100vh;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.15);
background: rgba(0, 0, 0, 0.075);
backdrop-filter: blur(3px);
}

Expand Down Expand Up @@ -69,10 +71,22 @@
}

#quick-search .results .quick-search-item b,
#quick-search .results .quick-search-item em,
#quick-search .results .quick-search-item strong {
background-color: rgba(255, 255, 64, 0.25);
}

#quick-search .results .quick-search-item > .search-breadcrumb {
margin-left: 0;
margin-bottom: 3px;
}
#quick-search .results .quick-search-item > .search-breadcrumb > li {
padding: 0;
}
#quick-search .results .quick-search-item > .search-breadcrumb > li + li:before {
content: "/\00a0";
}

#quick-search .results .quick-search-item > .title {
font-size: 1.25em;
margin-bottom: 5px;
Expand All @@ -82,6 +96,11 @@
color: #999;
margin-bottom: 5px;
}
#quick-search .results .quick-search-item > .meta .date,
#quick-search .results .quick-search-item > .meta .comments,
#quick-search .results .quick-search-item > .meta .author {
margin-right: 0.8em;
}

#quick-search .results .quick-search-item > .desc {
margin-bottom: 5px;
Expand All @@ -90,6 +109,10 @@
#quick-search .results .quick-search-item > .links a {
margin-right: 1em;
}
#quick-search .results .quick-search-item > .links span.tag {
color: #6a9299;
margin-right: 0.5em;
}

#quick-search .results-footer {
color: #999;
Expand All @@ -105,3 +128,27 @@
padding-left: 2em;
padding-right: 2em;
}
#quick-search .tabs .tab {
display: inline-block;
padding: 0.5em 2em;
border: 1px solid transparent;
border-bottom: none;
border-top-left-radius: 5px !important;
border-top-right-radius: 5px !important;
color: #565656;
cursor: pointer;
transition: all 200ms ease-in-out;
}
#quick-search .tabs .tab:hover,
#quick-search .tabs .tab:active {
background-color: rgb(163, 163, 163, 0.5);
border-color: rgb(147, 147, 147, 0.5);
color: #141414;
}
#quick-search .tabs .tab.active {
border-color: #bfbfbf;
background-color: #ffffff;
font-weight: bold;
color: #000000;
cursor: default;
}
17 changes: 17 additions & 0 deletions css/theme-dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,9 @@ html[theme='dark'] .cm-s-paper .CodeMirror-matchingbracket {
color: white !important;
}
/* Quick Search */
html[theme='dark'] #quick-search {
background: rgba(0, 0, 0, 0.15);
}
html[theme='dark'] #quick-search .results {
background-color: #262626;
border: 1px solid rgba(128, 128, 128, 0.1);
Expand All @@ -1058,3 +1061,17 @@ html[theme='dark'] #quick-search .results .quick-search-item {
html[theme='dark'] #quick-search .results .quick-search-item > .meta {
color: #999;
}
html[theme='dark'] #quick-search .tabs .tab {
color: #8f8f8f;
}
html[theme='dark'] #quick-search .tabs .tab:hover,
html[theme='dark'] #quick-search .tabs .tab:active {
background-color: rgb(32, 32, 32, 0.7);
border-color: rgb(38, 38, 38, 0.7);
color: #efefef;
}
html[theme='dark'] #quick-search .tabs .tab.active {
border-color: #262626;
background-color: #202020;
color: #ffffff;
}
200 changes: 170 additions & 30 deletions js/search.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
function extendQuickSearch() {
let isActive = false;

// UI: overlay
const bg = Utils.createElement('div', {
id: 'quick-search',
class: 'overlay',
Expand Down Expand Up @@ -30,14 +29,41 @@ function extendQuickSearch() {
moreButton.innerText = '더 많은 검색 결과 보기';
form.appendChild(input);
form.appendChild(resultBox);
// UI: tabs
const tabsContainer = Utils.createElement('div', {
class: 'tabs',
});
const tabs = [
{ title: '문제', c: 'Problems', active: true, el: null },
{ title: '문제집', c: 'Workbooks', el: null },
{ title: '출처', c: 'Categories', el: null },
{ title: '블로그', c: 'Blogs', el: null },
{ title: '게시판', c: 'Articles', el: null },
];
for (let i = 0; i < tabs.length; ++i) {
const tab = tabs[i];
const tabEl = Utils.createElement('div', { class: 'tab', tabIndex: i });
tabEl.innerText = tab.title;
if (tab.active) tabEl.classList.add('active');
tabEl.addEventListener('click', (evt) => {
evt.preventDefault();
activateTab(tabEl.getAttribute('tabIndex'));
});
tabs[i].el = tabEl; // refer
tabsContainer.appendChild(tabEl);
}
container.appendChild(tabsContainer);
container.appendChild(form);
container.appendChild(resultFooter);
container.appendChild(moreButton);
bg.appendChild(container);
document.body.appendChild(bg);

// variables
let searchHandle = null;
let lastSearchText = '';
let problemInfo = {};
let currentTabIndex = 0;

// add event listener to input
input.addEventListener('keyup', async (evt) => {
Expand All @@ -53,41 +79,66 @@ function extendQuickSearch() {
}
});

// handle key event
const keyPressed = new Set();
document.addEventListener('keydown', (evt) => {
keyPressed.add(evt.key);
});
document.addEventListener('keyup', (evt) => {
console.log(evt);
if (keyPressed.has('Escape')) {
isActive = false;
bg.style.display = 'none';
activate(false);
} else if (
keyPressed.has('/') &&
(keyPressed.has('Control') || keyPressed.has('Alt'))
) {
isActive = true;
bg.style.display = 'flex';
setTimeout(() => {
input.focus();
}, 10);
activate(true);
}
keyPressed.delete(evt.key);
});

// dismiss search overlay
document.addEventListener('click', (evt) => {
if (evt.target == bg) {
isActive = false;
if (evt.target == bg) activate(false);
});

async function activate(on) {
if (on === true) {
// fetch problem status by current user
problemInfo = await fetchProblemsByUser(getMyUsername());
// set variables
bg.style.display = 'flex';
setTimeout(() => {
input.focus();
}, 10);
} else {
// deactivate
bg.style.display = 'none';
}
});
}

function activateTab(tabIndex) {
for (const tab of tabs) {
const isActive = tab.el.getAttribute('tabIndex') == tabIndex;
if (isActive) {
tab.el.classList.add('active');
} else {
tab.el.classList.remove('active');
}
}
currentTabIndex = Number(tabIndex);
search(input.value);
}

async function search(searchText) {
// scroll to top
resultBox.scroll(0, 0);
// hijack
const currentIndexName = tabs[currentTabIndex].c || 'Problems';
const dataForm = {
requests: [
{
indexName: 'Problems',
indexName: currentIndexName,
params: encodeURI(
'query=' + searchText + '&page=0&facets=[]&tagFilters='
),
Expand All @@ -103,30 +154,119 @@ function extendQuickSearch() {
}
).then((res) => res.json());

resultBox.innerHTML = '';
const { hits, processingTimeMS, nbHits } = results[0];
console.log(results[0]);
resultBox.innerHTML = '';
console.groupCollapsed(`${currentIndexName}: "${searchText}"`);
console.log('results', results[0]);
for (const res of hits) {
const { id, time, memory } = res;
const { title, description } = res._highlightResult;
const item = Utils.createElement('div', { class: 'quick-search-item' });
item.innerHTML = `\
<div class="title"><a href="/problem/${id}">${id}번 - ${title.value}</a></div>\
<div class="meta">시간 제한: ${time}초 &nbsp; 메모리 제한: ${memory}MB</div>\
<div class="desc">${description.value}</div>\
<div class="links"> \
<a href="/submit/${id}">제출</a> \
<a href="/problem/status/${id}">맞은 사람</a> \
<a href="/status?from_problem=1&amp;problem_id=${id}">채점 현황</a> \
</div> \
`;
resultBox.appendChild(item);
const item = createResultItem(res, currentIndexName);
if (item) resultBox.appendChild(item);
console.log(res);
}
moreButton.href = encodeURI('/search#q=' + searchText + '&c=Problems');
console.groupEnd();
moreButton.href = encodeURI('/search#q=' + searchText + '&c=' + currentIndexName);
resultFooter.innerHTML = `${nbHits}개의 결과 중 ${hits.length}개 표시 (${
processingTimeMS / 1000
}초)`;
}

function createResultItem(result, indexName) {
if (result == null) return null;
switch (indexName) {
case 'Problems':
return createItemFromHTML(htmlProblems(result));
case 'Workbooks':
return createItemFromHTML(htmlWorkbooks(result));
case 'Categories':
return createItemFromHTML(htmlCategories(result));
case 'Blogs':
return createItemFromHTML(htmlBlogs(result));
case 'Articles':
return createItemFromHTML(htmlArticles(result));
default:
return null;
}
}

function createItemFromHTML(html) {
const item = Utils.createElement('div', { class: 'quick-search-item' });
item.innerHTML = html;
return item;
}

function htmlProblems(result) {
const { id, time, memory, _highlightResult } = result;
const { title, description } = _highlightResult;
return `\
<div class="title"><a href="/problem/${id}">${id}번 - ${title.value}</a></div>\
<div class="meta">시간 제한: ${time}초 &nbsp; 메모리 제한: ${memory}MB</div>\
<div class="desc">${description.value}</div>\
<div class="links"> \
<a href="/submit/${id}">제출</a> \
<a href="/problem/status/${id}">맞은 사람</a> \
<a href="/status?from_problem=1&amp;problem_id=${id}">채점 현황</a> \
</div> \
`;
}
function htmlWorkbooks(result) {
const { id, problems, _highlightResult } = result;
const { name, comment, creator, problem } = _highlightResult;
return `\
<div class="title"><a href="/workbook/view/${id}">${name.value}</a></div>\
<div class="meta">만든 사람: ${creator.value} &nbsp; 문제: ${problems}</div>\
<div class="desc">${comment.value}</div>\
`;
}
function htmlCategories(result) {
const { avail, id, parents, total, _highlightResult } = result;
const { name } = _highlightResult;
const breadcrumb = (parents || []).map(child => `<li><a href="/category/${child.id}">${child.name}</a></li>`).join('\n');
return `\
<ul class="list-inline up-ul search-breadcrumb">${breadcrumb}</ul>
<div class="title"><a href="/category/detail/view/${id}">${name.value}</a></div>\
<div class="meta">전체 문제: ${total} &nbsp; 풀 수 있는 문제: ${avail}</div>\
`;
}
function htmlBlogs(result) {
const { id, user, comments, date, tags } = result;
const { title } = result._highlightResult;
const { content } = result._snippetResult;
const tagList = tags.filter(tag => tag.length > 0).map(tag => `<span class="tag">#${tag}</span>`).join('\n');
return `\
<div class="title"><a href="/blog/view/${id}">${title.value}</a></div>\
<div class="meta">\
<span class="date">${date}</span>\
<a class="author" href="/user/${user}">${user}</a>\
<a href="/blog/view/${id}#comments"><i class="fa fa-comments-o"></i> ${comments}</a>\
</div>\
<div class="desc">${content.value}</div>\
<div class="links">${tagList}</div> \
`;
}
function htmlArticles(result) {
const { id, problem, category, created, user, comments, like } = result;
const { subject } = result._highlightResult;
const { content } = result._snippetResult;
let problemTag = '';
let problemColor = '';
if (problem != null) {
problemColor = problemInfo[problem] || '';
problemTag = `<a href="/problem/${problem}" class="${problemColor}">${problem}번</a>&nbsp;`;
}
return `\
<div class="meta ${problemColor}">\
${problemTag}<span>${category}</span>\
</div>
<div class="title"><a href="/board/view/${id}">${subject.value}</a></div>\
<div class="meta">\
<span class="date">${created}</span>\
<a class="author" href="/user/${user}">${user}</a>\
<a href="/board/view/${id}#comments" class="comments"><i class="fa fa-comments-o"></i> ${comments}</a>\
<span><i class="fa fa-thumbs-o-up"></i> ${like}</span>\
</div>\
<div class="desc">${content.value}</div>\
`;
}
}

async function extendSearchPage() {
Expand Down

0 comments on commit f0c5522

Please sign in to comment.