diff --git a/app/appearance/langs/de_DE.json b/app/appearance/langs/de_DE.json index a72e28d3a..c5c93a521 100644 --- a/app/appearance/langs/de_DE.json +++ b/app/appearance/langs/de_DE.json @@ -531,6 +531,7 @@ "syncConfGuide5": "Wenn die Datenmenge groß ist, wird die erste Synchronisation langsam sein. Bitte haben Sie Geduld
Wechseln Sie nicht zwischen Apps und halten Sie den Bildschirm während der Synchronisation des iOS/iPad hell.", "copyPlainText": "Reinen Text kopieren", "findInDoc": "Übereinstimmungen ${y} Blöcke in ${x} Dokumenten", + "matchDoc": "${x} Dokumente gefunden", "jumpToParentNext": "Springen Sie zum nächsten Block des übergeordneten Blocks", "jumpToParentPrev": "Springen Sie zum vorherigen Block des übergeordneten Blocks", "jumpToParent": "Springe zu übergeordnetem Block", diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 6239f36ea..25a3c5b57 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -531,6 +531,7 @@ "syncConfGuide5": "If the amount of data is large, the first sync will be slow, please wait patiently
Do not switch apps and keep the screen bright while the iOS/iPad is syncing", "copyPlainText": "Copy plain text", "findInDoc": "Matches ${y} blocks in ${x} documents", + "matchDoc": "Matched ${x} documents", "jumpToParentNext": "Jump to the next block of the parent block", "jumpToParentPrev": "Jump to the previous block of the parent block", "jumpToParent": "Jump to parent block", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index eb49aa480..44d69b692 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -531,6 +531,7 @@ "syncConfGuide5": "Si la cantidad de datos es grande, la primera sincronización será lenta, espere pacientemente
No cambie de aplicación y mantenga la pantalla brillante mientras iOS/iPad se sincroniza", "copyPlainText": "Copiar texto sin formato", "findInDoc": "Coincide con bloques ${y} en documentos ${x}", + "matchDoc": "Se encontraron ${x} documentos", "jumpToParentNext": "Saltar al siguiente bloque del bloque principal", "jumpToParentPrev": "Saltar al bloque anterior del bloque principal", "jumpToParent": "Saltar al bloque principal", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index e41d3c0c2..d9b0bd114 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -531,6 +531,7 @@ "syncConfGuide5": "Si la quantité de données est importante, la première synchronisation sera lente, veuillez patienter
Ne changez pas d'application et gardez l'écran lumineux pendant la synchronisation de l'iOS/iPad", "copyPlainText": "Copier du texte brut", "findInDoc": "Correspond à ${y} blocs dans ${x} documents", + "matchDoc": "${x} documents trouvés", "jumpToParentNext": "Sauter au bloc suivant du bloc parent", "jumpToParentPrev": "Sauter au bloc précédent du bloc parent", "jumpToParent": "Sauter au bloc parent", diff --git a/app/appearance/langs/he_IL.json b/app/appearance/langs/he_IL.json index fe724425e..379914ce6 100644 --- a/app/appearance/langs/he_IL.json +++ b/app/appearance/langs/he_IL.json @@ -531,6 +531,7 @@ "syncConfGuide5": "אם הכמות של הנתונים גבוהה, הסנכרון הראשון יתקשח, אנא חכה בסבלנות
אל תחליף אפליקציות ושמור על המסך מואר בזמן שמכשירי iOS/iPad בתהליך סנכרון", "copyPlainText": "העתק טקסט פשוט", "findInDoc": "התאמות ${y} בלוקים ב${x} מסמכים", + "matchDoc": "נמצאו ${x} מסמכים", "jumpToParentNext": "קפוץ לבלוק הבא של הבלוק ההורה", "jumpToParentPrev": "קפוץ לבלוק הקודם של הבלוק ההורה", "jumpToParent": "קפוץ לבלוק ההורה", diff --git a/app/appearance/langs/it_IT.json b/app/appearance/langs/it_IT.json index c18f3fd27..ceb9dccc5 100644 --- a/app/appearance/langs/it_IT.json +++ b/app/appearance/langs/it_IT.json @@ -531,6 +531,7 @@ "syncConfGuide5": "Se la quantità di dati è grande, la prima sincronizzazione sarà lenta, si prega di aspettare con pazienza
Non passare ad altre app e mantieni lo schermo acceso mentre si sincronizza su iOS/iPad", "copyPlainText": "Copia testo semplice", "findInDoc": "Corrispondenze ${y} blocchi in ${x} documenti", + "matchDoc": "Trovati ${x} documenti", "jumpToParentNext": "Salta al blocco successivo del blocco genitore", "jumpToParentPrev": "Salta al blocco precedente del blocco genitore", "jumpToParent": "Salta al blocco genitore", diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json index b24fc20a3..938cf910f 100644 --- a/app/appearance/langs/ja_JP.json +++ b/app/appearance/langs/ja_JP.json @@ -531,6 +531,7 @@ "syncConfGuide5": "データ量が多い場合は最初の同期が遅くなる場合がありますのでしばらくお待ちください。
iOS/iPad で同期している間はアプリを切り替えず画面を明るく保ってください", "copyPlainText": "プレーンテキストとしてコピー", "findInDoc": "${x} 個のドキュメントの ${y} ブロックと一致します", + "matchDoc": "${x} 件のドキュメントが見つかりました", "jumpToParentNext": "次の親ブロックへ移動", "jumpToParentPrev": "前の親ブロックへ移動", "jumpToParent": "親ブロックへ移動", diff --git a/app/appearance/langs/pl_PL.json b/app/appearance/langs/pl_PL.json index 13dddb52f..fc1edf051 100644 --- a/app/appearance/langs/pl_PL.json +++ b/app/appearance/langs/pl_PL.json @@ -531,6 +531,7 @@ "syncConfGuide5": "Jeśli ilość danych jest duża, pierwsza synchronizacja będzie wolna, proszę czekać cierpliwie
Nie przełączaj aplikacji i utrzymuj ekran włączony, podczas synchronizacji iOS/iPad", "copyPlainText": "Kopiuj tekst zwykły", "findInDoc": "Zgadza się ${y} bloków w ${x} dokumentach", + "matchDoc": "Znaleziono ${x} dokumentów", "jumpToParentNext": "Przejdź do następnego bloku rodzica", "jumpToParentPrev": "Przejdź do poprzedniego bloku rodzica", "jumpToParent": "Przejdź do bloku rodzica", diff --git a/app/appearance/langs/ru_RU.json b/app/appearance/langs/ru_RU.json index 61ca2949b..b57afbb06 100644 --- a/app/appearance/langs/ru_RU.json +++ b/app/appearance/langs/ru_RU.json @@ -531,6 +531,7 @@ "syncConfGuide5": "Если объем данных большой, первая синхронизация будет медленной, пожалуйста, ожидайте терпеливо
Не переключайте приложения и поддерживайте экран ярким, пока iOS/iPad синхронизирует", "copyPlainText": "Скопировать обычный текст", "findInDoc": "Соответствует ${y} блокам в ${x} документах", + "matchDoc": "Найдено ${x} документов", "jumpToParentNext": "Перейти к следующему блоку родительского блока", "jumpToParentPrev": "Перейти к предыдущему блоку родительского блока", "jumpToParent": "Перейти к родительскому блоку", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 856e055f9..5052eeb5a 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -531,6 +531,7 @@ "syncConfGuide5": "如果資料量較大,第一次同步會比較慢,請耐心等待
iOS/iPad 端在同步時請勿切換應用並保持螢幕恆亮", "copyPlainText": "複製純文字", "findInDoc": "${x} 個文件中符合 ${y} 個塊", + "matchDoc": "匹配到 ${x} 個文檔", "jumpToParentNext": "跳到父塊的下一個塊", "jumpToParentPrev": "跳到父塊的上一個塊", "jumpToParent": "跳到父塊", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index c47f278ca..7a240203a 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -531,6 +531,7 @@ "syncConfGuide5": "如果数据量较大,第一次同步会比较慢,请耐心等待
iOS/iPad 端在同步时请勿切换应用并保持亮屏", "copyPlainText": "复制纯文本", "findInDoc": "${x} 个文档中匹配 ${y} 个块", + "matchDoc": "匹配到 ${x} 个文档", "jumpToParentNext": "跳转到父块的下一个块", "jumpToParentPrev": "跳转到父块的上一个块", "jumpToParent": "跳转到父块", diff --git a/app/src/index.ts b/app/src/index.ts index 6285b4434..454f56bac 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -17,7 +17,9 @@ import { progressBackgroundTask, progressLoading, progressStatus, - reloadSync, setDefRefCount, setRefDynamicText, + reloadSync, + setDefRefCount, + setRefDynamicText, setTitle, transactionError } from "./dialog/processSystem"; @@ -166,6 +168,7 @@ export class App { addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); window.siyuan.config = response.data.conf; + window.siyuan.isPublish = response.data.isPublish; await loadPlugins(this); getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages: IObject) => { diff --git a/app/src/menus/commonMenuItem.ts b/app/src/menus/commonMenuItem.ts index cc2468ae7..0c7674114 100644 --- a/app/src/menus/commonMenuItem.ts +++ b/app/src/menus/commonMenuItem.ts @@ -446,6 +446,9 @@ export const copySubMenu = (id: string, accelerator = true, focusElement?: Eleme }; export const exportMd = (id: string) => { + if (window.siyuan.isPublish) { + return; + } return new MenuItem({ id: "export", label: window.siyuan.languages.export, diff --git a/app/src/mobile/index.ts b/app/src/mobile/index.ts index 0c46f3cfd..180ae733a 100644 --- a/app/src/mobile/index.ts +++ b/app/src/mobile/index.ts @@ -97,6 +97,7 @@ class App { addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); window.siyuan.config = confResponse.data.conf; + window.siyuan.isPublish = confResponse.data.isPublish; correctHotkey(siyuanApp); await loadPlugins(this); getLocalStorage(() => { diff --git a/app/src/mobile/menu/search.ts b/app/src/mobile/menu/search.ts index 2eede267e..b63744516 100644 --- a/app/src/mobile/menu/search.ts +++ b/app/src/mobile/menu/search.ts @@ -204,7 +204,11 @@ ${unicode2Emoji(childItem.ial.icon, "b3-list-item__graphic", true)} listElement.scrollTop = 0; let countHTML = ""; if (response) { - countHTML = `${window.siyuan.languages.findInDoc.replace("${x}", response.data.matchedRootCount).replace("${y}", response.data.matchedBlockCount)} + let text = window.siyuan.languages.findInDoc.replace("${x}", response.data.matchedRootCount).replace("${y}", response.data.matchedBlockCount); + if (response.data.docMode) { + text = window.siyuan.languages.matchDoc.replace("${x}", response.data.matchedRootCount); + } + countHTML = `${text} ${config.page}/${response.data.pageCount || 1}`; } diff --git a/app/src/search/util.ts b/app/src/search/util.ts index 15672fdaa..4631b95d7 100644 --- a/app/src/search/util.ts +++ b/app/src/search/util.ts @@ -1367,8 +1367,12 @@ export const inputEvent = (element: Element, config: Config.IUILayoutTabSearchCo nextElement.setAttribute("disabled", "disabled"); } onSearch(response.data.blocks, edit, element, config); + let text = window.siyuan.languages.findInDoc.replace("${x}", response.data.matchedRootCount).replace("${y}", response.data.matchedBlockCount); + if (response.data.docMode) { + text = window.siyuan.languages.matchDoc.replace("${x}", response.data.matchedRootCount); + } searchResultElement.innerHTML = `${config.page}/${response.data.pageCount || 1} -${window.siyuan.languages.findInDoc.replace("${x}", response.data.matchedRootCount).replace("${y}", response.data.matchedBlockCount)}`; +${text}`; loadingElement.classList.add("fn__none"); searchResultElement.setAttribute("data-pagecount", response.data.pageCount || 1); }); diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 044b10a3e..e36aa83be 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -432,7 +432,11 @@ interface ISiyuan { bookmarkLabel?: string[] blockPanels: import("../block/Panel").BlockPanel[], dialogs: import("../dialog").Dialog[], - viewer?: Viewer + viewer?: Viewer, + /** + * 是否在发布服务下访问 + */ + isPublish?: boolean; } interface IOperation { diff --git a/app/src/window/index.ts b/app/src/window/index.ts index 3f28f41b3..d8a6d8765 100644 --- a/app/src/window/index.ts +++ b/app/src/window/index.ts @@ -9,9 +9,13 @@ import {fetchGet, fetchPost} from "../util/fetch"; import {addBaseURL, setNoteBook} from "../util/pathName"; import {openFileById} from "../editor/util"; import { - processSync, progressBackgroundTask, + processSync, + progressBackgroundTask, progressLoading, - progressStatus, reloadSync, setDefRefCount, setRefDynamicText, + progressStatus, + reloadSync, + setDefRefCount, + setRefDynamicText, setTitle, transactionError } from "../dialog/processSystem"; @@ -151,6 +155,7 @@ class App { addScriptSync(`${Constants.PROTYLE_CDN}/js/lute/lute.min.js?v=${Constants.SIYUAN_VERSION}`, "protyleLuteScript"); addScript(`${Constants.PROTYLE_CDN}/js/protyle-html.js?v=${Constants.SIYUAN_VERSION}`, "protyleWcHtmlScript"); window.siyuan.config = response.data.conf; + window.siyuan.isPublish = response.data.isPublish; await loadPlugins(this); getLocalStorage(() => { fetchGet(`/appearance/langs/${window.siyuan.config.appearance.lang}.json?v=${Constants.SIYUAN_VERSION}`, (lauguages: IObject) => { diff --git a/kernel/api/router.go b/kernel/api/router.go index 8632aca40..a315d5293 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -90,7 +90,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/notebook/renameNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameNotebook) ginServer.Handle("POST", "/api/notebook/changeSortNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, changeSortNotebook) ginServer.Handle("POST", "/api/notebook/setNotebookIcon", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNotebookIcon) - ginServer.Handle("POST", "/api/notebook/getNotebookInfo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getNotebookInfo) + ginServer.Handle("POST", "/api/notebook/getNotebookInfo", model.CheckAuth, model.CheckReadonly, getNotebookInfo) ginServer.Handle("POST", "/api/filetree/searchDocs", model.CheckAuth, searchDocs) ginServer.Handle("POST", "/api/filetree/listDocsByPath", model.CheckAuth, listDocsByPath) @@ -276,33 +276,33 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/asset/fullReindexAssetContent", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, fullReindexAssetContent) ginServer.Handle("POST", "/api/asset/statAsset", model.CheckAuth, model.CheckAdminRole, statAsset) - ginServer.Handle("POST", "/api/export/batchExportMd", model.CheckAuth, batchExportMd) - ginServer.Handle("POST", "/api/export/exportMd", model.CheckAuth, exportMd) - ginServer.Handle("POST", "/api/export/exportSY", model.CheckAuth, exportSY) - ginServer.Handle("POST", "/api/export/exportNotebookSY", model.CheckAuth, exportNotebookSY) - ginServer.Handle("POST", "/api/export/exportMdContent", model.CheckAuth, exportMdContent) - ginServer.Handle("POST", "/api/export/exportHTML", model.CheckAuth, exportHTML) - ginServer.Handle("POST", "/api/export/exportPreviewHTML", model.CheckAuth, exportPreviewHTML) - ginServer.Handle("POST", "/api/export/exportMdHTML", model.CheckAuth, exportMdHTML) - ginServer.Handle("POST", "/api/export/exportDocx", model.CheckAuth, exportDocx) - ginServer.Handle("POST", "/api/export/processPDF", model.CheckAuth, processPDF) + ginServer.Handle("POST", "/api/export/batchExportMd", model.CheckAuth, model.CheckAdminRole, batchExportMd) + ginServer.Handle("POST", "/api/export/exportMd", model.CheckAuth, model.CheckAdminRole, exportMd) + ginServer.Handle("POST", "/api/export/exportSY", model.CheckAuth, model.CheckAdminRole, exportSY) + ginServer.Handle("POST", "/api/export/exportNotebookSY", model.CheckAuth, model.CheckAdminRole, exportNotebookSY) + ginServer.Handle("POST", "/api/export/exportMdContent", model.CheckAuth, model.CheckAdminRole, exportMdContent) + ginServer.Handle("POST", "/api/export/exportHTML", model.CheckAuth, model.CheckAdminRole, exportHTML) + ginServer.Handle("POST", "/api/export/exportPreviewHTML", model.CheckAuth, model.CheckAdminRole, exportPreviewHTML) + ginServer.Handle("POST", "/api/export/exportMdHTML", model.CheckAuth, model.CheckAdminRole, exportMdHTML) + ginServer.Handle("POST", "/api/export/exportDocx", model.CheckAuth, model.CheckAdminRole, exportDocx) + ginServer.Handle("POST", "/api/export/processPDF", model.CheckAuth, model.CheckAdminRole, processPDF) ginServer.Handle("POST", "/api/export/preview", model.CheckAuth, exportPreview) - ginServer.Handle("POST", "/api/export/exportResources", model.CheckAuth, exportResources) - ginServer.Handle("POST", "/api/export/exportAsFile", model.CheckAuth, exportAsFile) - ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData) - ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, exportDataInFolder) - ginServer.Handle("POST", "/api/export/exportTempContent", model.CheckAuth, exportTempContent) + ginServer.Handle("POST", "/api/export/exportResources", model.CheckAuth, model.CheckAdminRole, exportResources) + ginServer.Handle("POST", "/api/export/exportAsFile", model.CheckAuth, model.CheckAdminRole, exportAsFile) + ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, model.CheckAdminRole, exportData) + ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, model.CheckAdminRole, exportDataInFolder) + ginServer.Handle("POST", "/api/export/exportTempContent", model.CheckAuth, model.CheckAdminRole, exportTempContent) ginServer.Handle("POST", "/api/export/export2Liandi", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, export2Liandi) - ginServer.Handle("POST", "/api/export/exportReStructuredText", model.CheckAuth, exportReStructuredText) - ginServer.Handle("POST", "/api/export/exportAsciiDoc", model.CheckAuth, exportAsciiDoc) - ginServer.Handle("POST", "/api/export/exportTextile", model.CheckAuth, exportTextile) - ginServer.Handle("POST", "/api/export/exportOPML", model.CheckAuth, exportOPML) - ginServer.Handle("POST", "/api/export/exportOrgMode", model.CheckAuth, exportOrgMode) - ginServer.Handle("POST", "/api/export/exportMediaWiki", model.CheckAuth, exportMediaWiki) - ginServer.Handle("POST", "/api/export/exportODT", model.CheckAuth, exportODT) - ginServer.Handle("POST", "/api/export/exportRTF", model.CheckAuth, exportRTF) - ginServer.Handle("POST", "/api/export/exportEPUB", model.CheckAuth, exportEPUB) - ginServer.Handle("POST", "/api/export/exportAttributeView", model.CheckAuth, exportAttributeView) + ginServer.Handle("POST", "/api/export/exportReStructuredText", model.CheckAuth, model.CheckAdminRole, exportReStructuredText) + ginServer.Handle("POST", "/api/export/exportAsciiDoc", model.CheckAuth, model.CheckAdminRole, exportAsciiDoc) + ginServer.Handle("POST", "/api/export/exportTextile", model.CheckAuth, model.CheckAdminRole, exportTextile) + ginServer.Handle("POST", "/api/export/exportOPML", model.CheckAuth, model.CheckAdminRole, exportOPML) + ginServer.Handle("POST", "/api/export/exportOrgMode", model.CheckAuth, model.CheckAdminRole, exportOrgMode) + ginServer.Handle("POST", "/api/export/exportMediaWiki", model.CheckAuth, model.CheckAdminRole, exportMediaWiki) + ginServer.Handle("POST", "/api/export/exportODT", model.CheckAuth, model.CheckAdminRole, exportODT) + ginServer.Handle("POST", "/api/export/exportRTF", model.CheckAuth, model.CheckAdminRole, exportRTF) + ginServer.Handle("POST", "/api/export/exportEPUB", model.CheckAuth, model.CheckAdminRole, exportEPUB) + ginServer.Handle("POST", "/api/export/exportAttributeView", model.CheckAuth, model.CheckAdminRole, exportAttributeView) ginServer.Handle("POST", "/api/import/importStdMd", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importStdMd) ginServer.Handle("POST", "/api/import/importData", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importData) diff --git a/kernel/api/search.go b/kernel/api/search.go index 2c16a4697..e4b567461 100644 --- a/kernel/api/search.go +++ b/kernel/api/search.go @@ -369,12 +369,13 @@ func fullTextSearchBlock(c *gin.Context) { } page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg) - blocks, matchedBlockCount, matchedRootCount, pageCount := model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize) + blocks, matchedBlockCount, matchedRootCount, pageCount, docMode := model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize) ret.Data = map[string]interface{}{ "blocks": blocks, "matchedBlockCount": matchedBlockCount, "matchedRootCount": matchedRootCount, "pageCount": pageCount, + "docMode": docMode, } } diff --git a/kernel/api/system.go b/kernel/api/system.go index c13608557..5f1e66a34 100644 --- a/kernel/api/system.go +++ b/kernel/api/system.go @@ -429,7 +429,8 @@ func getConf(c *gin.Context) { // REF: https://github.com/siyuan-note/siyuan/issues/11364 role := model.GetGinContextRole(c) - if model.IsReadOnlyRole(role) { + isPublish := model.IsReadOnlyRole(role) + if isPublish { maskedConf.ReadOnly = true } if !model.IsValidRole(role, []model.Role{ @@ -439,8 +440,9 @@ func getConf(c *gin.Context) { } ret.Data = map[string]interface{}{ - "conf": maskedConf, - "start": !util.IsUILoaded, + "conf": maskedConf, + "start": !util.IsUILoaded, + "isPublish": isPublish, } } diff --git a/kernel/model/search.go b/kernel/model/search.go index c5c94b6f1..aab108d56 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -476,7 +476,7 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids if 1 > len(ids) { // `Replace All` is no longer affected by pagination https://github.com/siyuan-note/siyuan/issues/8265 - blocks, _, _, _ := FullTextSearchBlock(keyword, boxes, paths, types, method, orderBy, groupBy, 1, math.MaxInt) + blocks, _, _, _, _ := FullTextSearchBlock(keyword, boxes, paths, types, method, orderBy, groupBy, 1, math.MaxInt) for _, block := range blocks { ids = append(ids, block.ID) } @@ -903,7 +903,7 @@ func replaceNodeTokens(n *ast.Node, method int, keyword string, replacement stri // method:0:关键字,1:查询语法,2:SQL,3:正则表达式 // orderBy: 0:按块类型(默认),1:按创建时间升序,2:按创建时间降序,3:按更新时间升序,4:按更新时间降序,5:按内容顺序(仅在按文档分组时),6:按相关度升序,7:按相关度降序 // groupBy:0:不分组,1:按文档分组 -func FullTextSearchBlock(query string, boxes, paths []string, types map[string]bool, method, orderBy, groupBy, page, pageSize int) (ret []*Block, matchedBlockCount, matchedRootCount, pageCount int) { +func FullTextSearchBlock(query string, boxes, paths []string, types map[string]bool, method, orderBy, groupBy, page, pageSize int) (ret []*Block, matchedBlockCount, matchedRootCount, pageCount int, docMode bool) { ret = []*Block{} if "" == query { return @@ -945,7 +945,12 @@ func FullTextSearchBlock(query string, boxes, paths []string, types map[string]b typeFilter := buildTypeFilter(types) boxFilter := buildBoxesFilter(boxes) pathFilter := buildPathsFilter(paths) - blocks, matchedBlockCount, matchedRootCount = fullTextSearchByKeyword(query, boxFilter, pathFilter, typeFilter, ignoreFilter, orderByClause, beforeLen, page, pageSize) + if 2 > len(strings.Split(query, " ")) { + blocks, matchedBlockCount, matchedRootCount = fullTextSearchByQuerySyntax(query, boxFilter, pathFilter, typeFilter, ignoreFilter, orderByClause, beforeLen, page, pageSize) + } else { + docMode = true // 文档全文搜索模式 https://github.com/siyuan-note/siyuan/issues/10584 + blocks, matchedBlockCount, matchedRootCount = fullTextSearchByKeyword(query, boxFilter, pathFilter, typeFilter, ignoreFilter, orderByClause, beforeLen, page, pageSize) + } } pageCount = (matchedBlockCount + pageSize - 1) / pageSize @@ -1324,6 +1329,7 @@ func fullTextSearchCountByRegexp(exp, boxFilter, pathFilter, typeFilter, ignoreF } func fullTextSearchByFTS(query, boxFilter, pathFilter, typeFilter, ignoreFilter, orderBy string, beforeLen, page, pageSize int) (ret []*Block, matchedBlockCount, matchedRootCount int) { + start := time.Now() query = stringQuery(query) table := "blocks_fts" // 大小写敏感 if !Conf.Search.CaseSensitive { @@ -1349,20 +1355,11 @@ func fullTextSearchByFTS(query, boxFilter, pathFilter, typeFilter, ignoreFilter, } matchedBlockCount, matchedRootCount = fullTextSearchCountByFTS(query, boxFilter, pathFilter, typeFilter, ignoreFilter) + logging.LogInfof("time cost [fts]: %v", time.Since(start)) return } func fullTextSearchCountByFTS(query, boxFilter, pathFilter, typeFilter, ignoreFilter string) (matchedBlockCount, matchedRootCount int) { - if ast.IsNodeIDPattern(query) { - ret, _ := sql.QueryNoLimit("SELECT COUNT(id) AS `matches`, COUNT(DISTINCT(root_id)) AS `docs` FROM `blocks` WHERE `id` = '" + query + "'") - if 1 > len(ret) { - return - } - matchedBlockCount = int(ret[0]["matches"].(int64)) - matchedRootCount = int(ret[0]["docs"].(int64)) - return - } - table := "blocks_fts" // 大小写敏感 if !Conf.Search.CaseSensitive { table = "blocks_fts_case_insensitive" @@ -1403,43 +1400,17 @@ func fullTextSearchByFTSWithRoot(query, boxFilter, pathFilter, typeFilter, ignor " GROUP BY root_id HAVING " + likeFilter + "ORDER BY " + orderByLike + " DESC, MAX(updated) DESC" cteStmt := "WITH docBlocks AS (" + dMatchStmt + ")" likeFilter = strings.ReplaceAll(likeFilter, "GROUP_CONCAT("+contentField+")", "concatContent") + limit := " LIMIT " + strconv.Itoa(pageSize) + " OFFSET " + strconv.Itoa((page-1)*pageSize) selectStmt := cteStmt + "\nSELECT *, " + "(" + contentField + ") AS concatContent, " + "(SELECT COUNT(root_id) FROM docBlocks) AS docs, " + "(CASE WHEN (root_id IN (SELECT root_id FROM docBlocks) AND (" + strings.ReplaceAll(likeFilter, "concatContent", contentField) + ")) THEN 1 ELSE 0 END) AS blockSort" + " FROM blocks WHERE type IN " + typeFilter + boxFilter + pathFilter + ignoreFilter + - " AND (id IN (SELECT root_id FROM docBlocks) OR" + - " (root_id IN (SELECT root_id FROM docBlocks) AND (" + likeFilter + ")))" - selectStmt += " " + strings.Replace(orderBy, "END ASC, ", "END ASC, blockSort DESC, ", 1) + - " LIMIT " + strconv.Itoa(pageSize) + " OFFSET " + strconv.Itoa((page-1)*pageSize) - result, _ := sql.Query(selectStmt, -1) - var resultBlocks []*sql.Block - for _, row := range result { - b := &sql.Block{ - ID: row["id"].(string), - ParentID: row["parent_id"].(string), - RootID: row["root_id"].(string), - Hash: row["hash"].(string), - Box: row["box"].(string), - Path: row["path"].(string), - HPath: row["hpath"].(string), - Name: row["name"].(string), - Alias: row["alias"].(string), - Memo: row["memo"].(string), - Tag: row["tag"].(string), - Content: row["content"].(string), - FContent: row["fcontent"].(string), - Markdown: row["markdown"].(string), - Length: int(row["length"].(int64)), - Type: row["type"].(string), - SubType: row["subtype"].(string), - IAL: row["ial"].(string), - Sort: int(row["sort"].(int64)), - Created: row["created"].(string), - Updated: row["updated"].(string), - } - resultBlocks = append(resultBlocks, b) - } + " AND (id IN (SELECT root_id FROM docBlocks " + limit + ") OR" + + " (root_id IN (SELECT root_id FROM docBlocks" + limit + ") AND (" + likeFilter + ")))" + selectStmt += " " + strings.Replace(orderBy, "END ASC, ", "END ASC, blockSort DESC, ", 1) + result, _ := sql.QueryNoLimit(selectStmt) + resultBlocks := sql.ToBlocks(result) if 0 < len(resultBlocks) { matchedRootCount = int(result[0]["docs"].(int64)) matchedBlockCount = matchedRootCount @@ -1452,7 +1423,7 @@ func fullTextSearchByFTSWithRoot(query, boxFilter, pathFilter, typeFilter, ignor ret = []*Block{} } - logging.LogInfof("time cost [search]: %v", time.Since(start)) + logging.LogInfof("time cost [like]: %v", time.Since(start)) return } diff --git a/kernel/sql/block_query.go b/kernel/sql/block_query.go index e9843e8f7..796235eeb 100644 --- a/kernel/sql/block_query.go +++ b/kernel/sql/block_query.go @@ -457,6 +457,36 @@ func Query(stmt string, limit int) (ret []map[string]interface{}, err error) { return } +func ToBlocks(result []map[string]interface{}) (ret []*Block) { + for _, row := range result { + b := &Block{ + ID: row["id"].(string), + ParentID: row["parent_id"].(string), + RootID: row["root_id"].(string), + Hash: row["hash"].(string), + Box: row["box"].(string), + Path: row["path"].(string), + HPath: row["hpath"].(string), + Name: row["name"].(string), + Alias: row["alias"].(string), + Memo: row["memo"].(string), + Tag: row["tag"].(string), + Content: row["content"].(string), + FContent: row["fcontent"].(string), + Markdown: row["markdown"].(string), + Length: int(row["length"].(int64)), + Type: row["type"].(string), + SubType: row["subtype"].(string), + IAL: row["ial"].(string), + Sort: int(row["sort"].(int64)), + Created: row["created"].(string), + Updated: row["updated"].(string), + } + ret = append(ret, b) + } + return +} + func getLimitClause(parsedStmt sqlparser.Statement, limit int) (ret *sqlparser.Limit) { switch parsedStmt.(type) { case *sqlparser.Select: