mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-29 21:08:49 +01:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
76da700991
29 changed files with 264 additions and 150 deletions
|
|
@ -1120,6 +1120,7 @@
|
|||
"193": "Please refer to the [Artificial Intelligence] chapter of the User Guide for configuration",
|
||||
"194": "There are restrictions on the Nutstore interface, please use other WebDAV services",
|
||||
"195": "The system time is incorrect, please calibrate the system time and try again",
|
||||
"196": "Do not set the workspace under the path of a third-party sync disk, otherwise the data will be damaged (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)"
|
||||
"196": "Do not set the workspace under the path of a third-party sync disk, otherwise the data will be damaged (iCloud/OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)",
|
||||
"197": "Currently the editor is in read-only mode. If you need to edit content, please switch to edit mode"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1120,6 +1120,7 @@
|
|||
"193": "Consulte el capítulo [Inteligencia artificial] de la guía del usuario para la configuración",
|
||||
"194": "Existen restricciones en la interfaz de Nutstore, utilice otros servicios WebDAV",
|
||||
"195": "La hora del sistema es incorrecta, calibre la hora del sistema y vuelva a intentarlo",
|
||||
"196": "No configure el espacio de trabajo bajo la ruta de un disco de sincronización de terceros, de lo contrario, los datos se dañarán (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)"
|
||||
"196": "No configure el espacio de trabajo bajo la ruta de un disco de sincronización de terceros, de lo contrario, los datos se dañarán (iCloud/OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)",
|
||||
"197": "Actualmente, el editor se encuentra en modo de solo lectura. Si necesita editar contenido, cambie al modo de edición"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1120,6 +1120,7 @@
|
|||
"193": "Veuillez vous référer au chapitre [Intelligence Artificielle] du guide de l'utilisateur pour la configuration",
|
||||
"194": "Il existe des restrictions sur l'interface Nutstore, veuillez utiliser d'autres services WebDAV",
|
||||
"195": "L'heure du système est incorrecte, veuillez calibrer l'heure du système et réessayer",
|
||||
"196": "Ne définissez pas l'espace de travail sous le chemin d'un disque de synchronisation tiers, sinon les données seront endommagées (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)"
|
||||
"196": "Ne définissez pas l'espace de travail sous le chemin d'un disque de synchronisation tiers, sinon les données seront endommagées (iCloud/OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)",
|
||||
"197": "Actuellement, l'éditeur est en mode lecture seule. Si vous devez modifier le contenu, veuillez passer en mode édition"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1120,6 +1120,7 @@
|
|||
"193": "請先參考用戶指南 [人工智能] 章節進行配置",
|
||||
"194": "堅果雲接口存在限制,請使用其他 WebDAV 服務",
|
||||
"195": "系統時間不正確,請校準系統時間後再試",
|
||||
"196": "請勿將工作空間設置在第三方同步盤路徑下,否則數據會被損壞(OneDrive/Dropbox/Google Drive/堅果雲/百度網盤/騰訊微雲等)"
|
||||
"196": "請勿將工作空間設置在第三方同步盤路徑下,否則數據會被損壞(iCloud/OneDrive/Dropbox/Google Drive/堅果雲/百度網盤/騰訊微雲等)",
|
||||
"197": "目前編輯器正處於只讀模式狀態,如果需要編輯內容,請切換到編輯模式"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1120,6 +1120,7 @@
|
|||
"193": "请先参考用户指南 [人工智能] 章节进行配置",
|
||||
"194": "坚果云接口存在限制,请使用其他 WebDAV 服务",
|
||||
"195": "系统时间不正确,请校准系统时间后再试",
|
||||
"196": "请勿将工作空间设置在第三方同步盘路径下,否则数据会被损坏(OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等)"
|
||||
"196": "请勿将工作空间设置在第三方同步盘路径下,否则数据会被损坏(iCloud/OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等)",
|
||||
"197": "目前编辑器正处于只读模式状态,如果需要编辑内容,请切换到编辑模式"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,98 +285,112 @@
|
|||
</svg>
|
||||
<div class="drag"></div>
|
||||
<script>
|
||||
const getSearch = (key) => {
|
||||
if (window.location.search.indexOf('?') === -1) {
|
||||
return ''
|
||||
}
|
||||
let value = ''
|
||||
const data = window.location.search.split('?')[1].split('&')
|
||||
data.find(item => {
|
||||
const keyValue = item.split('=')
|
||||
if (keyValue[0] === key) {
|
||||
value = keyValue[1]
|
||||
return true
|
||||
}
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
const setLang = (lang) => {
|
||||
if (lang === 'zh_CN') {
|
||||
document.title = `思源笔记 v${getSearch('v')}`
|
||||
document.getElementById('zhCN').classList.remove('fn__none')
|
||||
document.getElementById('enUS').classList.add('fn__none')
|
||||
} else {
|
||||
document.title = `SiYuan v${getSearch('v')}`
|
||||
document.getElementById('zhCN').classList.add('fn__none')
|
||||
document.getElementById('enUS').classList.remove('fn__none')
|
||||
}
|
||||
}
|
||||
|
||||
let lang = decodeURIComponent(getSearch('lang'));
|
||||
setLang(lang)
|
||||
|
||||
document.querySelectorAll('.version').forEach(item => {
|
||||
item.textContent = `🔖 v${getSearch('v')}`
|
||||
})
|
||||
|
||||
document.querySelectorAll('.icon').forEach(item => {
|
||||
item.src = decodeURIComponent(`${getSearch('icon')}`)
|
||||
})
|
||||
|
||||
document.querySelectorAll('.lang').forEach(item => {
|
||||
item.value = lang
|
||||
item.addEventListener('change', () => {
|
||||
document.querySelectorAll('.lang').forEach(item1 => {
|
||||
item1.value = item.value
|
||||
})
|
||||
setLang(item.value)
|
||||
})
|
||||
})
|
||||
|
||||
document.getElementById('close').addEventListener('click', () => {
|
||||
const {ipcRenderer} = require('electron')
|
||||
ipcRenderer.send('siyuan-first-quit')
|
||||
})
|
||||
document.getElementById('min').addEventListener('click', () => {
|
||||
const {getCurrentWindow} = require('@electron/remote')
|
||||
getCurrentWindow().minimize()
|
||||
})
|
||||
|
||||
document.querySelectorAll('.choosePath').forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
const {dialog} = require('@electron/remote')
|
||||
const path = require('path')
|
||||
dialog.showOpenDialog({
|
||||
defaultPath: path.join(decodeURIComponent(getSearch('home')), 'Documents'),
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
}).then((result) => {
|
||||
if (!result.canceled) {
|
||||
const {ipcRenderer} = require('electron')
|
||||
const fs = require('fs')
|
||||
const initPath = result.filePaths[0]
|
||||
if (isCloudDrivePath(initPath)) {
|
||||
alert('⚠️ This folder may be a cloud sync disk folder, please change to a local folder')
|
||||
return
|
||||
}
|
||||
|
||||
if (!fs.existsSync(initPath)) {
|
||||
fs.mkdirSync(initPath, {mode: 0o755, recursive: true})
|
||||
}
|
||||
ipcRenderer.send('siyuan-first-init', {
|
||||
workspace: initPath,
|
||||
lang: document.querySelector('.lang').value
|
||||
})
|
||||
const getSearch = (key) => {
|
||||
if (window.location.search.indexOf('?') === -1) {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
let value = ''
|
||||
const data = window.location.search.split('?')[1].split('&')
|
||||
data.find(item => {
|
||||
const keyValue = item.split('=')
|
||||
if (keyValue[0] === key) {
|
||||
value = keyValue[1]
|
||||
return true
|
||||
}
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
const isCloudDrivePath = (absPath) => {
|
||||
const absPathLower = absPath.toLowerCase()
|
||||
return -1 < absPathLower.indexOf("onedrive") || -1 < absPathLower.indexOf("dropbox") ||
|
||||
-1 < absPathLower.indexOf("google drive") || -1 < absPathLower.indexOf("pcloud")
|
||||
}
|
||||
let currentLang = decodeURIComponent(getSearch('lang'))
|
||||
const setLang = (lang) => {
|
||||
if ('zh_CN' === lang) {
|
||||
document.title = `思源笔记 v${getSearch('v')}`
|
||||
document.getElementById('zhCN').classList.remove('fn__none')
|
||||
document.getElementById('enUS').classList.add('fn__none')
|
||||
} else {
|
||||
document.title = `SiYuan v${getSearch('v')}`
|
||||
document.getElementById('zhCN').classList.add('fn__none')
|
||||
document.getElementById('enUS').classList.remove('fn__none')
|
||||
}
|
||||
currentLang = lang
|
||||
}
|
||||
setLang(currentLang)
|
||||
|
||||
document.querySelectorAll('.version').forEach(item => {
|
||||
item.textContent = `🔖 v${getSearch('v')}`
|
||||
})
|
||||
|
||||
document.querySelectorAll('.icon').forEach(item => {
|
||||
item.src = decodeURIComponent(`${getSearch('icon')}`)
|
||||
})
|
||||
|
||||
document.querySelectorAll('.lang').forEach(item => {
|
||||
item.value = currentLang
|
||||
item.addEventListener('change', () => {
|
||||
document.querySelectorAll('.lang').forEach(item1 => {
|
||||
item1.value = item.value
|
||||
})
|
||||
setLang(item.value)
|
||||
})
|
||||
})
|
||||
|
||||
document.getElementById('close').addEventListener('click', () => {
|
||||
const {ipcRenderer} = require('electron')
|
||||
ipcRenderer.send('siyuan-first-quit')
|
||||
})
|
||||
document.getElementById('min').addEventListener('click', () => {
|
||||
const {getCurrentWindow} = require('@electron/remote')
|
||||
getCurrentWindow().minimize()
|
||||
})
|
||||
|
||||
document.querySelectorAll('.choosePath').forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
const {dialog} = require('@electron/remote')
|
||||
const path = require('path')
|
||||
dialog.showOpenDialog({
|
||||
defaultPath: path.join(decodeURIComponent(getSearch('home')), 'Documents'),
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
}).then((result) => {
|
||||
if (result.canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
const {ipcRenderer} = require('electron')
|
||||
const fs = require('fs')
|
||||
const initPath = result.filePaths[0]
|
||||
if (isCloudDrivePath(initPath)) {
|
||||
let msg = '⚠️ This folder may be a cloud sync disk folder, please change to another path'
|
||||
if ('zh_CN' === currentLang) {
|
||||
msg = '⚠️ 该文件夹可能是云同步盘文件夹,请更换其他路径'
|
||||
}
|
||||
alert(msg)
|
||||
return
|
||||
}
|
||||
|
||||
let msg = '⚠️ Do not set the workspace under the path of a third-party sync disk, otherwise the data will be damaged (iCloud/OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.), continue?'
|
||||
if ('zh_CN' === currentLang) {
|
||||
msg = '⚠️ 请勿将工作空间设置在第三方同步盘路径下,否则数据会被损坏(iCloud/OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等),是否继续?'
|
||||
}
|
||||
if (!confirm(msg)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!fs.existsSync(initPath)) {
|
||||
fs.mkdirSync(initPath, {mode: 0o755, recursive: true})
|
||||
}
|
||||
ipcRenderer.send('siyuan-first-init', {
|
||||
workspace: initPath,
|
||||
lang: document.querySelector('.lang').value
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const isCloudDrivePath = (absPath) => {
|
||||
const absPathLower = absPath.toLowerCase()
|
||||
return -1 < absPathLower.indexOf("onedrive") || -1 < absPathLower.indexOf("dropbox") ||
|
||||
-1 < absPathLower.indexOf("google drive") || -1 < absPathLower.indexOf("pcloud")
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -540,7 +540,7 @@ const initKernel = (workspace, port, lang) => {
|
|||
case 26:
|
||||
showErrorWindow(
|
||||
"⚠️ 文件系统不一致 File system inconsistent",
|
||||
"<div>请勿使用第三方同步盘进行数据同步,否则数据会被损坏(OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等)</div><div>Do not use a third-party sync disk for data sync, otherwise the data will be damaged (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)</div>");
|
||||
"<div>请勿使用第三方同步盘进行数据同步,否则数据会被损坏(iCloud/OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等)</div><div>Do not use a third-party sync disk for data sync, otherwise the data will be damaged (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)</div>");
|
||||
break;
|
||||
case 0:
|
||||
case 1: // Fatal error
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export class Scroll {
|
|||
if (protyle.block.showAll) {
|
||||
this.element.classList.add("fn__none");
|
||||
} else {
|
||||
if (protyle.block.childBlockCount > window.siyuan.config.editor.dynamicLoadBlocks) {
|
||||
if (protyle.block.scroll) {
|
||||
this.element.classList.remove("fn__none");
|
||||
} else {
|
||||
this.element.classList.add("fn__none");
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export const onGet = (data: IWebSocketData, protyle: IProtyle, action: string[]
|
|||
protyle.block.showAll = false;
|
||||
protyle.block.mode = data.data.mode;
|
||||
protyle.block.blockCount = data.data.blockCount;
|
||||
protyle.block.childBlockCount = data.data.childBlockCount;
|
||||
protyle.block.scroll = data.data.scroll;
|
||||
protyle.block.action = action;
|
||||
if (!action.includes(Constants.CB_GET_UNCHANGEID)) {
|
||||
protyle.block.id = data.data.id;
|
||||
|
|
|
|||
2
app/src/types/protyle.d.ts
vendored
2
app/src/types/protyle.d.ts
vendored
|
|
@ -409,7 +409,7 @@ interface IProtyle {
|
|||
id: string,
|
||||
block: {
|
||||
id?: string,
|
||||
childBlockCount?: number,
|
||||
scroll?: boolean
|
||||
parentID?: string,
|
||||
parent2ID?: string,
|
||||
rootID?: string,
|
||||
|
|
|
|||
|
|
@ -690,7 +690,7 @@ func getDoc(c *gin.Context) {
|
|||
isBacklink = isBacklinkArg.(bool)
|
||||
}
|
||||
|
||||
blockCount, childBlockCount, content, parentID, parent2ID, rootID, typ, eof, boxID, docPath, isBacklinkExpand, err := model.GetDoc(startID, endID, id, index, keyword, mode, size, isBacklink)
|
||||
blockCount, content, parentID, parent2ID, rootID, typ, eof, scroll, boxID, docPath, isBacklinkExpand, err := model.GetDoc(startID, endID, id, index, keyword, mode, size, isBacklink)
|
||||
if errors.Is(err, filelock.ErrUnableAccessFile) {
|
||||
ret.Code = 2
|
||||
ret.Data = id
|
||||
|
|
@ -719,8 +719,8 @@ func getDoc(c *gin.Context) {
|
|||
"type": typ,
|
||||
"content": content,
|
||||
"blockCount": blockCount,
|
||||
"childBlockCount": childBlockCount,
|
||||
"eof": eof,
|
||||
"scroll": scroll,
|
||||
"box": boxID,
|
||||
"path": docPath,
|
||||
"isSyncing": isSyncing,
|
||||
|
|
|
|||
|
|
@ -104,6 +104,25 @@ func reviewRiffCard(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func skipReviewRiffCard(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
deckID := arg["deckID"].(string)
|
||||
cardID := arg["cardID"].(string)
|
||||
err := model.SkipReviewFlashcard(deckID, cardID)
|
||||
if nil != err {
|
||||
ret.Code = -1
|
||||
ret.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getNotebookRiffDueCards(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
|
|
|||
|
|
@ -314,6 +314,7 @@ func ServeAPI(ginServer *gin.Engine) {
|
|||
ginServer.Handle("POST", "/api/riff/getTreeRiffDueCards", model.CheckAuth, getTreeRiffDueCards)
|
||||
ginServer.Handle("POST", "/api/riff/getNotebookRiffDueCards", model.CheckAuth, getNotebookRiffDueCards)
|
||||
ginServer.Handle("POST", "/api/riff/reviewRiffCard", model.CheckAuth, model.CheckReadonly, reviewRiffCard)
|
||||
ginServer.Handle("POST", "/api/riff/skipReviewRiffCard", model.CheckAuth, model.CheckReadonly, skipReviewRiffCard)
|
||||
ginServer.Handle("POST", "/api/riff/getRiffCards", model.CheckAuth, getRiffCards)
|
||||
ginServer.Handle("POST", "/api/riff/getTreeRiffCards", model.CheckAuth, getTreeRiffCards)
|
||||
ginServer.Handle("POST", "/api/riff/getNotebookRiffCards", model.CheckAuth, getNotebookRiffCards)
|
||||
|
|
|
|||
|
|
@ -164,6 +164,16 @@ func getConf(c *gin.Context) {
|
|||
|
||||
if start {
|
||||
start = false
|
||||
|
||||
if model.Conf.Editor.ReadOnly {
|
||||
// 编辑器启用只读模式时启动后提示用户 https://github.com/siyuan-note/siyuan/issues/7700
|
||||
go func() {
|
||||
time.Sleep(time.Second * 7)
|
||||
if model.Conf.Editor.ReadOnly {
|
||||
util.PushMsg(model.Conf.Language(197), 7000)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ func removeWorkspaceDir(c *gin.Context) {
|
|||
}
|
||||
|
||||
if util.WorkspaceDir == path && (util.ContainerIOS == util.Container || util.ContainerAndroid == util.Container) {
|
||||
os.Exit(util.ExitCodeOk)
|
||||
os.Exit(logging.ExitCodeOk)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func LoadTree(boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, err erro
|
|||
if nil != err {
|
||||
logging.LogErrorf("load tree [%s] failed: %s", p, err)
|
||||
if errors.Is(err, filelock.ErrUnableAccessFile) {
|
||||
os.Exit(util.ExitCodeFileSysInconsistent)
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
return
|
||||
}
|
||||
return
|
||||
|
|
@ -97,7 +97,7 @@ func LoadTreeByData(data []byte, boxID, p string, luteEngine *lute.Lute) (ret *p
|
|||
} else {
|
||||
logging.LogWarnf("read parent tree data [%s] failed: %s", parentAbsPath, readErr)
|
||||
if errors.Is(readErr, filelock.ErrUnableAccessFile) {
|
||||
os.Exit(util.ExitCodeFileSysInconsistent)
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ require (
|
|||
github.com/siyuan-note/dejavu v0.0.0-20230315034343-e9513a7e1999
|
||||
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75
|
||||
github.com/siyuan-note/eventbus v0.0.0-20230216103454-41885eac6c2b
|
||||
github.com/siyuan-note/filelock v0.0.0-20230223100551-200cbe1cf84e
|
||||
github.com/siyuan-note/filelock v0.0.0-20230318101209-f0e353df8b5b
|
||||
github.com/siyuan-note/httpclient v0.0.0-20230309131049-f703795de6bc
|
||||
github.com/siyuan-note/logging v0.0.0-20230223101545-ec2cbf198ffb
|
||||
github.com/siyuan-note/logging v0.0.0-20230318100514-8ece27db458d
|
||||
github.com/siyuan-note/riff v0.0.0-20230224144841-cfbe0748ddb7
|
||||
github.com/steambap/captcha v1.4.1
|
||||
github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2
|
||||
|
|
|
|||
|
|
@ -283,12 +283,12 @@ github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75 h1:Bi7/7f29
|
|||
github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75/go.mod h1:H8fyqqAbp9XreANjeSbc72zEdFfKTXYN34tc1TjZwtw=
|
||||
github.com/siyuan-note/eventbus v0.0.0-20230216103454-41885eac6c2b h1:828lTUW2C0uNiolODqoACu7J8sDUzswD4Xo04mUombg=
|
||||
github.com/siyuan-note/eventbus v0.0.0-20230216103454-41885eac6c2b/go.mod h1:Sqo4FYX5lAXu7gWkbEdJF0e6P57tNNVV4WDKYDctokI=
|
||||
github.com/siyuan-note/filelock v0.0.0-20230223100551-200cbe1cf84e h1:SjId8gvjrN/dWa/I+FrRFQtNR3QoU1Vkm/DP0Oos6Rw=
|
||||
github.com/siyuan-note/filelock v0.0.0-20230223100551-200cbe1cf84e/go.mod h1:9uEHHzT1PNXHKTP1PPIB+Q353kMK4RPHPnqjzPEGCUI=
|
||||
github.com/siyuan-note/filelock v0.0.0-20230318101209-f0e353df8b5b h1:NtC1PhGKtOvjQNf95UUW3K5OW/0OIu3ny8sLq9YYqOQ=
|
||||
github.com/siyuan-note/filelock v0.0.0-20230318101209-f0e353df8b5b/go.mod h1:kL4KLZ3/SJxQFQ/lUxkbOyQiqVh/MJyo1CGy6sAUqM8=
|
||||
github.com/siyuan-note/httpclient v0.0.0-20230309131049-f703795de6bc h1:MX2cPWpn7Hfd3FmpwLjGdPIjF84AFUS9f/mcPJc/4w4=
|
||||
github.com/siyuan-note/httpclient v0.0.0-20230309131049-f703795de6bc/go.mod h1:WDO42mUVRnkk8M4AhZ4oakZ5jnghulP0c8NFCrrFWG4=
|
||||
github.com/siyuan-note/logging v0.0.0-20230223101545-ec2cbf198ffb h1:qzz7ZQw7/tHJd1IST+8UymXFF8RacokMLD7VZgyS+ww=
|
||||
github.com/siyuan-note/logging v0.0.0-20230223101545-ec2cbf198ffb/go.mod h1:6mRFtAAvYPn3cDzqvyv+t8BVPGqpONDMMb5ywOhY1D4=
|
||||
github.com/siyuan-note/logging v0.0.0-20230318100514-8ece27db458d h1:K7y9mEvoQ2PZkM0f+bHvnyDi/gnKfY5OJjYUe2GFnpc=
|
||||
github.com/siyuan-note/logging v0.0.0-20230318100514-8ece27db458d/go.mod h1:6mRFtAAvYPn3cDzqvyv+t8BVPGqpONDMMb5ywOhY1D4=
|
||||
github.com/siyuan-note/riff v0.0.0-20230224144841-cfbe0748ddb7 h1:Kr8hhMhr6v+U24TMDCP5WdP4dWrXm5maar+TycTZs9I=
|
||||
github.com/siyuan-note/riff v0.0.0-20230224144841-cfbe0748ddb7/go.mod h1:XJtLlKCr8cZE+lzykM4edHHih92M9M50UNw/nDLYRN8=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ func fullReindex() {
|
|||
WaitForWritingFiles()
|
||||
|
||||
if err := sql.InitDatabase(true); nil != err {
|
||||
os.Exit(util.ExitCodeReadOnlyDatabase)
|
||||
os.Exit(logging.ExitCodeReadOnlyDatabase)
|
||||
return
|
||||
}
|
||||
treenode.InitBlockTree(true)
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ func Close(force bool, execInstallPkg int) (exitCode int) {
|
|||
}
|
||||
logging.LogInfof("exited kernel")
|
||||
util.WebSocketServer.Close()
|
||||
os.Exit(util.ExitCodeOk)
|
||||
os.Exit(logging.ExitCodeOk)
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ func StatTree(id string) (ret *util.BlockStatResult) {
|
|||
}
|
||||
}
|
||||
|
||||
func GetDoc(startID, endID, id string, index int, keyword string, mode int, size int, isBacklink bool) (blockCount, childBlockCount int, dom, parentID, parent2ID, rootID, typ string, eof bool, boxID, docPath string, isBacklinkExpand bool, err error) {
|
||||
func GetDoc(startID, endID, id string, index int, keyword string, mode int, size int, isBacklink bool) (blockCount int, dom, parentID, parent2ID, rootID, typ string, eof, scroll bool, boxID, docPath string, isBacklinkExpand bool, err error) {
|
||||
//os.MkdirAll("pprof", 0755)
|
||||
//cpuProfile, _ := os.Create("pprof/GetDoc")
|
||||
//pprof.StartCPUProfile(cpuProfile)
|
||||
|
|
@ -540,7 +540,6 @@ func GetDoc(startID, endID, id string, index int, keyword string, mode int, size
|
|||
}
|
||||
|
||||
blockCount = tree.DocBlockCount()
|
||||
childBlockCount = treenode.CountBlockNodes(tree.Root)
|
||||
if ast.NodeDocument == node.Type {
|
||||
parentID = node.ID
|
||||
parent2ID = parentID
|
||||
|
|
@ -561,6 +560,26 @@ func GetDoc(startID, endID, id string, index int, keyword string, mode int, size
|
|||
typ = node.Type.String()
|
||||
}
|
||||
|
||||
// 判断是否需要显示动态加载滚动条 https://github.com/siyuan-note/siyuan/issues/7693
|
||||
childCount := 0
|
||||
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||
if !entering {
|
||||
return ast.WalkContinue
|
||||
}
|
||||
|
||||
if 1 > childCount {
|
||||
childCount = 1
|
||||
} else {
|
||||
childCount += treenode.CountBlockNodes(n)
|
||||
}
|
||||
|
||||
if childCount > Conf.Editor.DynamicLoadBlocks {
|
||||
scroll = true
|
||||
return ast.WalkStop
|
||||
}
|
||||
return ast.WalkContinue
|
||||
})
|
||||
|
||||
var nodes []*ast.Node
|
||||
if isBacklink {
|
||||
// 引用计数浮窗请求,需要按照反链逻辑组装 https://github.com/siyuan-note/siyuan/issues/6853
|
||||
|
|
|
|||
|
|
@ -181,8 +181,13 @@ func getCardsBlocks(cards []riff.Card, page int) (blocks []*Block, total, pageCo
|
|||
return
|
||||
}
|
||||
|
||||
// reviewCardCache <cardID, card> 用于复习时缓存卡片,以便支持撤销。
|
||||
var reviewCardCache = map[string]riff.Card{}
|
||||
var (
|
||||
// reviewCardCache <cardID, card> 用于复习时缓存卡片,以便支持撤销。
|
||||
reviewCardCache = map[string]riff.Card{}
|
||||
|
||||
// skipCardCache <cardID, card> 用于复习时缓存跳过的卡片,以便支持跳过过滤。
|
||||
skipCardCache = map[string]riff.Card{}
|
||||
)
|
||||
|
||||
func ReviewFlashcard(deckID, cardID string, rating riff.Rating) (err error) {
|
||||
deckLock.Lock()
|
||||
|
|
@ -203,6 +208,9 @@ func ReviewFlashcard(deckID, cardID string, rating riff.Rating) (err error) {
|
|||
// 命中缓存说明这张卡片已经复习过了,这次调用复习是撤销后再次复习
|
||||
// 将缓存的卡片重新覆盖回卡包中,以恢复最开始复习前的状态
|
||||
deck.SetCard(cachedCard)
|
||||
|
||||
// 从跳过缓存中移除(如果上一次点的是跳过的话),如果不在跳过缓存中,说明上一次点的是复习,这里移除一下也没有副作用
|
||||
delete(skipCardCache, cardID)
|
||||
} else {
|
||||
// 首次复习该卡片,将卡片缓存以便后续支持撤销后再次复习
|
||||
reviewCardCache[cardID] = card
|
||||
|
|
@ -217,12 +225,32 @@ func ReviewFlashcard(deckID, cardID string, rating riff.Rating) (err error) {
|
|||
|
||||
dueCards := getDueFlashcards(deckID)
|
||||
if 1 > len(dueCards) {
|
||||
// 该卡包中没有待复习的卡片了,说明最后一张卡片已经复习完了,清空撤销缓存
|
||||
// 该卡包中没有待复习的卡片了,说明最后一张卡片已经复习完了,清空撤销缓存和跳过缓存
|
||||
reviewCardCache = map[string]riff.Card{}
|
||||
skipCardCache = map[string]riff.Card{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SkipReviewFlashcard(deckID, cardID string) (err error) {
|
||||
deckLock.Lock()
|
||||
defer deckLock.Unlock()
|
||||
|
||||
if syncingStorages {
|
||||
err = errors.New(Conf.Language(81))
|
||||
return
|
||||
}
|
||||
|
||||
deck := Decks[deckID]
|
||||
card := deck.GetCard(cardID)
|
||||
if nil == card {
|
||||
return
|
||||
}
|
||||
|
||||
skipCardCache[cardID] = card
|
||||
return
|
||||
}
|
||||
|
||||
type Flashcard struct {
|
||||
DeckID string `json:"deckID"`
|
||||
CardID string `json:"cardID"`
|
||||
|
|
@ -286,7 +314,7 @@ func GetNotebookDueFlashcards(boxID string) (ret []*Flashcard, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
cards := deck.Dues()
|
||||
cards := getDeckDueCards(deck)
|
||||
now := time.Now()
|
||||
for _, card := range cards {
|
||||
blockID := card.BlockID()
|
||||
|
|
@ -317,7 +345,7 @@ func GetTreeDueFlashcards(rootID string) (ret []*Flashcard, err error) {
|
|||
}
|
||||
|
||||
treeBlockIDs := getTreeSubTreeChildBlocks(rootID)
|
||||
cards := deck.Dues()
|
||||
cards := getDeckDueCards(deck)
|
||||
now := time.Now()
|
||||
for _, card := range cards {
|
||||
blockID := card.BlockID()
|
||||
|
|
@ -396,7 +424,7 @@ func getDueFlashcards(deckID string) (ret []*Flashcard) {
|
|||
return
|
||||
}
|
||||
|
||||
cards := deck.Dues()
|
||||
cards := getDeckDueCards(deck)
|
||||
now := time.Now()
|
||||
for _, card := range cards {
|
||||
blockID := card.BlockID()
|
||||
|
|
@ -416,7 +444,7 @@ func getDueFlashcards(deckID string) (ret []*Flashcard) {
|
|||
func getAllDueFlashcards() (ret []*Flashcard) {
|
||||
now := time.Now()
|
||||
for _, deck := range Decks {
|
||||
cards := deck.Dues()
|
||||
cards := getDeckDueCards(deck)
|
||||
for _, card := range cards {
|
||||
blockID := card.BlockID()
|
||||
if nil == treenode.GetBlockTree(blockID) {
|
||||
|
|
@ -867,3 +895,17 @@ func getDeckIDs() (deckIDs []string) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDeckDueCards(deck *riff.Deck) (ret []riff.Card) {
|
||||
ret = []riff.Card{}
|
||||
dues := deck.Dues()
|
||||
|
||||
for _, c := range dues {
|
||||
if nil != skipCardCache[c.ID()] {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,6 +259,10 @@ func setCriteria(criteria []*Criterion) (err error) {
|
|||
err = filelock.WriteFile(lsPath, data)
|
||||
if nil != err {
|
||||
logging.LogErrorf("write storage [criteria] failed: %s", err)
|
||||
if errors.Is(err, filelock.ErrUnableAccessFile) {
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
|
|
@ -274,6 +278,10 @@ func getCriteria() (ret []*Criterion, err error) {
|
|||
data, err := filelock.ReadFile(dataPath)
|
||||
if nil != err {
|
||||
logging.LogErrorf("read storage [criteria] failed: %s", err)
|
||||
if errors.Is(err, filelock.ErrUnableAccessFile) {
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -345,6 +353,10 @@ func setLocalStorage(val interface{}) (err error) {
|
|||
err = filelock.WriteFile(lsPath, data)
|
||||
if nil != err {
|
||||
logging.LogErrorf("write storage [local] failed: %s", err)
|
||||
if errors.Is(err, filelock.ErrUnableAccessFile) {
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
|
|
@ -360,6 +372,10 @@ func getLocalStorage() (ret map[string]interface{}, err error) {
|
|||
data, err := filelock.ReadFile(lsPath)
|
||||
if nil != err {
|
||||
logging.LogErrorf("read storage [local] failed: %s", err)
|
||||
if errors.Is(err, filelock.ErrUnableAccessFile) {
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ func Serve(fastMode bool) {
|
|||
if nil != err {
|
||||
if !fastMode {
|
||||
logging.LogErrorf("boot kernel failed: %s", err)
|
||||
os.Exit(util.ExitCodeUnavailablePort)
|
||||
os.Exit(logging.ExitCodeUnavailablePort)
|
||||
}
|
||||
|
||||
// fast 模式下启动失败则直接返回
|
||||
|
|
@ -95,7 +95,7 @@ func Serve(fastMode bool) {
|
|||
if nil != err {
|
||||
if !fastMode {
|
||||
logging.LogErrorf("boot kernel failed: %s", err)
|
||||
os.Exit(util.ExitCodeUnavailablePort)
|
||||
os.Exit(logging.ExitCodeUnavailablePort)
|
||||
}
|
||||
}
|
||||
util.ServerPort = port
|
||||
|
|
@ -129,7 +129,7 @@ func Serve(fastMode bool) {
|
|||
if err = http.Serve(ln, ginServer); nil != err {
|
||||
if !fastMode {
|
||||
logging.LogErrorf("boot kernel failed: %s", err)
|
||||
os.Exit(util.ExitCodeUnavailablePort)
|
||||
os.Exit(logging.ExitCodeUnavailablePort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1148,7 +1148,7 @@ func beginTx() (tx *sql.Tx, err error) {
|
|||
if tx, err = db.Begin(); nil != err {
|
||||
logging.LogErrorf("begin tx failed: %s\n %s", err, logging.ShortStack())
|
||||
if strings.Contains(err.Error(), "database is locked") {
|
||||
os.Exit(util.ExitCodeReadOnlyDatabase)
|
||||
os.Exit(logging.ExitCodeReadOnlyDatabase)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
@ -1158,7 +1158,7 @@ func beginHistoryTx() (tx *sql.Tx, err error) {
|
|||
if tx, err = historyDB.Begin(); nil != err {
|
||||
logging.LogErrorf("begin history tx failed: %s\n %s", err, logging.ShortStack())
|
||||
if strings.Contains(err.Error(), "database is locked") {
|
||||
os.Exit(util.ExitCodeReadOnlyDatabase)
|
||||
os.Exit(logging.ExitCodeReadOnlyDatabase)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ func InitBlockTree(force bool) {
|
|||
entries, err := os.ReadDir(util.BlockTreePath)
|
||||
if nil != err {
|
||||
logging.LogErrorf("read block tree dir failed: %s", err)
|
||||
os.Exit(util.ExitCodeBlockTreeErr)
|
||||
os.Exit(logging.ExitCodeBlockTreeErr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +342,7 @@ func InitBlockTree(force bool) {
|
|||
fh, err = os.OpenFile(p, os.O_RDWR, 0644)
|
||||
if nil != err {
|
||||
logging.LogErrorf("open block tree file failed: %s", err)
|
||||
os.Exit(util.ExitCodeBlockTreeErr)
|
||||
os.Exit(logging.ExitCodeBlockTreeErr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +351,7 @@ func InitBlockTree(force bool) {
|
|||
fh.Close()
|
||||
if nil != err {
|
||||
logging.LogErrorf("read block tree failed: %s", err)
|
||||
os.Exit(util.ExitCodeBlockTreeErr)
|
||||
os.Exit(logging.ExitCodeBlockTreeErr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -361,7 +361,7 @@ func InitBlockTree(force bool) {
|
|||
if err = os.RemoveAll(util.BlockTreePath); nil != err {
|
||||
logging.LogErrorf("removed corrupted block tree failed: %s", err)
|
||||
}
|
||||
os.Exit(util.ExitCodeBlockTreeErr)
|
||||
os.Exit(logging.ExitCodeBlockTreeErr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -410,7 +410,7 @@ func SaveBlockTree(force bool) {
|
|||
data, err := msgpack.Marshal(slice.data)
|
||||
if nil != err {
|
||||
logging.LogErrorf("marshal block tree failed: %s", err)
|
||||
os.Exit(util.ExitCodeBlockTreeErr)
|
||||
os.Exit(logging.ExitCodeBlockTreeErr)
|
||||
return false
|
||||
}
|
||||
slice.m.Unlock()
|
||||
|
|
@ -418,7 +418,7 @@ func SaveBlockTree(force bool) {
|
|||
p := filepath.Join(util.BlockTreePath, key.(string)) + ".msgpack"
|
||||
if err = gulu.File.WriteFileSafer(p, data, 0644); nil != err {
|
||||
logging.LogErrorf("write block tree failed: %s", err)
|
||||
os.Exit(util.ExitCodeBlockTreeErr)
|
||||
os.Exit(logging.ExitCodeBlockTreeErr)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,18 +37,6 @@ import (
|
|||
|
||||
const DatabaseVer = "20220501" // 修改表结构的话需要修改这里
|
||||
|
||||
const (
|
||||
ExitCodeReadOnlyDatabase = 20 // 数据库文件被锁
|
||||
ExitCodeUnavailablePort = 21 // 端口不可用
|
||||
ExitCodeCreateConfDirErr = 22 // 创建配置目录失败
|
||||
ExitCodeBlockTreeErr = 23 // 无法读写 blocktree.msgpack 文件
|
||||
ExitCodeWorkspaceLocked = 24 // 工作空间已被锁定
|
||||
ExitCodeCreateWorkspaceDirErr = 25 // 创建工作空间失败
|
||||
ExitCodeFileSysInconsistent = 26 // 文件系统不一致
|
||||
ExitCodeOk = 0 // 正常退出
|
||||
ExitCodeFatal = 1 // 致命错误
|
||||
)
|
||||
|
||||
// IsExiting 是否正在退出程序。
|
||||
var IsExiting = false
|
||||
|
||||
|
|
@ -139,7 +127,7 @@ func ReportFileSysFatalError(err error) {
|
|||
output = strings.Join(lines[5:], "\n")
|
||||
}
|
||||
logging.LogErrorf("check file system status failed: %s, %s", err, output)
|
||||
os.Exit(ExitCodeFileSysInconsistent)
|
||||
os.Exit(logging.ExitCodeFileSysInconsistent)
|
||||
}
|
||||
|
||||
func CheckFileSysStatus() {
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ func initWorkspaceDir(workspaceArg string) {
|
|||
if !gulu.File.IsExist(workspaceConf) {
|
||||
if err := os.MkdirAll(userHomeConfDir, 0755); nil != err && !os.IsExist(err) {
|
||||
log.Printf("create user home conf folder [%s] failed: %s", userHomeConfDir, err)
|
||||
os.Exit(ExitCodeCreateConfDirErr)
|
||||
os.Exit(logging.ExitCodeCreateConfDirErr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ func initWorkspaceDir(workspaceArg string) {
|
|||
}
|
||||
if err := os.MkdirAll(defaultWorkspaceDir, 0755); nil != err && !os.IsExist(err) {
|
||||
log.Printf("create default workspace folder [%s] failed: %s", defaultWorkspaceDir, err)
|
||||
os.Exit(ExitCodeCreateWorkspaceDirErr)
|
||||
os.Exit(logging.ExitCodeCreateWorkspaceDirErr)
|
||||
}
|
||||
|
||||
var workspacePaths []string
|
||||
|
|
@ -481,7 +481,7 @@ func tryLockWorkspace() {
|
|||
} else {
|
||||
logging.LogErrorf("lock workspace [%s] failed", WorkspaceDir)
|
||||
}
|
||||
os.Exit(ExitCodeWorkspaceLocked)
|
||||
os.Exit(logging.ExitCodeWorkspaceLocked)
|
||||
}
|
||||
|
||||
func IsWorkspaceLocked(workspacePath string) bool {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func BootMobile(container, appDir, workspaceBaseDir, lang string) {
|
|||
if !gulu.File.IsExist(userHomeConfDir) {
|
||||
if err := os.MkdirAll(userHomeConfDir, 0755); nil != err && !os.IsExist(err) {
|
||||
log.Printf("create user home conf folder [%s] failed: %s", userHomeConfDir, err)
|
||||
os.Exit(ExitCodeCreateConfDirErr)
|
||||
os.Exit(logging.ExitCodeCreateConfDirErr)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ func BootMobile(container, appDir, workspaceBaseDir, lang string) {
|
|||
defaultWorkspaceDir := filepath.Join(workspaceBaseDir, "siyuan")
|
||||
if err := os.MkdirAll(defaultWorkspaceDir, 0755); nil != err && !os.IsExist(err) {
|
||||
log.Printf("create default workspace folder [%s] failed: %s", defaultWorkspaceDir, err)
|
||||
os.Exit(ExitCodeCreateWorkspaceDirErr)
|
||||
os.Exit(logging.ExitCodeCreateWorkspaceDirErr)
|
||||
}
|
||||
|
||||
initWorkspaceDirMobile(workspaceBaseDir)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue