一個純 JavaScript、輕量級的 Ghost 瀏覽器內全文搜尋插件(部落格)
SearchinGhost 是一款專用於 Ghost 部落格平台的輕量級可擴展搜尋引擎。它的核心是使用 Ghost Content API 載入您的部落格內容,並使用強大的 FlexSearch 庫來索引和執行搜尋查詢。
一切都發生在客戶端瀏覽器中,它幫助我們提供極快的搜尋結果並將其實時顯示給您的用戶(也稱為「鍵入時搜尋」)。我們也透過依賴瀏覽器localStorage
僅在必要時發送請求來盡量減少網路負載。
您的部落格使用西里爾文、中文、韓文、希臘文、印度文或任何其他非拉丁語言?不用擔心,它是支援的,請參閱專用部分。
額外獎勵:如果您喜歡這個概念,但希望快速輕鬆地安裝它(真的,不到 3 分鐘!),請訪問 SearchinGhostEasy 專案。
在深入安裝和配置之前,請透過此現場演示親自嘗試。
在此示範中,可搜尋的內容來自官方 Ghost 演示 API(即 https://demo.ghost.io)。選項設定為預設值,以便將每個輸入單字搜尋到貼文標題、標籤、摘錄和主要內容。
例如,搜尋單字“marmalade”。它不存在於任何帖子標題、摘錄或標籤中,但在“Down The Rabbit Hole”文章中使用過一次,這就是為什麼您會得到它的結果。
首先,更新主題的default.hbs
檔案以包含輸入欄位和輸出元素以顯示搜尋結果。然後,新增 SearchinGhost 腳本的連結並使用您自己的CONTENT_API_KEY
對其進行初始化。若要取得內容 API 金鑰,請參閱 Ghost 官方文件。
< input id =" search-bar " >
< ul id =" search-results " > </ ul >
< script src =" https://cdn.jsdelivr.net/npm/[email protected]/dist/searchinghost.min.js " > </ script >
< script >
var searchinGhost = new SearchinGhost ( {
key : 'CONTENT_API_KEY'
} ) ;
</ script >
就這樣,一切都完成了!如果您需要更細粒度的配置,請閱讀接下來的部分。
您可以使用多種方法安裝 SearchinGhost,以下是可能的方法:
這是安裝 SearchinGhost 最簡單且首選的方法。將這些腳本之一添加到您的主題default.hbs
中。我們還建議使用 jsdelivr 而不是 unpkg,因為它具有可靠性和性能。
< script src =" https://cdn.jsdelivr.net/npm/[email protected]/dist/searchinghost.min.js " > </ script >
<!-- OR -->
< script src =" https://unpkg.com/[email protected]/dist/searchinghost.min.js " > </ script >
如果您想從自己的伺服器提供 SearchinGhost 或將其包含在建置過程中,您可以從發布頁面資產中取得它或下載dist/searchinghost.min.js
檔案。
安裝 SearchinGhost 作為專案依賴項。
$ npm install searchinghost
# OR
$ yarn add searchinghost
然後,從任何 Javascript 檔案載入它。
import SearchinGhost from 'searchinghost' ;
// OR
var SearchinGhost = require ( 'searchinghost' ) ;
唯一的強製配置欄位是key
。任何其他欄位都有預設值並且變為可選。
SearchinGhost 被設計成開箱即用,這種最小的配置卻很強大!每次擊鍵時,它都會搜尋貼文標題、標籤、摘錄和內容。這是預設行為,因為它似乎是最常見的。
// SearchinGhost minimal configuration
var searchinGhost = new SearchinGhost ( {
key : '<CONTENT_API_KEY>'
} ) ;
儘管如此,為了滿足您的需求,一些額外的配置可能是值得的。假設您只想搜尋title
並顯示找到的每個貼文的title
和published_at
欄位。您可以使用此配置:
var searchinGhost = new SearchinGhost ( {
key : '<CONTENT_API_KEY>' ,
postsFields : [ 'title' , 'url' , 'published_at' ] ,
postsExtraFields : [ ] ,
postsFormats : [ ] ,
indexedFields : [ 'title' ] ,
template : function ( post ) {
return `<a href=" ${ post . url } "> ${ post . published_at } - ${ post . title } </a>`
}
} ) ;
SearchinGhost 透過其配置可以輕鬆自訂和擴展,請花點時間研究下一節中的每個選項。
{
key : '' ,
url : window . location . origin ,
version : 'v3' ,
loadOn : 'focus' ,
searchOn : 'keyup' ,
limit : 10 ,
inputId : [ 'search-bar' ] ,
outputId : [ 'search-results' ] ,
outputChildsType : 'li' ,
postsFields : [ 'title' , 'url' , 'excerpt' , 'custom_excerpt' , 'published_at' , 'feature_image' ] ,
postsExtraFields : [ 'tags' ] ,
postsFormats : [ 'plaintext' ] ,
indexedFields : [ 'title' , 'string_tags' , 'excerpt' , 'plaintext' ] ,
template : function ( post ) {
var o = `<a href=" ${ post . url } ">`
if ( post . feature_image ) o += `<img src=" ${ post . feature_image } ">`
o += '<section>'
if ( post . tags . length > 0 ) {
o += `<header>
<span class="head-tags"> ${ post . tags [ 0 ] . name } </span>
<span class="head-date"> ${ post . published_at } </span>
</header>`
} else {
o += `<header>
<span class="head-tags">UNKNOWN</span>
<span class="head-date"> ${ post . published_at } </span>
</header>`
}
o += `<h2> ${ post . title } </h2>`
o += `</section></a>`
return o ;
} ,
emptyTemplate : function ( ) { } ,
customProcessing : function ( post ) {
if ( post . tags ) post . string_tags = post . tags . map ( o => o . name ) . join ( ' ' ) . toLowerCase ( ) ;
return post ;
} ,
date : {
locale : document . documentElement . lang || "en-US" ,
options : { year : 'numeric' , month : 'short' , day : 'numeric' }
} ,
cacheMaxAge : 1800 ,
onFetchStart : function ( ) { } ,
onFetchEnd : function ( posts ) { } ,
onIndexBuildStart : function ( ) { } ,
onIndexBuildEnd : function ( index ) { } ,
onSearchStart : function ( ) { } ,
onSearchEnd : function ( posts ) { } ,
indexOptions : { } ,
searchOptions : { } ,
debug : false
} ) ;
用於存取部落格資料的公共內容 API 金鑰。
例如:
'22444f78447824223cefc48062'
Ghost API 的完整網域。
範例:
'https://demo.ghost.io'
預設值:
window.location.origin
設定 Ghost API 版本。同時使用
'v2'
和'v3'
。預設值:
'v3'
設定庫載入策略。它可以在 HTML 頁面載入時觸發,也可以在使用者點擊搜尋欄時按需觸發,也可以從不觸發。
若要自行觸發搜尋欄初始化,請將此值設為
false
(布林值)。這樣,當其餘程式碼準備就緒時,您可以呼叫searchinGhost.loadData()
。預期值:
'page'
、'focus'
或false
預設值:
'focus'
選擇何時執行搜尋查詢。要在每個使用者按鍵和表單提交時進行搜索,請使用
'keyup'
。要僅在使用者透過按鈕提交表單或輸入「回車」鍵時進行搜索,請使用'submit'
。如果您想從自己的 javascript 程式碼中完全控制它,請使用false
(布林值)並使用searchinGhost.search("...")
自行執行搜尋。預期值:
'keyup'
、'submit'
或false
預設值:
'keyup'
設定搜尋查詢傳回的最大貼文數。
1
到50
之間的任何值都將快如閃電,低於1000
的值不應過多降低性能。但請記住,當搜尋引擎達到此限制時,它會停止挖掘並返回結果:越低越好。儘管強烈建議不要這樣做,但請將此值設為
0
以顯示所有可用結果。預設值:
10
[已棄用] 在
v1.6.0
之前,該欄位是string
,此行為已被棄用。您的網站可能有一個或多個搜尋欄,每個搜尋欄都必須有一個唯一的 HTML
id
屬性。將每個搜尋欄id
放入此數組中。名稱中請勿包含「#」。如果不需要任何輸入字段,請將值設為
[]
(空數組),並將searchOn
設為false
(布爾值)。然後,使用searchinGhost.search("<your query>")
執行搜尋。預設值:
['search-bar']
[已棄用] 在
v1.6.0
之前,該欄位是string
,此行為已被棄用。您的網站可以使用一個或多個 HTML 元素來顯示搜尋結果。該數組引用所有這些輸出元素的
id
屬性。如果這些元素中的任何一個已經有內容,它將被搜尋結果覆蓋。如果您使用JS框架顯示搜尋結果,請將此值設為
[]
(空數組)。您將獲得作為函數searchinGhost.search("<your query>")
傳回的值找到的貼文。預設值:
['search-results']
[已棄用] 在
v1.6.0
之前,該欄位是string
。這已被棄用。每個搜尋結果在加入
outputId
父元素之前都包裝在子元素內。預設類型是li
,但您可以將其設定為任何有效的 HTML 元素(請參閱 MDN 文件)。如果您不想使用包裝元素將
template
和emptyTemplate
的結果直接附加到輸出元素,請將該值設為false
(布林值)。預設值:
'li'
所有所需貼文欄位的陣列。所有這些欄位都將在
template
功能中可用,以顯示有用的貼文資訊。參考“fields”官方文件。
注意:如果您使用
'custom_excerpt'
,其內容將自動放入'excerpt'
中以使模板化更容易。預設值:
['title', 'url', 'excerpt', 'custom_excerpt', 'published_at', 'feature_image']
此數組允許您使用額外的字段,例如
tags
或authors
。我個人不知道為什麼它們不與其他“字段”在一起,但 Ghost API 是這樣設計的...將其值設為
[]
(空數組)以完全停用它。參考“include”官方文件。
預設值:
['tags']
這對應於「格式」Ghost API,它允許使用 HTML 或純文字取得貼文內容。
將其值設為
[]
(空數組)以完全停用它。請參考“formats”官方文件。
預設值:
['plaintext']
索引欄位列表。所有這些欄位的內容都將是可搜尋的。
此清單中的所有值都必須在帖子中定義。否則,搜尋結果將不準確,但應用程式不會崩潰!仔細檢查
postsFields
、postsExtraFields
和postsFormats
值。注意:
'string_tags'
奇怪的欄位被加入到customProcessing
選項中。如果你想使用標籤,這個醜陋的東西是必要的(目前),因為 FlexSearch 無法正確處理陣列。如果您不想要/喜歡它,請覆蓋customProcessing
以僅返回posts
而不進行額外修改。如果您決定使用標籤,也請在此使用'string_tags'
。預設值:
['title', 'string_tags', 'excerpt', 'plaintext']
定義您自己的結果範本。此範本將用於找到的每個帖子以產生結果,並作為子元素附加到輸出元素。沒有模板引擎,只有一個使用
post
物件作為參數的本機 JavaScript 函數。這個模板選項比您想像的要強大得多。我們也可以將其視為對搜尋結果呼叫的自訂處理函數。例如,如果您想做一些過濾,則不傳回任何內容(例如
return;
)或傳回空字串(例如return "";
)以丟棄項目。請注意使用反引號(例如“`”)而不是單/雙引號。這是啟用非常有用的 javascript 變數插值所必需的。
可用變數是
postsFields
選項中定義的變數。例子:
template: function ( post ) { return `<a href=" ${ post . url } "># ${ post . tags } - ${ post . published_at } - ${ post . title } </a>` }
當沒有找到結果時,定義您自己的結果範本。
例子:
emptyTemplate: function ( ) { return '<p>Sorry, nothing found...</p>' }
您需要對從 Ghost 獲取的帖子資料進行一些額外的修改嗎?使用此功能可以完成您需要的任何操作。此函數在每個帖子上調用,在
onFetchEnd()
之後和onIndexBuildStart()
之前執行。如果您想丟棄帖子,請返回任何 JS 假值(例如
null
、undefined
、false
、""
等)。若要輕鬆偵錯輸入/輸出,請使用
onFetchEnd()
和onIndexBuildEnd()
透過console.log()
顯示結果。如果您是更高級的用戶,最好的選擇仍然是使用偵錯器。另外,測試時不要忘記清理本地快取!注意:預設情況下,此選項已填入輔助函數,以便更輕鬆地在貼文中使用「標籤」欄位。請參閱
indexedFields
選項。例子:
customProcessing: function ( post ) { post . extra_field = "hello" ; return post ; }
定義從貼文中取得的日期格式。
請參閱 MDN 參考以獲取更多資訊。
例子:
date: { locale : "fr-FR" , options : { weekday : 'long' , year : 'numeric' , month : 'long' , day : 'numeric' } }
設定快取最長期限(以秒為單位)。在此期間,如果在本機儲存中找到已經存在的索引,則將載入該索引,而無需任何額外的 HTTP 請求來確認其有效性。當快取被清除時,該值會被重置。
這對於節省伺服器的寬頻和網路負載特別有用。預設值設定為半小時。該值來自 Google Analytics 使用的預設使用者會話持續時間。
預設值:
1800
在從 Ghost API 取得資料之前定義一個回調函數。
此函數不帶任何參數。
例子:
onFetchStart: function ( ) { console . log ( "before data fetch" ) ; }
定義獲取完成時的回呼函數。即使對
posts
所做的修改得以保留,我們也建議使用customProcessing()
函數來執行此操作。函數採用一個參數:Ghost 本身傳回的所有貼文的陣列。
例子:
onFetchEnd: function ( posts ) { console . log ( "Total posts found on Ghost:" , posts . length ) ; }
在開始建立搜尋索引之前定義一個回調函數。
此函數不帶參數。
例子:
onIndexBuildStart: function ( ) { console . log ( "before building the index" ) ; }
定義搜尋索引建置完成時的回呼函數。
此函數採用一個參數:建構 FlexSearch 索引物件。
例子:
onIndexBuildEnd: function ( index ) { console . log ( "index built:" , index ) ; }
在開始執行搜尋查詢之前定義回調函數。例如,它可用於在等待
onSearchEnd
完成時隱藏結果 HTML 元素或添加任何花哨的過渡效果。但在大多數情況下,這是沒有必要的,因為搜尋功能夠快,對眼睛來說很舒服。此函數不帶參數。
例子:
onSearchStart: function ( ) { console . log ( "before executing the search query" ) ; }
當搜尋結果準備好時定義回調函數。
此函數採用 1 個參數:符合貼文的陣列。
例子:
onSearchEnd: function ( posts ) { console . log ( "search complete, posts found:" , posts ) ; }
新增額外的搜尋索引配置或覆蓋預設配置。這些選項將與已提供的選項合併:
{ doc : { id : "id" , field : this . config . indexedFields } , encode : "simple" , tokenize : "forward" , threshold : 0 , resolution : 4 , depth : 0 }也可以使用此參數來啟用非拉丁語言支持,請參閱本節。
預設:
{}
專為進階使用者設計,讓您微調搜尋查詢。請參閱此 FlexSearch 文件。
我們使用這個特定的查詢結構:
index.search("your query", searchOptions)
因此新增至searchOptions
任何內容都會以這種方式傳遞到 FlexSearch。當根據標籤過濾貼文時,此參數非常方便。舉個例子:
searchOptions: { where : { string_tags : "getting started" } }另請注意,
limit
Searchinghost 選項會自動合併到searchOptions
。在我們的例子中,它最終會變成:searchOptions: { where : { string_tags : "getting started" } , limit : 10 }預設:
{}
當某些內容未按預期工作時,設定為
true
以顯示應用程式日誌。預設值:
false
如果您的部落格使用拉丁字母語言(例如英語、法語、西班牙語)或北歐/東歐語言(例如德語、瑞典語、匈牙利語、斯洛維尼亞語、愛沙尼亞語),則預設配置將正常運作。在其他情況下,找到適當的indexOptions
值並將其新增至您的主要SearchinGhost配置中。
要建立您自己的特定設置,請參閱 FlexSearch 自述文件和這三個問題。
如果沒有什麼對你有用或結果行為不正確,請隨意創建一個問題。
indexOptions: {
encode : false ,
rtl : true ,
split : / s+ /
}
indexOptions: {
encode : false ,
tokenize : function ( str ) {
return str . replace ( / [x00-x7F] / g , "" ) . split ( "" ) ;
}
}
任何使用複雜字元的空格分隔單字語言都可以使用此選項。
indexOptions: {
encode : false ,
split : / s+ /
}
如果您需要使用多種語言類型(例如西里爾文/英語或印度語/西班牙語),請使用下方的專用設定。我知道,乍一看可能看起來很嚇人,但只需複製/貼上它並相信我。
indexOptions: {
split : / s+ / ,
encode : function ( str ) {
var regexp_replacements = {
"a" : / [àáâãäå] / g ,
"e" : / [èéêë] / g ,
"i" : / [ìíîï] / g ,
"o" : / [òóôõöő] / g ,
"u" : / [ùúûüű] / g ,
"y" : / [ýŷÿ] / g ,
"n" : / ñ / g ,
"c" : / [ç] / g ,
"s" : / ß / g ,
" " : / [-/] / g ,
"" : / ['!"#$%&\()*+,-./:;<=>?@[]^_`{|}~] / g ,
" " : / s+ / g ,
}
str = str . toLowerCase ( ) ;
for ( var key of Object . keys ( regexp_replacements ) ) {
str = str . replace ( regexp_replacements [ key ] , key ) ;
}
return str === " " ? "" : str ;
}
}
首先,我們也嘗試了其他解決方案:Lunr.js、minisearch 和 fusion.js。最後,FlexSearch 提供了最佳的整體結果,結果快速且準確,捆綁包大小足夠小,而且易於設定/配置。一切都需要選擇!
不用擔心,這是正常的。 SearchinGhost 使用快取系統將您的部落格資料儲存在瀏覽器中,從而限制網路互動。預設情況下,30 分鐘內儲存的快取資料仍被視為有效。此後,您將可以看到新文章。
請記住,其他用戶可能不需要等待 30 分鐘,具體取決於他們上次進行研究的時間。如果您是 1 小時前,他們的快取將被清除並更新,以便顯示該文章。
如果您希望您的使用者始終保持最新狀態,請將cacheMaxAge
設定為0
。這樣做時,您還應該將loadOn
設定為'focus'
以限制 HTTP 請求的數量。
預設情況下,當您使用feature_image
URL 變數在搜尋結果中顯示圖像時,您將始終獲得原始/全尺寸的圖像,並且它們通常對於我們的需求來說太大(尺寸和重量),微型會更好合身。
從 Ghost V3 開始,嵌入了媒體處理引擎來創建響應式圖像。預設情況下,Ghost 會重新建立給定映像的 6 個不同映像。可用尺寸有: w30
、 w100
、 w300
、 w600
、 w1000
、 w2000
。
在我們的例子中,更快地載入圖像的最簡單方法就是使用較小的圖像。基本上,我們希望此 URL https://www.example.fr/content/images/2020/05/picture.jpg
(預設從 Ghost API 取得)變為https://www.example.fr/content/images/size/w600/2020/05/picture.jpg
(寬度為 600 像素)。
為此,請透過使用以下程式碼範例新增"customProcessing"
欄位來更新配置。當然,您可以使用上面提到的任何可用大小來代替w600
。
customProcessing: function ( post ) {
if ( post . tags ) post . string_tags = post . tags . map ( o => o . name ) . join ( ' ' ) . toLowerCase ( ) ;
if ( post . feature_image ) post . feature_image = post . feature_image . replace ( '/images/' , '/images/size/w600/' ) ; // reduce image size to w600
return post ;
}
此修改不是立即的,您需要刷新快取才能真正看到差異。
建立一個 ID 為"search-counter"
HTML 元素,並利用onSearchEnd()
函數來用結果填滿它。這是一個例子:
< p id =" search-counter " > </ p >
onSearchEnd: function ( posts ) {
var counterEl = document . getElementById ( 'search-counter' ) ;
counterEl . textContent = ` ${ posts . length } posts found` ;
}
是的,透過使用 SearchinGhost 內部方法,但這是可能的。它可能看起來像黑魔法,但將下面的程式碼添加到您當前的配置中。這裡, searchinGhost
指的是您自己使用new SearchinGhost(...)
建立的實例。
emptyTemplate: function ( ) {
var allPostsArray = Object . values ( searchinGhost . index . l ) ;
var latestPosts = allPostsArray . slice ( 0 , 6 ) ;
searchinGhost . display ( latestPosts ) ;
}
如果使用 React、Vue 或 Angular 等框架,您可能不會想要讓 SearchinGhost 自行操作 DOM。因為您肯定需要在框架內保留任何內容更新,所以您應該使用以下配置:
var searchinGhost = new SearchinGhost ( {
key : '<CONTENT_API_KEY>' ,
inputId : false ,
outputId : false ,
[ ... ]
} ) ;
現在,要執行搜尋查詢,請呼叫此 SearchinGhost 方法:
var postsFound = searchinGhost . search ( "my query" ) ;
// Where 'postsFound' content looks like:
[
{
"title" : "A Full and Comprehensive Style Test" ,
"published_at" : "Sep 1, 2012" ,
[ ... ]
} ,
{
"title" : "Publishing options" ,
"published_at" : "Aug 20, 2018" ,
[ ... ]
}
]
這樣,就不會在您背後渲染任何內容,並且一切都將在 ShadowDom 中控制。
debug: true
onFetchStart()
、 onSearchStart()
) 等從現在開始,任何修改都會在此專用的 CHANGELOG.md 檔案中進行追蹤。
任何貢獻都非常受歡迎!如果您發現錯誤或想要改進程式碼,請隨時建立問題或 PR。
所有程式碼更新必須在src
目錄下完成。
要自己建置項目,請運行:
$ npm install
$ npm run build
開發時,請使用 watch 命令,它會在每次文件修改時更快地重建,並包含來源映射的鏈接,使調試更容易。
$ npm run watch
注意:在建立此專案時,我使用 Node v12.16.2 和 NPM v6.14.4,但也應該適用於舊/新版本
SearchinGhost 在 Ghost 搜尋外掛領域並不孤單。這是其他相關項目的簡短清單。您絕對應該嘗試一下,看看它們是否更適合您的需求:
幽靈獵人 (v0.6.0 - 101 kB, 26 kB gzip)
優點:
- 最著名的,很多關於它的文章和教程
- 基於localStorage的強大快取系統
- 全文索引(不僅是貼文標題)
缺點:
- 依賴 jQuery
- 僅適用於 Ghost v2 API(目前)
- 隨著時間的推移,原始碼變得混亂
幽靈搜尋(v1.1.0 - 12 kB,4.2 kB gzip)
優點:
- 編寫良好且易於閱讀的程式碼庫
- 利用「模糊」功能
缺點:
- 搜尋長單字時瀏覽器滯後
- 可能發送太多 API 請求
- 不使用評分系統首先顯示最好的結果
幽靈查找器 (v3.1.2 - 459 kB, 116 kB gzip)
優點:
- 純 Javascript 庫
缺點:
- 最終捆綁尺寸巨大
- 為每個按下的鍵發送一個 HTTP 請求!
- 不使用搜尋引擎,僅查找貼文標題中的子字串
- 無法正確索引重音字元(例如“é”應與“e”一起找到)