setting.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package api
  17. import (
  18. "fmt"
  19. "net/http"
  20. "strings"
  21. "github.com/88250/gulu"
  22. "github.com/gin-gonic/gin"
  23. "github.com/siyuan-note/siyuan/kernel/conf"
  24. "github.com/siyuan-note/siyuan/kernel/model"
  25. "github.com/siyuan-note/siyuan/kernel/sql"
  26. "github.com/siyuan-note/siyuan/kernel/util"
  27. )
  28. func addVirtualBlockRefExclude(c *gin.Context) {
  29. // Add internal kernel API `/api/setting/addVirtualBlockRefExclude` https://github.com/siyuan-note/siyuan/issues/9909
  30. ret := gulu.Ret.NewResult()
  31. defer c.JSON(http.StatusOK, ret)
  32. arg, ok := util.JsonArg(c, ret)
  33. if !ok {
  34. return
  35. }
  36. keywordsArg := arg["keywords"]
  37. var keywords []string
  38. for _, k := range keywordsArg.([]interface{}) {
  39. keywords = append(keywords, k.(string))
  40. }
  41. model.AddVirtualBlockRefExclude(keywords)
  42. }
  43. func addVirtualBlockRefInclude(c *gin.Context) {
  44. // Add internal kernel API `/api/setting/addVirtualBlockRefInclude` https://github.com/siyuan-note/siyuan/issues/9909
  45. ret := gulu.Ret.NewResult()
  46. defer c.JSON(http.StatusOK, ret)
  47. arg, ok := util.JsonArg(c, ret)
  48. if !ok {
  49. return
  50. }
  51. keywordsArg := arg["keywords"]
  52. var keywords []string
  53. for _, k := range keywordsArg.([]interface{}) {
  54. keywords = append(keywords, k.(string))
  55. }
  56. model.AddVirtualBlockRefInclude(keywords)
  57. }
  58. func refreshVirtualBlockRef(c *gin.Context) {
  59. // Add internal kernel API `/api/setting/refreshVirtualBlockRef` https://github.com/siyuan-note/siyuan/issues/9829
  60. ret := gulu.Ret.NewResult()
  61. defer c.JSON(http.StatusOK, ret)
  62. model.ResetVirtualBlockRefCache()
  63. }
  64. func setBazaar(c *gin.Context) {
  65. ret := gulu.Ret.NewResult()
  66. defer c.JSON(http.StatusOK, ret)
  67. arg, ok := util.JsonArg(c, ret)
  68. if !ok {
  69. return
  70. }
  71. param, err := gulu.JSON.MarshalJSON(arg)
  72. if nil != err {
  73. ret.Code = -1
  74. ret.Msg = err.Error()
  75. return
  76. }
  77. bazaar := &conf.Bazaar{}
  78. if err = gulu.JSON.UnmarshalJSON(param, bazaar); nil != err {
  79. ret.Code = -1
  80. ret.Msg = err.Error()
  81. return
  82. }
  83. model.Conf.Bazaar = bazaar
  84. model.Conf.Save()
  85. ret.Data = bazaar
  86. }
  87. func setAI(c *gin.Context) {
  88. ret := gulu.Ret.NewResult()
  89. defer c.JSON(http.StatusOK, ret)
  90. arg, ok := util.JsonArg(c, ret)
  91. if !ok {
  92. return
  93. }
  94. param, err := gulu.JSON.MarshalJSON(arg)
  95. if nil != err {
  96. ret.Code = -1
  97. ret.Msg = err.Error()
  98. return
  99. }
  100. ai := &conf.AI{}
  101. if err = gulu.JSON.UnmarshalJSON(param, ai); nil != err {
  102. ret.Code = -1
  103. ret.Msg = err.Error()
  104. return
  105. }
  106. if 5 > ai.OpenAI.APITimeout {
  107. ai.OpenAI.APITimeout = 5
  108. }
  109. if 600 < ai.OpenAI.APITimeout {
  110. ai.OpenAI.APITimeout = 600
  111. }
  112. if 0 > ai.OpenAI.APIMaxTokens {
  113. ai.OpenAI.APIMaxTokens = 0
  114. }
  115. model.Conf.AI = ai
  116. model.Conf.Save()
  117. ret.Data = ai
  118. }
  119. func setFlashcard(c *gin.Context) {
  120. ret := gulu.Ret.NewResult()
  121. defer c.JSON(http.StatusOK, ret)
  122. arg, ok := util.JsonArg(c, ret)
  123. if !ok {
  124. return
  125. }
  126. param, err := gulu.JSON.MarshalJSON(arg)
  127. if nil != err {
  128. ret.Code = -1
  129. ret.Msg = err.Error()
  130. return
  131. }
  132. flashcard := &conf.Flashcard{}
  133. if err = gulu.JSON.UnmarshalJSON(param, flashcard); nil != err {
  134. ret.Code = -1
  135. ret.Msg = err.Error()
  136. return
  137. }
  138. if 0 > flashcard.NewCardLimit {
  139. flashcard.NewCardLimit = 20
  140. }
  141. if 0 > flashcard.ReviewCardLimit {
  142. flashcard.ReviewCardLimit = 200
  143. }
  144. model.Conf.Flashcard = flashcard
  145. model.Conf.Save()
  146. ret.Data = flashcard
  147. }
  148. func setAccount(c *gin.Context) {
  149. ret := gulu.Ret.NewResult()
  150. defer c.JSON(http.StatusOK, ret)
  151. arg, ok := util.JsonArg(c, ret)
  152. if !ok {
  153. return
  154. }
  155. param, err := gulu.JSON.MarshalJSON(arg)
  156. if nil != err {
  157. ret.Code = -1
  158. ret.Msg = err.Error()
  159. return
  160. }
  161. account := &conf.Account{}
  162. if err = gulu.JSON.UnmarshalJSON(param, account); nil != err {
  163. ret.Code = -1
  164. ret.Msg = err.Error()
  165. return
  166. }
  167. model.Conf.Account = account
  168. model.Conf.Save()
  169. ret.Data = model.Conf.Account
  170. }
  171. func setEditor(c *gin.Context) {
  172. ret := gulu.Ret.NewResult()
  173. defer c.JSON(http.StatusOK, ret)
  174. arg, ok := util.JsonArg(c, ret)
  175. if !ok {
  176. return
  177. }
  178. param, err := gulu.JSON.MarshalJSON(arg)
  179. if nil != err {
  180. ret.Code = -1
  181. ret.Msg = err.Error()
  182. return
  183. }
  184. oldGenerateHistoryInterval := model.Conf.Editor.GenerateHistoryInterval
  185. editor := conf.NewEditor()
  186. if err = gulu.JSON.UnmarshalJSON(param, editor); nil != err {
  187. ret.Code = -1
  188. ret.Msg = err.Error()
  189. return
  190. }
  191. if "" == editor.PlantUMLServePath {
  192. editor.PlantUMLServePath = "https://www.plantuml.com/plantuml/svg/~1"
  193. }
  194. if "" == editor.KaTexMacros {
  195. editor.KaTexMacros = "{}"
  196. }
  197. oldVirtualBlockRef := model.Conf.Editor.VirtualBlockRef
  198. oldVirtualBlockRefInclude := model.Conf.Editor.VirtualBlockRefInclude
  199. oldVirtualBlockRefExclude := model.Conf.Editor.VirtualBlockRefExclude
  200. oldReadOnly := model.Conf.Editor.ReadOnly
  201. model.Conf.Editor = editor
  202. model.Conf.Save()
  203. if oldGenerateHistoryInterval != model.Conf.Editor.GenerateHistoryInterval {
  204. model.ChangeHistoryTick(editor.GenerateHistoryInterval)
  205. }
  206. if oldVirtualBlockRef != model.Conf.Editor.VirtualBlockRef ||
  207. oldVirtualBlockRefInclude != model.Conf.Editor.VirtualBlockRefInclude ||
  208. oldVirtualBlockRefExclude != model.Conf.Editor.VirtualBlockRefExclude {
  209. model.ResetVirtualBlockRefCache()
  210. }
  211. if oldReadOnly != model.Conf.Editor.ReadOnly {
  212. util.BroadcastByType("protyle", "readonly", 0, "", model.Conf.Editor.ReadOnly)
  213. util.BroadcastByType("main", "readonly", 0, "", model.Conf.Editor.ReadOnly)
  214. }
  215. ret.Data = model.Conf.Editor
  216. }
  217. func setExport(c *gin.Context) {
  218. ret := gulu.Ret.NewResult()
  219. defer c.JSON(http.StatusOK, ret)
  220. arg, ok := util.JsonArg(c, ret)
  221. if !ok {
  222. return
  223. }
  224. param, err := gulu.JSON.MarshalJSON(arg)
  225. if nil != err {
  226. ret.Code = -1
  227. ret.Msg = err.Error()
  228. return
  229. }
  230. export := &conf.Export{}
  231. if err = gulu.JSON.UnmarshalJSON(param, export); nil != err {
  232. ret.Code = -1
  233. ret.Msg = err.Error()
  234. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  235. return
  236. }
  237. if "" != export.PandocBin {
  238. if !util.IsValidPandocBin(export.PandocBin) {
  239. util.PushErrMsg(fmt.Sprintf(model.Conf.Language(117), export.PandocBin), 5000)
  240. export.PandocBin = util.PandocBinPath
  241. } else {
  242. util.PandocBinPath = export.PandocBin
  243. }
  244. }
  245. model.Conf.Export = export
  246. model.Conf.Save()
  247. ret.Data = model.Conf.Export
  248. }
  249. func setFiletree(c *gin.Context) {
  250. ret := gulu.Ret.NewResult()
  251. defer c.JSON(http.StatusOK, ret)
  252. arg, ok := util.JsonArg(c, ret)
  253. if !ok {
  254. return
  255. }
  256. param, err := gulu.JSON.MarshalJSON(arg)
  257. if nil != err {
  258. ret.Code = -1
  259. ret.Msg = err.Error()
  260. return
  261. }
  262. fileTree := conf.NewFileTree()
  263. if err = gulu.JSON.UnmarshalJSON(param, fileTree); nil != err {
  264. ret.Code = -1
  265. ret.Msg = err.Error()
  266. return
  267. }
  268. fileTree.RefCreateSavePath = strings.TrimSpace(fileTree.RefCreateSavePath)
  269. if "" != fileTree.RefCreateSavePath {
  270. if !strings.HasSuffix(fileTree.RefCreateSavePath, "/") {
  271. fileTree.RefCreateSavePath += "/"
  272. }
  273. }
  274. fileTree.DocCreateSavePath = strings.TrimSpace(fileTree.DocCreateSavePath)
  275. if "../" == fileTree.DocCreateSavePath {
  276. fileTree.DocCreateSavePath = "../Untitled"
  277. }
  278. for strings.HasSuffix(fileTree.DocCreateSavePath, "/") {
  279. fileTree.DocCreateSavePath = strings.TrimSuffix(fileTree.DocCreateSavePath, "/")
  280. fileTree.DocCreateSavePath = strings.TrimSpace(fileTree.DocCreateSavePath)
  281. }
  282. if 1 > fileTree.MaxOpenTabCount {
  283. fileTree.MaxOpenTabCount = 8
  284. }
  285. if 32 < fileTree.MaxOpenTabCount {
  286. fileTree.MaxOpenTabCount = 32
  287. }
  288. model.Conf.FileTree = fileTree
  289. model.Conf.Save()
  290. util.UseSingleLineSave = model.Conf.FileTree.UseSingleLineSave
  291. ret.Data = model.Conf.FileTree
  292. }
  293. func setSearch(c *gin.Context) {
  294. ret := gulu.Ret.NewResult()
  295. defer c.JSON(http.StatusOK, ret)
  296. arg, ok := util.JsonArg(c, ret)
  297. if !ok {
  298. return
  299. }
  300. param, err := gulu.JSON.MarshalJSON(arg)
  301. if nil != err {
  302. ret.Code = -1
  303. ret.Msg = err.Error()
  304. return
  305. }
  306. s := &conf.Search{}
  307. if err = gulu.JSON.UnmarshalJSON(param, s); nil != err {
  308. ret.Code = -1
  309. ret.Msg = err.Error()
  310. return
  311. }
  312. if 32 > s.Limit {
  313. s.Limit = 32
  314. }
  315. oldCaseSensitive := model.Conf.Search.CaseSensitive
  316. oldIndexAssetPath := model.Conf.Search.IndexAssetPath
  317. oldVirtualRefName := model.Conf.Search.VirtualRefName
  318. oldVirtualRefAlias := model.Conf.Search.VirtualRefAlias
  319. oldVirtualRefAnchor := model.Conf.Search.VirtualRefAnchor
  320. oldVirtualRefDoc := model.Conf.Search.VirtualRefDoc
  321. model.Conf.Search = s
  322. model.Conf.Save()
  323. sql.SetCaseSensitive(s.CaseSensitive)
  324. sql.SetIndexAssetPath(s.IndexAssetPath)
  325. if needFullReindex := s.CaseSensitive != oldCaseSensitive || s.IndexAssetPath != oldIndexAssetPath; needFullReindex {
  326. model.FullReindex()
  327. }
  328. if oldVirtualRefName != s.VirtualRefName ||
  329. oldVirtualRefAlias != s.VirtualRefAlias ||
  330. oldVirtualRefAnchor != s.VirtualRefAnchor ||
  331. oldVirtualRefDoc != s.VirtualRefDoc {
  332. model.ResetVirtualBlockRefCache()
  333. }
  334. ret.Data = s
  335. }
  336. func setKeymap(c *gin.Context) {
  337. ret := gulu.Ret.NewResult()
  338. defer c.JSON(http.StatusOK, ret)
  339. arg, ok := util.JsonArg(c, ret)
  340. if !ok {
  341. return
  342. }
  343. param, err := gulu.JSON.MarshalJSON(arg["data"])
  344. if nil != err {
  345. ret.Code = -1
  346. ret.Msg = err.Error()
  347. return
  348. }
  349. keymap := &conf.Keymap{}
  350. if err = gulu.JSON.UnmarshalJSON(param, keymap); nil != err {
  351. ret.Code = -1
  352. ret.Msg = err.Error()
  353. return
  354. }
  355. model.Conf.Keymap = keymap
  356. model.Conf.Save()
  357. }
  358. func setAppearance(c *gin.Context) {
  359. ret := gulu.Ret.NewResult()
  360. defer c.JSON(http.StatusOK, ret)
  361. arg, ok := util.JsonArg(c, ret)
  362. if !ok {
  363. return
  364. }
  365. param, err := gulu.JSON.MarshalJSON(arg)
  366. if nil != err {
  367. ret.Code = -1
  368. ret.Msg = err.Error()
  369. return
  370. }
  371. appearance := &conf.Appearance{}
  372. if err = gulu.JSON.UnmarshalJSON(param, appearance); nil != err {
  373. ret.Code = -1
  374. ret.Msg = err.Error()
  375. return
  376. }
  377. model.Conf.Appearance = appearance
  378. model.Conf.Lang = appearance.Lang
  379. util.Lang = model.Conf.Lang
  380. model.Conf.Save()
  381. model.InitAppearance()
  382. ret.Data = model.Conf.Appearance
  383. }
  384. func getCloudUser(c *gin.Context) {
  385. ret := gulu.Ret.NewResult()
  386. defer c.JSON(http.StatusOK, ret)
  387. arg, ok := util.JsonArg(c, ret)
  388. if !ok {
  389. return
  390. }
  391. t := arg["token"]
  392. var token string
  393. if nil != t {
  394. token = t.(string)
  395. }
  396. model.RefreshUser(token)
  397. ret.Data = model.Conf.GetUser()
  398. }
  399. func logoutCloudUser(c *gin.Context) {
  400. ret := gulu.Ret.NewResult()
  401. defer c.JSON(http.StatusOK, ret)
  402. model.LogoutUser()
  403. }
  404. func login2faCloudUser(c *gin.Context) {
  405. ret := gulu.Ret.NewResult()
  406. defer c.JSON(http.StatusOK, ret)
  407. arg, ok := util.JsonArg(c, ret)
  408. if !ok {
  409. return
  410. }
  411. token := arg["token"].(string)
  412. code := arg["code"].(string)
  413. data, err := model.Login2fa(token, code)
  414. if nil != err {
  415. ret.Code = -1
  416. ret.Msg = err.Error()
  417. return
  418. }
  419. ret.Data = data
  420. }
  421. func setEmoji(c *gin.Context) {
  422. ret := gulu.Ret.NewResult()
  423. defer c.JSON(http.StatusOK, ret)
  424. arg, ok := util.JsonArg(c, ret)
  425. if !ok {
  426. return
  427. }
  428. argEmoji := arg["emoji"].([]interface{})
  429. var emoji []string
  430. for _, ae := range argEmoji {
  431. emoji = append(emoji, ae.(string))
  432. }
  433. model.Conf.Editor.Emoji = emoji
  434. }