Becoming X
← Back to dashboard

옵시디언 옴니서치: 기본 검색 이상의 가치

Omnisearch 플러그인의 핵심 기능부터 구글 연동, CMDS 커뮤니티 개선 유저스크립트까지

Contributor 구요한
옵시디언 옴니서치: 기본 검색 이상의 가치
Contributor 구요한
Obsidian Omnisearch 검색

옵시디언 옴니서치

기본 검색 이상의 가치

옵시디언 기본 검색을 넘어서는 스마트 검색, 그리고 구글과 연동하는 방법까지. 용어 위에 마우스를 올려보세요!

왜 옴니서치인가?

옴니서치(Omnisearch)옵시디언용 고급 검색 플러그인. BM25 알고리즘으로 문서를 점수화하여 가장 관련도 높은 결과를 먼저 보여줍니다. 2023 Obsidian Gems of the Year 수상.는 옵시디언 기본 검색과 무엇이 다를까요? 핵심은 검색 결과의 정렬 방식입니다.
쉽게 말하면... 기본 검색은 "이 단어가 들어있는 노트 목록"을 보여줍니다. 옴니서치는 "이 단어가 제목에 있는 노트가 본문에만 있는 노트보다 더 중요하겠지?"라고 판단해서 똑똑하게 정렬해줍니다.
기능 기본 검색 옴니서치
결과 정렬 기준 제목, 생성일, 수정일 키워드 위치 기반 가중치 (제목 > heading > 본문)
Results Weighting검색어가 문서의 어느 위치(제목, heading, 본문)에 등장하는지에 따라 가중치를 부여하는 기능. 제목에 키워드가 있으면 더 높은 점수를 받습니다. ✓ 커스텀 가중치 설정 가능
PDF / 이미지 검색 ✓ Text Extractor 연동 시 OCR 지원
오타 허용 (Fuzzy)
웹 브라우저 연동 ✓ HTTP Server + Companion
🔍

BM25 알고리즘

검색어 등장 횟수, 문서 길이, 키워드 위치를 종합적으로 분석하여 관련도 점수BM25는 정보 검색(Information Retrieval) 분야의 표준 알고리즘입니다. 구글 등 주요 검색 엔진에서도 유사한 방식을 사용합니다.를 매깁니다.

📄

다양한 파일 지원

마크다운뿐 아니라 PDF, Office 문서, 이미지까지. Text ExtractorOmnisearch와 함께 사용하는 별도 플러그인. 이미지에서 텍스트를 추출(OCR)하고, PDF 내용을 인덱싱합니다. 플러그인과 함께 사용하면 더욱 강력합니다.

고급 검색 문법

따옴표로 정확한 구문 검색, 마이너스(-) 기호로 제외 검색, 파일 확장자 필터링까지 지원합니다.

  • "정확한 구문" 구문 검색
  • -제외어 제외 검색
  • ext:pdf 파일 타입 필터
💡 CMDS Q&A에서

"혹시 옵시디언에서 옴니서치가 기본검색보다 좋은 이유가 뭐가 있나요?"라는 질문에 대해 — 옴니서치의 Results Weighting 설정을 통해 키워드가 제목에 쓰였는지, heading에 쓰였는지, 본문에만 쓰였는지를 평가하여 검색 결과의 순서를 정할 수 있습니다.


구글과 연동하기

구글에서 검색할 때 내 옵시디언 노트도 함께 검색되게 할 수 있습니다. 옴니서치의 HTTP Server옴니서치가 로컬에서 실행하는 작은 웹 서버. 외부 앱이나 브라우저 확장이 이 서버를 통해 옵시디언 검색 결과를 가져갈 수 있습니다. 기능과 Tampermonkey브라우저에서 사용자 정의 스크립트(유저스크립트)를 실행해주는 확장 프로그램. 웹 페이지의 동작을 커스텀할 수 있습니다. 유저스크립트를 활용합니다.
쉽게 말하면... 옴니서치에 "외부에서 검색 가능" 옵션을 켜두면, 브라우저에서 구글 검색할 때 내 옵시디언 노트 중 관련된 것들이 함께 표시됩니다. 마치 구글이 내 노트까지 검색해주는 것 같은 경험입니다.
1

옴니서치 HTTP Server 활성화

옵시디언 설정 → Omnisearch → API Access Through HTTP 섹션에서 HTTP Server를 활성화합니다. 포트 번호(기본 51361)를 기억해두세요.

2

Tampermonkey 설치

크롬 웹 스토어에서 Tampermonkey가장 널리 쓰이는 유저스크립트 매니저. Chrome, Firefox, Safari, Edge 등 대부분의 브라우저에서 사용 가능합니다. 확장 프로그램을 설치합니다. Firefox에서도 사용 가능합니다.

3

유저스크립트 추가

Omnisearch 공식 문서에서 제공하는 유저스크립트를 Tampermonkey에 추가합니다. 이 스크립트는 구글 검색 결과 페이지에 옵시디언 노트 결과를 삽입합니다.

4

검색 테스트

구글에서 검색하면, 옵시디언이 실행 중일 때 검색 결과 상단에 내 노트가 함께 표시됩니다.

Omnisearch Google 연동 데모
Google 검색 결과에 옵시디언 노트가 함께 표시되는 모습 (출처: Omnisearch 공식 문서)
Omnisearch Kagi 연동 데모
Kagi 등 다른 검색 엔진에서도 연동이 가능합니다 (출처: Omnisearch 공식 문서)
💡 알아두면 좋은 것
  • HTTP Server는 로컬 전용입니다. 외부에서 접속할 수 없으므로 보안 걱정 없이 사용하세요.
  • 옵시디언이 실행 중이어야 구글 연동이 작동합니다.
  • Google 외에 Kagi, DuckDuckGo 등 다른 검색 엔진도 지원합니다.
  • Tampermonkey 대신 Omnisearch Companion 브라우저 확장을 사용할 수도 있습니다.

CMDS 개선 유저스크립트

CMDS 커뮤니티에서 Omnisearch 공식 유저스크립트의 디자인과 기능을 개선한 버전을 만들었습니다. 구글 검색 결과와 더 자연스럽게 어울리는 UI를 제공합니다.
쉽게 말하면... 공식 스크립트가 기본적인 텍스트 목록을 보여준다면, CMDS 개선 버전은 구글 검색 결과와 비슷한 스타일의 카드로 옵시디언 노트를 보여줍니다. 더 깔끔하고, 클릭하면 바로 옵시디언에서 해당 노트가 열립니다.
📋

v1 — 기본 개선

공식 유저스크립트를 기반으로 구글 검색 결과 페이지에 자연스럽게 녹아드는 디자인으로 개선한 첫 번째 버전입니다.

  • 검색 결과 사이드바에 노트 목록 표시
  • 노트 제목과 매칭된 텍스트 미리보기
  • 클릭 시 옵시디언에서 바로 열기

v2 — 커스텀 디자인

사용자 피드백을 반영하여 더 세련된 UI와 추가 기능을 포함한 두 번째 버전입니다.

  • 카드형 레이아웃으로 가독성 향상
  • 검색 결과 강조 표시 개선
  • 옵시디언 아이콘 및 볼트 이름 표시
v1 초기 버전 (CMDS v0.1) 스크립트 보기
"use strict"; // ==UserScript== // @name Obsidian Omnisearch in Google CMDS v0.1 // @namespace https://github.com/scambier/userscripts // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js // @version 0.4.2 // @description Injects Obsidian notes in Google search results with modern design // @author Simon Cambier, Modified by CMDSPACE // @match https://google.com/* // @match https://www.google.com/* // @icon https://obsidian.md/favicon.ico // @require https://code.jquery.com/jquery-3.7.1.min.js // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js // @grant GM.xmlHttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // ==/UserScript== /* globals GM_config, jQuery, $, waitForKeyElements */ (function () { "use strict"; // Google's right "sidebar" that will contain the results div const sidebarSelector = "#rhs"; // The results div const resultsDivId = "OmnisearchObsidianResults"; // The "loading"/"no results" label const loadingSpanId = "OmnisearchObsidianLoading"; // Modern styles for the sidebar panel const injectStyles = () => { const style = document.createElement('style'); style.textContent = ` /* Main container styles */ #${resultsDivId} { margin-top: 20px; margin-bottom: 20px; padding: 0; width: 100%; min-width: 360px; box-sizing: border-box; } /* Header section */ #${resultsDivId} .obsidian-header { background: #7C3AED; color: white; padding: 16px 20px; border-radius: 12px 12px 0 0; display: flex; align-items: center; justify-content: space-between; border: 1px solid #7C3AED; border-bottom: none; } #${resultsDivId} .obsidian-header-title { display: flex; align-items: center; gap: 10px; font-size: 16px; font-weight: 600; color: white; } #${resultsDivId} .obsidian-header svg { width: 24px; height: 24px; filter: brightness(0) invert(1); } #${resultsDivId} .obsidian-settings-link { color: white; opacity: 0.9; font-size: 13px; text-decoration: none; transition: opacity 0.2s; } #${resultsDivId} .obsidian-settings-link:hover { opacity: 1; text-decoration: underline; } /* Content container */ #${resultsDivId} .obsidian-content { background: #ffffff; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 12px 12px; padding: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); } /* Result items */ #${resultsDivId} [data-omnisearch-result] { background: #f8f9fa; border-radius: 8px; padding: 14px; margin-bottom: 12px; transition: all 0.2s ease; border: 1px solid transparent; cursor: pointer; } #${resultsDivId} [data-omnisearch-result]:last-child { margin-bottom: 0; } #${resultsDivId} [data-omnisearch-result]:hover { background: #ffffff; border-color: #dadce0; transform: translateX(-2px); box-shadow: 0 2px 8px rgba(0,0,0,0.08); } /* Result title */ #${resultsDivId} .LC20lb { color: #1a73e8; font-size: 15px; font-weight: 500; line-height: 1.4; margin-bottom: 6px; display: block; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #${resultsDivId} a:hover .LC20lb { text-decoration: underline; } /* Meta information */ #${resultsDivId} .result-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; } #${resultsDivId} .result-meta svg { width: 14px; height: 14px; } #${resultsDivId} .VuuXrf { color: #7C3AED; font-size: 12px; font-weight: 500; } #${resultsDivId} .dyjrff { color: #80868b; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; display: block; } /* Excerpt text */ #${resultsDivId} .VwiC3b { color: #4d5156; font-size: 13px; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } /* Loading state */ #${resultsDivId} #${loadingSpanId} { display: block; text-align: center; color: #5f6368; padding: 20px; font-size: 14px; } /* Error state */ #${resultsDivId} .error-message { color: #d93025; padding: 16px; text-align: center; font-size: 14px; } #${resultsDivId} .error-message a { color: #1a73e8; text-decoration: none; } #${resultsDivId} .error-message a:hover { text-decoration: underline; } /* Dark mode support */ @media (prefers-color-scheme: dark) { #${resultsDivId} .obsidian-header { background: #6B2EC5; color: white; border-color: #6B2EC5; } #${resultsDivId} .obsidian-settings-link { color: white; opacity: 0.9; } #${resultsDivId} .obsidian-settings-link:hover { opacity: 1; } #${resultsDivId} .obsidian-content { background: #202124; border-color: #3c4043; } #${resultsDivId} [data-omnisearch-result] { background: #303134; } #${resultsDivId} [data-omnisearch-result]:hover { background: #3c4043; border-color: #5f6368; } #${resultsDivId} .LC20lb { color: #8ab4f8; } #${resultsDivId} .VuuXrf { color: #9974F8; } #${resultsDivId} .dyjrff { color: #9aa0a6; } #${resultsDivId} .VwiC3b { color: #bdc1c6; } #${resultsDivId} #${loadingSpanId} { color: #9aa0a6; } } /* Responsive adjustments */ @media (max-width: 1200px) { #${resultsDivId} .obsidian-header { padding: 14px 16px; } #${resultsDivId} .obsidian-header-title { font-size: 15px; } #${resultsDivId} .obsidian-content { padding: 12px; } } `; document.head.appendChild(style); }; // The `new GM_config()` syntax is not recognized by the TS compiler // @ts-ignore const gmc = new GM_config({ id: "ObsidianOmnisearchGoogle", title: "Omnisearch in Google - Configuration", fields: { port: { label: "HTTP Port", type: "text", default: "51361", }, nbResults: { label: "Number of results to display", type: "int", default: 3, }, }, events: { save: () => { location.reload(); }, init: () => { }, }, }); // Promise resolves when initialization completes const onInit = (config) => new Promise((resolve) => { let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); isInit(); }); // Obsidian logo const logo = `<svg height="1em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 256 256"> <style> .purple { fill: #7C3AED; } @media (prefers-color-scheme: dark) { .purple { fill: #9974F8; } } </style> <path class="purple" d="M94.82 149.44c6.53-1.94 17.13-4.9 29.26-5.71a102.97 102.97 0 0 1-7.64-48.84c1.63-16.51 7.54-30.38 13.25-42.1l3.47-7.14 4.48-9.18c2.35-5 4.08-9.38 4.9-13.56.81-4.07.81-7.64-.2-11.11-1.03-3.47-3.07-7.14-7.15-11.21a17.02 17.02 0 0 0-15.8 3.77l-52.81 47.5a17.12 17.12 0 0 0-5.5 10.2l-4.5 30.18a149.26 149.26 0 0 1 38.24 57.2ZM54.45 106l-1.02 3.06-27.94 62.2a17.33 17.33 0 0 0 3.27 18.96l43.94 45.16a88.7 88.7 0 0 0 8.97-88.5A139.47 139.47 0 0 0 54.45 106Z"/><path class="purple" d="m82.9 240.79 2.34.2c8.26.2 22.33 1.02 33.64 3.06 9.28 1.73 27.73 6.83 42.82 11.21 11.52 3.47 23.45-5.8 25.08-17.73 1.23-8.67 3.57-18.46 7.75-27.53a94.81 94.81 0 0 0-25.9-40.99 56.48 56.48 0 0 0-29.56-13.35 96.55 96.55 0 0 0-40.99 4.79 98.89 98.89 0 0 1-15.29 80.34h.1Z"/><path class="purple" d="M201.87 197.76a574.87 574.87 0 0 0 19.78-31.6 8.67 8.67 0 0 0-.61-9.48 185.58 185.58 0 0 1-21.82-35.9c-5.91-14.16-6.73-36.08-6.83-46.69 0-4.07-1.22-8.05-3.77-11.21l-34.16-43.33c0 1.94-.4 3.87-.81 5.81a76.42 76.42 0 0 1-5.71 15.9l-4.7 9.8-3.36 6.72a111.95 111.95 0 0 0-12.03 38.23 93.9 93.9 0 0 0 8.67 47.92 67.9 67.9 0 0 1 39.56 16.52 99.4 99.4 0 0 1 25.8 37.31Z"/></svg> `; function omnisearch() { const port = gmc.get("port"); const nbResults = gmc.get("nbResults"); // Extract the ?q= part of the URL with URLSearchParams const params = new URLSearchParams(window.location.search); const query = params.get("q"); if (!query) return; injectLoadingLabel(); GM.xmlHttpRequest({ method: "GET", url: `http://localhost:${port}/search?q=${query}`, headers: { "Content-Type": "application/json", }, onload: function (res) { const data = JSON.parse(res.response); removeLoadingLabel(data.length > 0); // Keep the x first results data.splice(nbResults); const resultsContainer = $(`#${resultsDivId} .obsidian-content`); // Delete all existing data-omnisearch-result $("[data-omnisearch-result]").remove(); // Inject results for (const item of data) { const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; const element = $(` <div data-omnisearch-result> <a href="${url}" style="text-decoration: none; color: inherit;"> <h3 class="LC20lb">${item.basename}</h3> <div class="result-meta"> ${logo} <span class="VuuXrf">Obsidian</span> </div> <div class="dyjrff">${item.path}</div> <div class="VwiC3b"> ${item.excerpt.replaceAll("<br />", " ").replaceAll("<br>", " ")} </div> </a> </div> `); resultsContainer.append(element); } }, onerror: function (res) { console.log("Omnisearch error", res); const span = $("#" + loadingSpanId)[0]; if (span) { span.parentElement.innerHTML = ` <div class="error-message"> Error: Obsidian is not running or the Omnisearch server is not enabled. <br /><a href="obsidian://open">Open Obsidian</a> </div> `; } }, }); } function injectTitle() { const id = "OmnisearchObsidianConfig"; if (!$("#" + id)[0]) { const header = $(` <div class="obsidian-header"> <div class="obsidian-header-title"> ${logo} <span>Omnisearch Results</span> </div> <a id="${id}" class="obsidian-settings-link" href="#">Settings</a> </div> `); $(`#${resultsDivId}`).prepend(header); $(document).on("click", "#" + id, function (e) { e.preventDefault(); gmc.open(); }); } } function injectResultsContainer() { const resultsDiv = $(` <div id="${resultsDivId}"> <div class="obsidian-content"></div> </div> `); $(sidebarSelector).append(resultsDiv); // append instead of prepend to put at bottom } function injectLoadingLabel() { if (!$("#" + loadingSpanId)[0]) { const label = $(`<span id="${loadingSpanId}">Loading Obsidian results...</span>`); $(`#${resultsDivId} .obsidian-content`).append(label); } } function removeLoadingLabel(foundResults = true) { if (foundResults) { $("#" + loadingSpanId).remove(); } else { $("#" + loadingSpanId).text("No results found in Obsidian"); } } console.log("Loading Omnisearch injector CMDS v0.1"); let init = onInit(gmc); init.then(() => { // Inject styles injectStyles(); // Make sure the results container is there if (!$(sidebarSelector)[0]) { // Create sidebar with proper width if it doesn't exist $("#rcnt").append('<div id="rhs" style="min-width: 400px; flex-shrink: 0;"></div>'); } injectResultsContainer(); injectTitle(); omnisearch(); // Make an initial call console.log("Loaded Omnisearch injector CMDS v0.1"); // Keep the results at the bottom of sidebar waitForKeyElements(sidebarSelector, () => { // Move to bottom if other elements are added const omnisearchDiv = $(`#${resultsDivId}`); if (omnisearchDiv.next().length > 0) { omnisearchDiv.appendTo(sidebarSelector); } }); }); })();
v2 임영록님 수정 버전 (Custom) 스크립트 보기
"use strict"; // ==UserScript== // @name Obsidian Omnisearch in Google CMDS v0.1 - Custom // @namespace https://github.com/scambier/userscripts // @downloadURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js // @updateURL https://github.com/scambier/userscripts/raw/master/dist/obsidian-omnisearch-google.user.js // @version 0.4.2-custom // @description Injects Obsidian notes in Google search results with modern design - Custom version with dark header and no logos // @author Simon Cambier, Modified by CMDSPACE, Customized for user // @match https://google.com/* // @match https://www.google.com/* // @icon https://obsidian.md/favicon.ico // @require https://code.jquery.com/jquery-3.7.1.min.js // @require https://raw.githubusercontent.com/sizzlemctwizzle/GM_config/master/gm_config.js // @require https://gist.githubusercontent.com/scambier/109932d45b7592d3decf24194008be4d/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js // @grant GM.xmlHttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // ==/UserScript== /* globals GM_config, jQuery, $, waitForKeyElements */ (function () { "use strict"; // Google's right "sidebar" that will contain the results div const sidebarSelector = "#rhs"; // The results div const resultsDivId = "OmnisearchObsidianResults"; // The "loading"/"no results" label const loadingSpanId = "OmnisearchObsidianLoading"; // Modern styles for the sidebar panel - CUSTOMIZED VERSION const injectStyles = () => { const style = document.createElement('style'); style.textContent = ` /* Main container styles */ #${resultsDivId} { margin-top: 20px; margin-bottom: 20px; padding: 0; width: 100%; min-width: 360px; box-sizing: border-box; } /* Header section - CUSTOMIZED: Light blue background without border */ #${resultsDivId} .obsidian-header { background: #F2F6FF; color: #1B0CAB; padding: 16px 20px; border-radius: 20px 20px 0 0; display: flex; align-items: center; justify-content: space-between; border: none; } #${resultsDivId} .obsidian-header-title { display: flex; align-items: center; gap: 10px; font-size: 16px; font-weight: normal; color: #1B0CAB; } /* Logo in header */ #${resultsDivId} .obsidian-header svg { width: 20px; height: 20px; } #${resultsDivId} .obsidian-settings-link { color: #1B0CAB; opacity: 0.7; font-size: 13px; text-decoration: none; transition: opacity 0.2s; } #${resultsDivId} .obsidian-settings-link:hover { opacity: 1; color: #1B0CAB; text-decoration: underline; } /* Content container */ #${resultsDivId} .obsidian-content { background: #F2F6FF; border: none; border-top: none; border-radius: 0 0 20px 20px; padding: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); } /* Result items */ #${resultsDivId} [data-omnisearch-result] { background: #ffffff; border-radius: 8px; padding: 14px; margin-bottom: 12px; transition: all 0.2s ease; border: none; cursor: pointer; } #${resultsDivId} [data-omnisearch-result]:last-child { margin-bottom: 0; } #${resultsDivId} [data-omnisearch-result]:hover { background: #ffffff; transform: translateX(-2px); box-shadow: 0 2px 8px rgba(0,0,0,0.08); } /* Result title - CUSTOMIZED: Smaller font size and reduced margin */ #${resultsDivId} .LC20lb { color: #1B0CAB; font-size: 12px; font-weight: 500; line-height: 1.4; margin-bottom: 3px; display: block; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #${resultsDivId} a:hover .LC20lb { text-decoration: underline; } /* Meta information - CUSTOMIZED: Hide logos */ #${resultsDivId} .result-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; } /* CUSTOMIZED: Hide all logos in result items */ #${resultsDivId} .result-meta svg { display: none !important; } #${resultsDivId} .VuuXrf { color: #374151; font-size: 12px; font-weight: 500; } #${resultsDivId} .dyjrff { color: #80868b; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; display: block; } /* Excerpt text - CUSTOMIZED: Highlight search terms, 4 lines */ #${resultsDivId} .VwiC3b { color: #4d5156; font-size: 13px; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 8px; font-weight: 500; } /* Search term highlighting */ #${resultsDivId} .search-highlight { font-weight: bold; background: rgba(255, 255, 0, 0.2); padding: 1px 2px; border-radius: 2px; } /* Loading state */ #${resultsDivId} #${loadingSpanId} { display: block; text-align: center; color: #5f6368; padding: 20px; font-size: 14px; } /* Error state */ #${resultsDivId} .error-message { color: #d93025; padding: 16px; text-align: center; font-size: 14px; } #${resultsDivId} .error-message a { color: #1a73e8; text-decoration: none; } #${resultsDivId} .error-message a:hover { text-decoration: underline; } /* Dark mode support - CUSTOMIZED: Unified color scheme */ @media (prefers-color-scheme: dark) { /* Main container - no border */ #${resultsDivId} { border: none; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } /* Header - Unified color */ #${resultsDivId} .obsidian-header { background: #2C303D; color: #e2e8f0; border: none; } #${resultsDivId} .obsidian-header-title { color: #e2e8f0; } #${resultsDivId} .obsidian-settings-link { color: #e2e8f0; opacity: 0.7; } #${resultsDivId} .obsidian-settings-link:hover { color: #e2e8f0; opacity: 1; } /* Content container - Unified color */ #${resultsDivId} .obsidian-content { background: #2C303D; border: none; } /* Individual result items - Unified color with subtle distinction */ #${resultsDivId} [data-omnisearch-result] { background: #2C303D; border-bottom: 1px solid rgba(255, 255, 255, 0.03); } #${resultsDivId} [data-omnisearch-result]:last-child { border-bottom: none; } #${resultsDivId} [data-omnisearch-result]:hover { background: #363A47; transform: translateX(-2px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } /* Text colors */ #${resultsDivId} .LC20lb { color: #9974F8; } #${resultsDivId} .VuuXrf { color: #a0aec0; } #${resultsDivId} .dyjrff { color: #9aa0a6; } #${resultsDivId} .VwiC3b { color: #bdc1c6; } #${resultsDivId} #${loadingSpanId} { color: #9aa0a6; } /* Search highlighting in dark mode */ #${resultsDivId} .search-highlight { background: rgba(153, 116, 248, 0.3); color: #e2e8f0; } } /* Responsive adjustments */ @media (max-width: 1200px) { #${resultsDivId} .obsidian-header { padding: 14px 16px; } #${resultsDivId} .obsidian-header-title { font-size: 15px; } #${resultsDivId} .obsidian-content { padding: 12px; } } `; document.head.appendChild(style); }; // The `new GM_config()` syntax is not recognized by the TS compiler // @ts-ignore const gmc = new GM_config({ id: "ObsidianOmnisearchGoogle", title: "Omnisearch in Google - Configuration", fields: { port: { label: "HTTP Port", type: "text", default: "51361", }, nbResults: { label: "Number of results to display", type: "int", default: 3, }, }, events: { save: () => { location.reload(); }, init: () => { }, }, }); // Promise resolves when initialization completes const onInit = (config) => new Promise((resolve) => { let isInit = () => setTimeout(() => (config.isInit ? resolve() : isInit()), 0); isInit(); }); // Obsidian logo SVG const logo = `<svg height="1em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 256 256"> <style> .purple { fill: #1B0CAB; } @media (prefers-color-scheme: dark) { .purple { fill: #9974F8; } } </style> <path class="purple" d="M94.82 149.44c6.53-1.94 17.13-4.9 29.26-5.71a102.97 102.97 0 0 1-7.64-48.84c1.63-16.51 7.54-30.38 13.25-42.1l3.47-7.14 4.48-9.18c2.35-5 4.08-9.38 4.9-13.56.81-4.07.81-7.64-.2-11.11-1.03-3.47-3.07-7.14-7.15-11.21a17.02 17.02 0 0 0-15.8 3.77l-52.81 47.5a17.12 17.12 0 0 0-5.5 10.2l-4.5 30.18a149.26 149.26 0 0 1 38.24 57.2ZM54.45 106l-1.02 3.06-27.94 62.2a17.33 17.33 0 0 0 3.27 18.96l43.94 45.16a88.7 88.7 0 0 0 8.97-88.5A139.47 139.47 0 0 0 54.45 106Z"/><path class="purple" d="m82.9 240.79 2.34.2c8.26.2 22.33 1.02 33.64 3.06 9.28 1.73 27.73 6.83 42.82 11.21 11.52 3.47 23.45-5.8 25.08-17.73 1.23-8.67 3.57-18.46 7.75-27.53a94.81 94.81 0 0 0-25.9-40.99 56.48 56.48 0 0 0-29.56-13.35 96.55 96.55 0 0 0-40.99 4.79 98.89 98.89 0 0 1-15.29 80.34h.1Z"/><path class="purple" d="M201.87 197.76a574.87 574.87 0 0 0 19.78-31.6 8.67 8.67 0 0 0-.61-9.48 185.58 185.58 0 0 1-21.82-35.9c-5.91-14.16-6.73-36.08-6.83-46.69 0-4.07-1.22-8.05-3.77-11.21l-34.16-43.33c0 1.94-.4 3.87-.81 5.81a76.42 76.42 0 0 1-5.71 15.9l-4.7 9.8-3.36 6.72a111.95 111.95 0 0 0-12.03 38.23 93.9 93.9 0 0 0 8.67 47.92 67.9 67.9 0 0 1 39.56 16.52 99.4 99.4 0 0 1 25.8 37.31Z"/></svg>`; function omnisearch() { const port = gmc.get("port"); const nbResults = gmc.get("nbResults"); // Extract the ?q= part of the URL with URLSearchParams const params = new URLSearchParams(window.location.search); const query = params.get("q"); if (!query) return; injectLoadingLabel(); GM.xmlHttpRequest({ method: "GET", url: `http://localhost:${port}/search?q=${query}`, headers: { "Content-Type": "application/json", }, onload: function (res) { const data = JSON.parse(res.response); removeLoadingLabel(data.length > 0); // Keep the x first results data.splice(nbResults); const resultsContainer = $(`#${resultsDivId} .obsidian-content`); // Delete all existing data-omnisearch-result $("[data-omnisearch-result]").remove(); // Inject results - CUSTOMIZED: New order and search highlighting for (const item of data) { const url = `obsidian://open?vault=${encodeURIComponent(item.vault)}&file=${encodeURIComponent(item.path)}`; // Highlight search terms in excerpt let highlightedExcerpt = item.excerpt.replaceAll("<br />", " ").replaceAll("<br>", " "); if (query) { const queryTerms = query.split(' ').filter(term => term.length > 1); queryTerms.forEach(term => { const regex = new RegExp(`(${term})`, 'gi'); highlightedExcerpt = highlightedExcerpt.replace(regex, '<span class="search-highlight">$1</span>'); }); } const element = $(` <div data-omnisearch-result> <a href="${url}" style="text-decoration: none; color: inherit;"> <div class="VwiC3b"> ${highlightedExcerpt} </div> <h3 class="LC20lb">${item.basename}</h3> <div class="dyjrff">${item.path}</div> </a> </div> `); resultsContainer.append(element); } }, onerror: function (res) { console.log("Omnisearch error", res); const span = $("#" + loadingSpanId)[0]; if (span) { span.parentElement.innerHTML = ` <div class="error-message"> Error: Obsidian is not running or the Omnisearch server is not enabled. <br /><a href="obsidian://open">Open Obsidian</a> </div> `; } }, }); } function injectTitle() { const id = "OmnisearchObsidianConfig"; if (!$("#" + id)[0]) { // CUSTOMIZED: Header with logo const header = $(` <div class="obsidian-header"> <div class="obsidian-header-title"> ${logo} <span>Omnisearch Results</span> </div> <a id="${id}" class="obsidian-settings-link" href="#">Settings</a> </div> `); $(`#${resultsDivId}`).prepend(header); $(document).on("click", "#" + id, function (e) { e.preventDefault(); gmc.open(); }); } } function injectResultsContainer() { const resultsDiv = $(` <div id="${resultsDivId}"> <div class="obsidian-content"></div> </div> `); $(sidebarSelector).append(resultsDiv); // append instead of prepend to put at bottom } function injectLoadingLabel() { if (!$("#" + loadingSpanId)[0]) { const label = $(`<span id="${loadingSpanId}">Loading Obsidian results...</span>`); $(`#${resultsDivId} .obsidian-content`).append(label); } } function removeLoadingLabel(foundResults = true) { if (foundResults) { $("#" + loadingSpanId).remove(); } else { $("#" + loadingSpanId).text("No results found in Obsidian"); } } console.log("Loading Omnisearch injector CMDS v0.1 - Custom version"); let init = onInit(gmc); init.then(() => { // Inject styles injectStyles(); // Make sure the results container is there if (!$(sidebarSelector)[0]) { // Create sidebar with proper width if it doesn't exist $("#rcnt").append('<div id="rhs" style="min-width: 400px; flex-shrink: 0;"></div>'); } injectResultsContainer(); injectTitle(); omnisearch(); // Make an initial call console.log("Loaded Omnisearch injector CMDS v0.1 - Custom version"); // Keep the results at the bottom of sidebar waitForKeyElements(sidebarSelector, () => { // Move to bottom if other elements are added const omnisearchDiv = $(`#${resultsDivId}`); if (omnisearchDiv.next().length > 0) { omnisearchDiv.appendTo(sidebarSelector); } }); }); })();
CMDS 유저스크립트 v1 결과
CMDS 개선 유저스크립트로 구글 검색 시 옵시디언 노트가 표시되는 모습 (출처: CMDS Slashpage)
CMDS 유저스크립트 v2 커스텀 디자인
v2 커스텀 디자인 — 구글 검색 결과와 자연스럽게 조화되는 카드 UI (출처: CMDS Slashpage)

🔍 왜 유저스크립트를 커스텀할까?

Omnisearch 공식 유저스크립트는 기본적인 기능만 제공합니다. 검색 결과의 디자인이 구글 UI와 이질적이거나, 노트 미리보기가 부족할 수 있습니다. CMDS 커뮤니티에서는 이런 불편함을 해결하기 위해 직접 스크립트를 개선했고, 그 결과물을 커뮤니티에 공유하고 있습니다. 이것이 바로 커뮤니티 기반 지식 공유의 힘입니다.

💡 설치 방법
  • CMDS Slashpage에서 스크립트 전문을 확인하세요.
  • Tampermonkey 대시보드에서 "새 스크립트 추가"를 클릭하고 코드를 붙여넣습니다.
  • 옴니서치의 HTTP Server가 활성화되어 있어야 합니다 (Section 2 참고).
  • 스크립트가 활성화된 상태로 구글 검색하면 바로 동작합니다.

관련 링크

더 자세한 내용은 아래 공식 문서와 가이드를 참고하세요.