diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json
index 03e988dcecc..96dc348da62 100644
--- a/app/appearance/langs/en_US.json
+++ b/app/appearance/langs/en_US.json
@@ -1,4 +1,17 @@
{
+ "publish": "Publish",
+ "publishService": "Publish service",
+ "publishServiceNotStarted": "Publish Service Not Started",
+ "publishServiceTip": "When enabled, the publish service will be started. This service publishes the content of the current workspace in read-only mode on the local network.",
+ "publishServicePort": "Service port",
+ "publishServicePortTip": "Enable the publish service using the specified port number. If set to 0
, a random port will be used.",
+ "publishServiceAddresses": "Service access addresses",
+ "publishServiceAddressesTip": "Possible network addresses to access the publish service.",
+ "publishServiceAuth": "Service basic authentication",
+ "publishServiceAuthTip": "When enabled, authentication is required to access the publish service.",
+ "publishServiceAuthAccounts": "Authenticated accounts",
+ "publishServiceAuthAccountsTip": "List of Basic authentication accounts. Visitors need to enter the username and password from this list to view the published content.",
+ "publishServiceAuthAccountAdd": "Add account",
"copyMirror": "Copy mirror",
"duplicateMirror": "Duplicate mirror",
"duplicateCompletely": "Duplicate completely",
diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json
index 7f98438e6cb..4d378d71e37 100644
--- a/app/appearance/langs/es_ES.json
+++ b/app/appearance/langs/es_ES.json
@@ -1,4 +1,17 @@
{
+ "publish": "Publicar",
+ "publishService": "Publicar servicio",
+ "publishServiceNotStarted": "Servicio de publicación no iniciado",
+ "publishServiceTip": "Al activar esto, se iniciará el servicio de publicación. Este servicio publicará el contenido del espacio de trabajo actual en modo de solo lectura en la LAN",
+ "publishServicePort": "Número de puerto del servicio",
+ "publishServicePortTip": "Activar el servicio de publicación con el número de puerto especificado. Si se establece en 0, se utilizará un puerto aleatorio",
+ "publishServiceAddresses": "Direcciones de acceso al servicio",
+ "publishServiceAddressesTip": "Direcciones de red desde las que se puede acceder al servicio de publicación",
+ "publishServiceAuth": "Autenticación básica del servicio",
+ "publishServiceAuthTip": "Al activar esto, se requerirá autenticación al acceder al servicio de publicación",
+ "publishServiceAuthAccounts": "Cuentas de autenticación",
+ "publishServiceAuthAccountsTip": "Lista de cuentas de autenticación básica. Después de activar la autenticación básica, los visitantes deberán ingresar el nombre de usuario y la contraseña de la lista para ver el contenido publicado",
+ "publishServiceAuthAccountAdd": "Agregar cuenta",
"copyMirror": "Copiar espejo",
"duplicateMirror": "Espejo duplicado",
"duplicateCompletely": "Duplicar completamente",
diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json
index d49cca4e6d8..f01d7d21b4d 100644
--- a/app/appearance/langs/fr_FR.json
+++ b/app/appearance/langs/fr_FR.json
@@ -1,4 +1,17 @@
{
+ "publish": "Publier",
+ "publishService": "Publier le service",
+ "publishServiceNotStarted": "Service de publication non démarré",
+ "publishServiceTip": "Lorsqu'activé, le service de publication démarre. Ce service publie en mode lecture seule le contenu de l'espace de travail actuel dans le réseau local.",
+ "publishServicePort": "Numéro de port du service",
+ "publishServicePortTip": "Active le service de publication avec le numéro de port spécifié. Si défini sur 0, un port aléatoire sera utilisé.",
+ "publishServiceAddresses": "Adresses d'accès au service",
+ "publishServiceAddressesTip": "Adresses réseau qui peuvent accéder au service de publication",
+ "publishServiceAuth": "Authentification Basic du service",
+ "publishServiceAuthTip": "Lorsqu'activé, une authentification est requise pour accéder au service de publication",
+ "publishServiceAuthAccounts": "Comptes d'authentification",
+ "publishServiceAuthAccountsTip": "Liste des comptes d'authentification Basic. Lorsque l'authentification Basic est activée, les visiteurs doivent entrer un nom d'utilisateur et un mot de passe figurant dans cette liste pour consulter le contenu publié.",
+ "publishServiceAuthAccountAdd": "Ajouter un compte",
"copyMirror": "Copier le miroir",
"duplicateMirror": "Miroir en double",
"duplicateCompletely": "Dupliquer complètement",
diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json
index 2fe7c88b193..039e1e04e30 100644
--- a/app/appearance/langs/ja_JP.json
+++ b/app/appearance/langs/ja_JP.json
@@ -1,4 +1,17 @@
{
+ "publish": "公開する",
+ "publishService": "サービスを公開する",
+ "publishServiceNotStarted": "サービスが開始されていません",
+ "publishServiceTip": "有効にすると、サービスを開始します。このサービスは、現在のワークスペースの内容を読み取り専用モードでローカルネットワークに公開します",
+ "publishServicePort": "サービスポート",
+ "publishServicePortTip": "指定したポート番号を使用してサービスを有効にします。0に設定するとランダムなポートが使用されます",
+ "publishServiceAddresses": "サービスアドレス",
+ "publishServiceAddressesTip": "サービスを公開することが可能なネットワークアドレス",
+ "publishServiceAuth": "サービスの基本認証",
+ "publishServiceAuthTip": "有効にすると、公開サービスへのアクセス時に認証が必要になります",
+ "publishServiceAuthAccounts": "認証アカウント",
+ "publishServiceAuthAccountsTip": "基本認証アカウントのリスト。基本認証を有効にした場合、訪問者はリスト内のユーザー名とパスワードを入力して公開内容を表示することができます",
+ "publishServiceAuthAccountAdd": "アカウントを追加する",
"copyMirror": "ミラーをコピー",
"duplicateMirror": "ミラーを複製",
"duplicateCompletely": "完全に複製",
@@ -1523,4 +1536,4 @@
"247": "ファイル [%s] は制限サイズ [%s] を超えているためアップロードされませんでした",
"248": "目標の見出しがコンテナブロック内にあるためドロップできません"
}
-}
\ No newline at end of file
+}
diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json
index b739f8a7653..8147efa9180 100644
--- a/app/appearance/langs/zh_CHT.json
+++ b/app/appearance/langs/zh_CHT.json
@@ -1,4 +1,17 @@
{
+ "publish": "發布",
+ "publishService": "發布服務",
+ "publishServiceNotStarted": "發布服務未啟動",
+ "publishServiceTip": "啟用後將啟動發布服務。該服務以只讀模式在區域網中發布當前工作空間的內容",
+ "publishServicePort": "服務端口號",
+ "publishServicePortTip": "使用指定的端口號啟用發布服務。若設置為 0
則使用隨機端口",
+ "publishServiceAddresses": "服務訪問地址",
+ "publishServiceAddressesTip": "可能訪問到發布服務的網路地址",
+ "publishServiceAuth": "服務 Basic 認證",
+ "publishServiceAuthTip": "啟用後在訪問發布服務時需要進行認證",
+ "publishServiceAuthAccounts": "認證帳戶",
+ "publishServiceAuthAccountsTip": "Basic 認證帳戶列表。啟用 Basic 認證後訪問者輸入列表中的用戶名與密碼後才能查看發布的內容",
+ "publishServiceAuthAccountAdd": "添加帳戶",
"copyMirror": "複製鏡像",
"duplicateMirror": "複製為鏡像副本",
"duplicateCompletely": "複製為完整副本",
diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json
index 41c82d39b73..a049a8095b9 100644
--- a/app/appearance/langs/zh_CN.json
+++ b/app/appearance/langs/zh_CN.json
@@ -1,4 +1,17 @@
{
+ "publish": "发布",
+ "publishService": "发布服务",
+ "publishServiceNotStarted": "发布服务未启动",
+ "publishServiceTip": "启用后将启动发布服务。该服务以只读模式在局域网中发布当前工作空间的内容",
+ "publishServicePort": "服务端口号",
+ "publishServicePortTip": "使用指定的端口号启用发布服务。若设置为 0
则使用随机端口",
+ "publishServiceAddresses": "服务访问地址",
+ "publishServiceAddressesTip": "可能访问到发布服务的网络地址",
+ "publishServiceAuth": "服务 Basic 认证",
+ "publishServiceAuthTip": "启用后访问者在访问发布服务时需要使用用户名与密码进行认证",
+ "publishServiceAuthAccounts": "认证账户",
+ "publishServiceAuthAccountsTip": "Basic 认证账户列表。访问者输入列表中的用户名与密码后才能查看发布的内容",
+ "publishServiceAuthAccountAdd": "添加账户",
"copyMirror": "复制镜像",
"duplicateMirror": "复制为镜像副本",
"duplicateCompletely": "复制为完整副本",
diff --git a/app/guide/20210808180117-6v0mkxr/.gitignore b/app/guide/20210808180117-6v0mkxr/.gitignore
deleted file mode 100644
index 5e86e558d03..00000000000
--- a/app/guide/20210808180117-6v0mkxr/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.idea/
-.siyuan/history/
diff --git a/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json b/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json
index a61e32b0b0a..cfb82eeda54 100644
--- a/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json
+++ b/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json
@@ -1 +1 @@
-{"20200923234011-ieuun1p":1,"20200923234602-gy54e67":8,"20200923234731-h3zkwm2":3,"20200924093441-ft2rhps":1,"20200924095938-a9p5450":2,"20200924100110-vcg96wy":1,"20200924100635-ms0p9lb":6,"20200924100717-yzwzn64":21,"20200924100744-br924ar":10,"20200924100808-j9sddk9":2,"20200924100906-0u4zfq3":4,"20200924100950-9op5xi1":18,"20200924101106-19z4kaa":1,"20200924101200-gss5vee":4,"20200924101225-k254i8g":2,"20200924101256-f8b1sbi":3,"20201004194026-s8h2cog":19,"20201117112518-dott91x":6,"20201121224345-rc27qvo":9,"20201204184532-3qm9l8n":11,"20201210233038-3xr19g5":5,"20201222100222-q47d64s":3,"20201222100339-i5hzcph":2,"20201227201128-m1wrouw":20,"20201227201751-gv0fpx2":22,"20210110181011-fbhoesf":5,"20210117215840-jcl17fx":4,"20210127203829-qe2mzof":12,"20210331201142-4g923es":14,"20210505164949-c085p1d":3,"20210613191509-cbkxcbz":7,"20210615213222-vs5tzbd":15,"20210721112159-9p645xm":1,"20210721112206-mhr9wxi":2,"20210721160238-yvhbh0h":4,"20210808180303-6yi0dv5":1,"20210808180303-axh6q1d":4,"20210808180303-h361q1i":2,"20210808180303-l3qg72k":3,"20210808180303-xaduj2o":5,"20210824202056-udkf7wg":8,"20211010212318-3wx2kqb":13,"20220105101227-n5zpr1a":6,"20220415232231-pqcizol":1,"20220628204454-hhxohv5":2,"20220708103401-mgydrfg":3,"20221016204105-qx2aq0g":3,"20221223221636-ms2b4w9":16,"20230104152135-1iei0xa":23,"20230106104821-9nfphwm":1,"20230304000547-ibldj1z":17,"20230405172236-pg3l9eu":6,"20230429115711-ejbts4s":5,"20230506205948-yah52eb":9,"20230802114825-2jkkct7":5,"20230805231614-vqn28eh":7,"20230805231816-h1z9mpc":2,"20230805232018-hgrq0ju":1,"20230805232134-3d6mx2k":2,"20240113110040-7sgw8kl":2,"20240119211017-1vbbt95":4,"20240119212048-0huuevw":5,"20240208172514-9dsv6na":7,"20240317202444-5txwumu":7}
\ No newline at end of file
+{"20200923234011-ieuun1p":1,"20200923234602-gy54e67":8,"20200923234731-h3zkwm2":3,"20200924093441-ft2rhps":1,"20200924095938-a9p5450":2,"20200924100110-vcg96wy":1,"20200924100635-ms0p9lb":6,"20200924100717-yzwzn64":21,"20200924100744-br924ar":10,"20200924100808-j9sddk9":2,"20200924100906-0u4zfq3":4,"20200924100950-9op5xi1":18,"20200924101106-19z4kaa":1,"20200924101200-gss5vee":4,"20200924101225-k254i8g":2,"20200924101256-f8b1sbi":3,"20201004194026-s8h2cog":19,"20201117112518-dott91x":6,"20201121224345-rc27qvo":9,"20201204184532-3qm9l8n":11,"20201210233038-3xr19g5":5,"20201222100222-q47d64s":3,"20201222100339-i5hzcph":2,"20201227201128-m1wrouw":20,"20201227201751-gv0fpx2":22,"20210110181011-fbhoesf":5,"20210117215840-jcl17fx":4,"20210127203829-qe2mzof":12,"20210331201142-4g923es":14,"20210505164949-c085p1d":3,"20210613191509-cbkxcbz":7,"20210615213222-vs5tzbd":15,"20210721112159-9p645xm":1,"20210721112206-mhr9wxi":2,"20210721160238-yvhbh0h":4,"20210808180303-6yi0dv5":1,"20210808180303-axh6q1d":4,"20210808180303-h361q1i":2,"20210808180303-l3qg72k":3,"20210808180303-xaduj2o":5,"20210824202056-udkf7wg":8,"20211010212318-3wx2kqb":13,"20220105101227-n5zpr1a":6,"20220415232231-pqcizol":1,"20220628204454-hhxohv5":2,"20220708103401-mgydrfg":3,"20221016204105-qx2aq0g":3,"20221223221636-ms2b4w9":16,"20230104152135-1iei0xa":23,"20230106104821-9nfphwm":1,"20230304000547-ibldj1z":17,"20230405172236-pg3l9eu":6,"20230429115711-ejbts4s":5,"20230506205948-yah52eb":9,"20230802114825-2jkkct7":5,"20230805231614-vqn28eh":7,"20230805231816-h1z9mpc":2,"20230805232018-hgrq0ju":1,"20230805232134-3d6mx2k":2,"20240113110040-7sgw8kl":2,"20240119211017-1vbbt95":4,"20240119212048-0huuevw":5,"20240208172514-9dsv6na":7,"20240317202444-5txwumu":7,"20240517031132-jmr5ihm":24}
\ No newline at end of file
diff --git a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20240517031132-jmr5ihm.sy b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20240517031132-jmr5ihm.sy
new file mode 100644
index 00000000000..afef5d773c7
--- /dev/null
+++ b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20240517031132-jmr5ihm.sy
@@ -0,0 +1,526 @@
+{
+ "ID": "20240517031132-jmr5ihm",
+ "Spec": "1",
+ "Type": "NodeDocument",
+ "Properties": {
+ "id": "20240517031132-jmr5ihm",
+ "title": "Publish service",
+ "type": "doc",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-um049u9",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031304-um049u9",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Overview"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-kebcgzv",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-kebcgzv",
+ "updated": "20240517031305"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Joplin supports publishing the content of the current workspace in read-only mode on the local area network."
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-7e0kvj3",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031304-7e0kvj3",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Usage"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-gt5qj60",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031304-gt5qj60",
+ "updated": "20240517031305"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-sjppgk3",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-sjppgk3",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-r9a0anm",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-r9a0anm",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Open "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Settings"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " - "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Publish"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " to enter the publishing service settings panel."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-u6inl9h",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-u6inl9h",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-ye7ittw",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-ye7ittw",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Set the "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Server port"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-olvdqhj",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031304-olvdqhj",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-1ttdtl4",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-1ttdtl4",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-sl59b81",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-sl59b81",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "The default port number is "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "6808"
+ },
+ {
+ "Type": "NodeText",
+ "Data": "."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-t37dfgj",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-t37dfgj",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-mxx5df4",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-mxx5df4",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "If the port number is set to "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "0"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ", a random port will be used."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-y4kcmaf",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-y4kcmaf",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-5qilsih",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-5qilsih",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "If access control is required for the publishing service:"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-659m8fi",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031304-659m8fi",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-xh0kq2y",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-xh0kq2y",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-lay209u",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-lay209u",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Add "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Authenticated accounts"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " and enable the "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Service basic authentication"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " switch."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-ri1nm5j",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-ri1nm5j",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-4y267ml",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-4y267ml",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "When enabled, the publishing service will use the "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "a",
+ "TextMarkAHref": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme",
+ "TextMarkTextContent": "Basic authentication scheme"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " to authenticate visitors."
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-h61xxm7",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031304-h61xxm7",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-1k96w0z",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-1k96w0z",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-l5hafpz",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-l5hafpz",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Visitors must enter the username and password set in the \"Authentication Accounts\" before browsing the published content."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-0nps2p8",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-0nps2p8",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-y16fc8r",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-y16fc8r",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Enable the "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Publish service"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " switch."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-6cy4lbq",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031304-6cy4lbq",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "Note"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-3slh48m",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031304-3slh48m",
+ "updated": "20240517031305"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-uquw6i6",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-uquw6i6",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-j38u7hu",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-j38u7hu",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "After enabling the publishing service, visitors can browse the content of the entire workspace."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031304-c6iqquc",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031304-c6iqquc",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "ID": "20240517031304-1qsa7qz",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031304-1qsa7qz",
+ "updated": "20240517031304"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "When "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Service basic authentication"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " is disabled, all visitors can browse the content of the entire workspace without authentication."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/guide/20210808180117-czj9bvb/.gitignore b/app/guide/20210808180117-czj9bvb/.gitignore
deleted file mode 100644
index 5e86e558d03..00000000000
--- a/app/guide/20210808180117-czj9bvb/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.idea/
-.siyuan/history/
diff --git a/app/guide/20210808180117-czj9bvb/.siyuan/sort.json b/app/guide/20210808180117-czj9bvb/.siyuan/sort.json
index 90390d00071..8be2ae31d0e 100644
--- a/app/guide/20210808180117-czj9bvb/.siyuan/sort.json
+++ b/app/guide/20210808180117-czj9bvb/.siyuan/sort.json
@@ -1 +1 @@
-{"20200812220555-lj3enxa":1,"20200813004551-gm0pbn1":18,"20200813004931-q4cu8na":1,"20200813013559-sgbzl5k":3,"20200813093015-u6bopdt":3,"20200813125307-pxsjela":2,"20200813131152-0wk5akh":4,"20200813163359-v04n73b":10,"20200822191536-rm6hwid":4,"20200825162036-4dx365o":1,"20200828105441-r76vmu5":21,"20200905090211-2vixtlf":2,"20200910201551-h4twhas":6,"20200915214115-42b8zma":10,"20200922102318-oz84yu3":2,"20201004184819-nj8ibyg":19,"20201117101902-2ewjjum":6,"20201121212605-9td1a62":11,"20201204181006-7bkppue":11,"20201210103036-1x3vm8t":5,"20201222093044-rx4zjoy":2,"20201222095049-hghafhe":3,"20201227173504-847cs1q":20,"20201227194925-7ipoiv6":22,"20210110175347-2xrwoiq":5,"20210117211155-56n4odu":4,"20210127202655-2334vvv":12,"20210331200042-94gs1hh":14,"20210505163537-oo97zov":3,"20210612224500-ywcms1m":7,"20210615211733-v6rzowm":15,"20210808180320-abz7w6k":2,"20210808180320-fqgskfj":1,"20210808180320-gyngv2x":3,"20210808180320-qgr0b3q":5,"20210808180321-hbvl5c2":6,"20210824201257-cy7icrc":8,"20211010211311-ffz0wbu":13,"20220415190432-r3xqn3r":1,"20220628204444-9n0y9h2":2,"20221016213308-uz5af79":3,"20221223215557-o6gfsoy":16,"20230104144904-39br4c6":23,"20230106101434-e6g4av3":1,"20230303235619-ex5l63e":17,"20230405155631-leo4vc6":6,"20230428153709-hioyy5l":7,"20230429114837-70asb4j":5,"20230506210010-houyyvy":9,"20230519105228-hm0y74i":8,"20230805222417-2lj3dvk":7,"20230805225107-qm1m2f5":2,"20230805230131-sn7obzb":1,"20230805230218-aea8icj":2,"20230808120347-3cob0nb":2,"20230808120347-mw3qrwy":4,"20230808120347-pzvmkik":1,"20230808120348-hynr7og":5,"20230808120348-lgcp9zm":3,"20230808120348-vaxi6eq":6,"20230808120348-yut741f":7,"20240113102857-c63dmo5":2,"20240119205452-o8xp4ve":4,"20240119205543-hknwwrl":5,"20240208113259-nykkvaq":7,"20240317200013-fim8wm8":9}
\ No newline at end of file
+{"20200812220555-lj3enxa":1,"20200813004551-gm0pbn1":18,"20200813004931-q4cu8na":1,"20200813013559-sgbzl5k":3,"20200813093015-u6bopdt":3,"20200813125307-pxsjela":2,"20200813131152-0wk5akh":4,"20200813163359-v04n73b":10,"20200822191536-rm6hwid":4,"20200825162036-4dx365o":1,"20200828105441-r76vmu5":21,"20200905090211-2vixtlf":2,"20200910201551-h4twhas":6,"20200915214115-42b8zma":10,"20200922102318-oz84yu3":2,"20201004184819-nj8ibyg":19,"20201117101902-2ewjjum":6,"20201121212605-9td1a62":11,"20201204181006-7bkppue":11,"20201210103036-1x3vm8t":5,"20201222093044-rx4zjoy":2,"20201222095049-hghafhe":3,"20201227173504-847cs1q":20,"20201227194925-7ipoiv6":22,"20210110175347-2xrwoiq":5,"20210117211155-56n4odu":4,"20210127202655-2334vvv":12,"20210331200042-94gs1hh":14,"20210505163537-oo97zov":3,"20210612224500-ywcms1m":7,"20210615211733-v6rzowm":15,"20210808180320-abz7w6k":2,"20210808180320-fqgskfj":1,"20210808180320-gyngv2x":3,"20210808180320-qgr0b3q":5,"20210808180321-hbvl5c2":6,"20210824201257-cy7icrc":8,"20211010211311-ffz0wbu":13,"20220415190432-r3xqn3r":1,"20220628204444-9n0y9h2":2,"20221016213308-uz5af79":3,"20221223215557-o6gfsoy":16,"20230104144904-39br4c6":23,"20230106101434-e6g4av3":1,"20230303235619-ex5l63e":17,"20230405155631-leo4vc6":6,"20230428153709-hioyy5l":7,"20230429114837-70asb4j":5,"20230506210010-houyyvy":9,"20230519105228-hm0y74i":8,"20230805222417-2lj3dvk":7,"20230805225107-qm1m2f5":2,"20230805230131-sn7obzb":1,"20230805230218-aea8icj":2,"20230808120347-3cob0nb":2,"20230808120347-mw3qrwy":4,"20230808120347-pzvmkik":1,"20230808120348-hynr7og":5,"20230808120348-lgcp9zm":3,"20230808120348-vaxi6eq":6,"20230808120348-yut741f":7,"20240113102857-c63dmo5":2,"20240119205452-o8xp4ve":4,"20240119205543-hknwwrl":5,"20240208113259-nykkvaq":7,"20240317200013-fim8wm8":9,"20240517031028-xqinazo":24}
\ No newline at end of file
diff --git a/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20240517031028-xqinazo.sy b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20240517031028-xqinazo.sy
new file mode 100644
index 00000000000..9e2bbbc1862
--- /dev/null
+++ b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20240517031028-xqinazo.sy
@@ -0,0 +1,540 @@
+{
+ "ID": "20240517031028-xqinazo",
+ "Spec": "1",
+ "Type": "NodeDocument",
+ "Properties": {
+ "id": "20240517031028-xqinazo",
+ "title": "发布服务",
+ "type": "doc",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-7ox0m9z",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031044-7ox0m9z",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "概述"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-6mppc0k",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-6mppc0k",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "思源笔记支持以只读模式在局域网中发布当前工作空间的内容。"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-anhkwow",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031044-anhkwow",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "使用方式"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-0vj6or7",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031044-0vj6or7",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-8ugboo7",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-8ugboo7",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-grogeue",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-grogeue",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "打开 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "设置"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " - "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "发布"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 进入发布服务的设置面板"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-mta08zv",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-mta08zv",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-7dg9xy9",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-7dg9xy9",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "设置 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "服务端口号"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-vzj8o1a",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031044-vzj8o1a",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-t2igim6",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-t2igim6",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-8ghv9tj",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-8ghv9tj",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "默认的端口号为 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "6808"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-3oto1iz",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-3oto1iz",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-0m68jzx",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-0m68jzx",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "若端口号设置为 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "0"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 将使用随机端口"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-ddii8sb",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-ddii8sb",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-vaiskyz",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-vaiskyz",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "若需要对发布服务进行访问控制"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-ze7l6sg",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031044-ze7l6sg",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-fxpcxvc",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-fxpcxvc",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-bno9bca",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-bno9bca",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "添加 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "认证账户"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 并开启 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "服务 Basic 认证"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 开关"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-29kw4ks",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-29kw4ks",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-g05n8gc",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-g05n8gc",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "开启后发布服务将使用 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "a",
+ "TextMarkAHref": "https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication#basic_验证方案",
+ "TextMarkTextContent": "Basic 验证方案"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 对访问者进行认证"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-e54f9vf",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031044-e54f9vf",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-kr1t232",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-kr1t232",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-rf8nyba",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-rf8nyba",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "访问者在浏览发布内容前必须正确输入 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "认证账户"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 中设置的用户名与密码"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-s5d5aak",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-s5d5aak",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-ec9vaac",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-ec9vaac",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "开启 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "发布服务"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 开关"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-6le4t1r",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031044-6le4t1r",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "tag",
+ "TextMarkTextContent": "注意"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-ahzjzvg",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031044-ahzjzvg",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-8f3g2fd",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-8f3g2fd",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-uiys91u",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-uiys91u",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "开启发布服务后访问者可以浏览整个工作空间的内容"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031044-979coq3",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031044-979coq3",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "ID": "20240517031044-7be46qj",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031044-7be46qj",
+ "updated": "20240517031044"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "关闭 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "服务 Basic 认证"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 后所有的访问者无需认证即可浏览整个工作空间的内容"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/guide/20211226090932-5lcq56f/.gitignore b/app/guide/20211226090932-5lcq56f/.gitignore
deleted file mode 100644
index 5e86e558d03..00000000000
--- a/app/guide/20211226090932-5lcq56f/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.idea/
-.siyuan/history/
diff --git a/app/guide/20211226090932-5lcq56f/.siyuan/sort.json b/app/guide/20211226090932-5lcq56f/.siyuan/sort.json
index b16c52c8f60..30845523009 100644
--- a/app/guide/20211226090932-5lcq56f/.siyuan/sort.json
+++ b/app/guide/20211226090932-5lcq56f/.siyuan/sort.json
@@ -1 +1 @@
-{"20211226114339-dk0gtpr":8,"20211226114929-08ap1r0":9,"20211226115043-afhev0g":4,"20211226115227-r1rty9v":3,"20211226115423-d5z1joq":1,"20211226115825-mhcslw2":1,"20211226120055-9mityht":1,"20211226120147-ib6yy3i":2,"20211226120247-63nd8y5":3,"20211226120349-rbkmozu":4,"20211226120422-bkzsd2e":5,"20211226120508-yzh70eh":6,"20211226120802-77aj0is":7,"20211226120854-dr1jfx2":2,"20211226120933-vnjgwwh":3,"20211226121109-f060fkg":4,"20211226121203-rjjngpz":5,"20211226121319-emrk2yy":1,"20211226121322-9argcys":3,"20211226121329-c5v3dto":22,"20211226121332-irgblss":4,"20211226121438-xaafdo8":2,"20211226121503-k3jma6m":1,"20211226121808-fnxmngk":2,"20211226122358-hctqcn5":21,"20211226122459-08mi5cq":20,"20211226122523-rl8356a":19,"20211226122549-jktxego":18,"20211226122707-8cr09co":15,"20211226122728-cnqf7rz":14,"20211226122814-r1rdpcx":13,"20211226122943-st7fpcj":12,"20211226123004-dplpw0o":11,"20211226123038-4umgpxy":10,"20211226123101-qjw03ab":8,"20211226123130-jpeg5b2":6,"20211226123154-fd5e001":5,"20211226123216-tlxw66f":4,"20211226123241-51pujtr":3,"20211226123302-akitvb1":2,"20220105101348-corstqc":6,"20220415232129-shpzg6r":1,"20220628204420-ui79vkt":2,"20220708102441-u6wopo9":3,"20221016213639-1nag9jj":3,"20221223221501-mops33i":16,"20230104151953-48hwkwf":23,"20230106104645-o838uew":1,"20230304000829-9jwu3po":17,"20230405172131-yb16aax":6,"20230429115206-ob8nl8t":5,"20230506211210-1roopyo":9,"20230805232636-zh0adz2":6,"20230805232719-04mqbcx":2,"20230805232903-erdoerp":1,"20230805232920-5fdco36":2,"20240113110500-dz2ae4n":2,"20240119210914-a2tm8c4":4,"20240119212000-qkldbjm":5,"20240208171522-y7dxcno":7,"20240317202230-l8duv3r":7,"20240508010647-8nuyk31":5}
\ No newline at end of file
+{"20211226114339-dk0gtpr":8,"20211226114929-08ap1r0":9,"20211226115043-afhev0g":4,"20211226115227-r1rty9v":3,"20211226115423-d5z1joq":1,"20211226115825-mhcslw2":1,"20211226120055-9mityht":1,"20211226120147-ib6yy3i":2,"20211226120247-63nd8y5":3,"20211226120349-rbkmozu":4,"20211226120422-bkzsd2e":5,"20211226120508-yzh70eh":6,"20211226120802-77aj0is":7,"20211226120854-dr1jfx2":2,"20211226120933-vnjgwwh":3,"20211226121109-f060fkg":4,"20211226121203-rjjngpz":5,"20211226121319-emrk2yy":1,"20211226121322-9argcys":3,"20211226121329-c5v3dto":22,"20211226121332-irgblss":4,"20211226121438-xaafdo8":2,"20211226121503-k3jma6m":1,"20211226121808-fnxmngk":2,"20211226122358-hctqcn5":21,"20211226122459-08mi5cq":20,"20211226122523-rl8356a":19,"20211226122549-jktxego":18,"20211226122707-8cr09co":15,"20211226122728-cnqf7rz":14,"20211226122814-r1rdpcx":13,"20211226122943-st7fpcj":12,"20211226123004-dplpw0o":11,"20211226123038-4umgpxy":10,"20211226123101-qjw03ab":8,"20211226123130-jpeg5b2":6,"20211226123154-fd5e001":5,"20211226123216-tlxw66f":4,"20211226123241-51pujtr":3,"20211226123302-akitvb1":2,"20220105101348-corstqc":6,"20220415232129-shpzg6r":1,"20220628204420-ui79vkt":2,"20220708102441-u6wopo9":3,"20221016213639-1nag9jj":3,"20221223221501-mops33i":16,"20230104151953-48hwkwf":23,"20230106104645-o838uew":1,"20230304000829-9jwu3po":17,"20230405172131-yb16aax":6,"20230429115206-ob8nl8t":5,"20230506211210-1roopyo":9,"20230805232636-zh0adz2":6,"20230805232719-04mqbcx":2,"20230805232903-erdoerp":1,"20230805232920-5fdco36":2,"20240113110500-dz2ae4n":2,"20240119210914-a2tm8c4":4,"20240119212000-qkldbjm":5,"20240208171522-y7dxcno":7,"20240317202230-l8duv3r":7,"20240508010647-8nuyk31":5,"20240517031052-mgoxs16":24}
\ No newline at end of file
diff --git a/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20240517031052-mgoxs16.sy b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20240517031052-mgoxs16.sy
new file mode 100644
index 00000000000..f5b1cf78119
--- /dev/null
+++ b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20240517031052-mgoxs16.sy
@@ -0,0 +1,540 @@
+{
+ "ID": "20240517031052-mgoxs16",
+ "Spec": "1",
+ "Type": "NodeDocument",
+ "Properties": {
+ "id": "20240517031052-mgoxs16",
+ "title": "發布服務",
+ "type": "doc",
+ "updated": "20240517031115"
+ },
+ "Children": [
+ {
+ "ID": "20240517031115-pjkrctx",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031115-pjkrctx",
+ "updated": "20240517031115"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "概述"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-a0xf075",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-a0xf075",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "思源筆記支援以只讀模式在區域網路中發佈當前工作空間的內容。"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-ml8jd6n",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240517031116-ml8jd6n",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "使用方式"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-5u42tlr",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031116-5u42tlr",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-06mevu7",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-06mevu7",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-wqo2aje",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-wqo2aje",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "開啟 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "設定"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " - "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "發佈"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 進入發佈服務的設定面板"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-dsvhebl",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-dsvhebl",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-13481er",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-13481er",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "設定 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "服務端口號"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-sjbg5cn",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031116-sjbg5cn",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-z1q43ui",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-z1q43ui",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-ssdjmbf",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-ssdjmbf",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "默認的端口號為 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "6808"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-jkcxgaj",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-jkcxgaj",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-ka1wome",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-ka1wome",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "若端口號設定為 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "0"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 將使用隨機端口"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-ipkirs4",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-ipkirs4",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-g8qr29s",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-g8qr29s",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "若需要對發佈服務進行訪問控制"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-maj6mmd",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031116-maj6mmd",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-du7rttx",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-du7rttx",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-ct3chrz",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-ct3chrz",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "添加 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "認證帳戶"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 並開啟 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "服務 Basic 認證"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 開關"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-dlaep2b",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-dlaep2b",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-3u2v8g7",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-3u2v8g7",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "開啟後發佈服務將使用 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "a",
+ "TextMarkAHref": "https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication#basic_验证方案",
+ "TextMarkTextContent": "Basic 驗證方案"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 對訪問者進行認證"
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-qmzfje7",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031116-qmzfje7",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-xthto7w",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-xthto7w",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-kbz5qye",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-kbz5qye",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "訪問者在瀏覽發佈內容前必須正確輸入 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "認證帳戶"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 中設定的用戶名與密碼"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-bx01v3n",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-bx01v3n",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-44j60lx",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-44j60lx",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "開啟 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "發佈服務"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 開關"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-2v0lh0m",
+ "Type": "NodeHeading",
+ "HeadingLevel": 3,
+ "Properties": {
+ "id": "20240517031116-2v0lh0m",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "tag",
+ "TextMarkTextContent": "注意"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-lx0pe26",
+ "Type": "NodeList",
+ "ListData": {},
+ "Properties": {
+ "id": "20240517031116-lx0pe26",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-kvje8ru",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-kvje8ru",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-gydcx3b",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-gydcx3b",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "開啟發佈服務後訪問者可以瀏覽整個工作空間的內容"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240517031116-ufsul2c",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20240517031116-ufsul2c",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "ID": "20240517031116-xyb4012",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240517031116-xyb4012",
+ "updated": "20240517031116"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "關閉 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "服務 Basic 認證"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " 後所有的訪問者無需認證即可瀏覽整個工作空間的內容"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/guide/20240530133126-axarxgx/.siyuan/sort.json b/app/guide/20240530133126-axarxgx/.siyuan/sort.json
index f312046cefa..79c3184bfbe 100644
--- a/app/guide/20240530133126-axarxgx/.siyuan/sort.json
+++ b/app/guide/20240530133126-axarxgx/.siyuan/sort.json
@@ -1 +1 @@
-{"20240530101000-0zd9si2":13,"20240530101000-1m5un7l":10,"20240530101000-1vvnhju":6,"20240530101000-35bbvcx":2,"20240530101000-3eaevtp":7,"20240530101000-3ourzoa":2,"20240530101000-3qhz7br":1,"20240530101000-3xv6jjr":9,"20240530101000-40hohog":19,"20240530101000-4cqkoel":23,"20240530101000-4p096e8":1,"20240530101000-4qitucx":1,"20240530101000-58z6rjh":2,"20240530101000-5gi76ax":8,"20240530101000-5k5d5i3":2,"20240530101000-6x9ivi7":11,"20240530101000-75auqcn":6,"20240530101000-78d5w10":4,"20240530101000-7yv4y5z":4,"20240530101000-875snki":7,"20240530101000-8m2dowc":9,"20240530101000-96n5y9v":1,"20240530101000-a91lmk2":3,"20240530101000-bb0qktp":1,"20240530101000-bgv304g":8,"20240530101000-boiita2":4,"20240530101000-bpj42r0":2,"20240530101000-cb37szr":7,"20240530101000-d31i9v5":2,"20240530101000-dro2zi9":5,"20240530101000-dytmky4":22,"20240530101000-e6z5okf":3,"20240530101000-ei5t6tt":17,"20240530101000-f16hpct":20,"20240530101000-flot1gj":7,"20240530101000-g3ugxml":5,"20240530101000-gc0tuyd":3,"20240530101000-gcdp3gl":3,"20240530101000-gvtksyj":1,"20240530101000-h0cp5vx":5,"20240530101000-hkgs92c":5,"20240530101000-hnu6o79":2,"20240530101000-hq9sn3k":1,"20240530101000-jp793ic":4,"20240530101000-mpln2lp":6,"20240530101000-mrlr6e9":4,"20240530101000-na9sys7":1,"20240530101000-odye3ea":3,"20240530101000-qf0xtkd":2,"20240530101000-scr2p21":5,"20240530101000-to5uvpi":12,"20240530101000-txrc7z1":15,"20240530101000-uxno1yp":3,"20240530101000-vejnh1k":1,"20240530101000-vql5s27":5,"20240530101000-wo49zvq":2,"20240530101000-xc13cm6":6,"20240530101000-xq26o73":21,"20240530101000-xr22qn8":4,"20240530101000-xsbxokr":18,"20240530101000-ynpmvl7":16,"20240530101000-zj4k048":3,"20240530101000-znj103k":10,"20240530101000-zprnpfi":14}
\ No newline at end of file
+{"20240530101000-0zd9si2":13,"20240530101000-1m5un7l":10,"20240530101000-1vvnhju":6,"20240530101000-35bbvcx":2,"20240530101000-3eaevtp":7,"20240530101000-3ourzoa":2,"20240530101000-3qhz7br":1,"20240530101000-3xv6jjr":9,"20240530101000-40hohog":19,"20240530101000-4cqkoel":23,"20240530101000-4p096e8":1,"20240530101000-4qitucx":1,"20240530101000-58z6rjh":2,"20240530101000-5gi76ax":8,"20240530101000-5k5d5i3":2,"20240530101000-6x9ivi7":11,"20240530101000-75auqcn":6,"20240530101000-78d5w10":4,"20240530101000-7yv4y5z":4,"20240530101000-875snki":7,"20240530101000-8m2dowc":9,"20240530101000-96n5y9v":1,"20240530101000-a91lmk2":3,"20240530101000-bb0qktp":1,"20240530101000-bgv304g":8,"20240530101000-boiita2":4,"20240530101000-bpj42r0":2,"20240530101000-cb37szr":7,"20240530101000-d31i9v5":2,"20240530101000-dro2zi9":5,"20240530101000-dytmky4":22,"20240530101000-e6z5okf":3,"20240530101000-ei5t6tt":17,"20240530101000-f16hpct":20,"20240530101000-flot1gj":7,"20240530101000-g3ugxml":5,"20240530101000-gc0tuyd":3,"20240530101000-gcdp3gl":3,"20240530101000-gvtksyj":1,"20240530101000-h0cp5vx":5,"20240530101000-hkgs92c":5,"20240530101000-hnu6o79":2,"20240530101000-hq9sn3k":1,"20240530101000-jp793ic":4,"20240530101000-mpln2lp":6,"20240530101000-mrlr6e9":4,"20240530101000-na9sys7":1,"20240530101000-odye3ea":3,"20240530101000-qf0xtkd":2,"20240530101000-scr2p21":5,"20240530101000-to5uvpi":12,"20240530101000-txrc7z1":15,"20240530101000-uxno1yp":3,"20240530101000-vejnh1k":1,"20240530101000-vql5s27":5,"20240530101000-wo49zvq":2,"20240530101000-xc13cm6":6,"20240530101000-xq26o73":21,"20240530101000-xr22qn8":4,"20240530101000-xsbxokr":18,"20240530101000-ynpmvl7":16,"20240530101000-zj4k048":3,"20240530101000-znj103k":10,"20240530101000-zprnpfi":14,"20240610233601-ylh1mvk":24}
\ No newline at end of file
diff --git a/app/guide/20240530133126-axarxgx/20240530101000-4qitucx/20240530101000-g3ugxml/20240610233601-ylh1mvk.sy b/app/guide/20240530133126-axarxgx/20240530101000-4qitucx/20240530101000-g3ugxml/20240610233601-ylh1mvk.sy
new file mode 100644
index 00000000000..f05e8f801b1
--- /dev/null
+++ b/app/guide/20240530133126-axarxgx/20240530101000-4qitucx/20240530101000-g3ugxml/20240610233601-ylh1mvk.sy
@@ -0,0 +1,676 @@
+{
+ "ID": "20240610233601-ylh1mvk",
+ "Spec": "1",
+ "Type": "NodeDocument",
+ "Properties": {
+ "id": "20240610233601-ylh1mvk",
+ "title": "サービスの公開",
+ "type": "doc",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-jjii9tu",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240610233629-jjii9tu",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "Type": "NodeHeadingC8hMarker",
+ "Data": "## ",
+ "Properties": {
+ "id": ""
+ }
+ },
+ {
+ "Type": "NodeText",
+ "Data": "概要",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-49em84g",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-49em84g",
+ "updated": "20240610233739"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "SiYuan Note は、現在のワークスペースの内容を読み取り専用モードでローカルネットワークで公開することができます。",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-bxuf2mx",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240610233629-bxuf2mx",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "使い方"
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-7d7e66r",
+ "Type": "NodeList",
+ "ListData": {
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-7d7e66r",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-jn7c5xq",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-jn7c5xq",
+ "updated": "20240610234354"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-ibn4bp6",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-ibn4bp6",
+ "updated": "20240610234354"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": ""
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "設定"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " - "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "公開する"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " に移動して、公開サービスの設定パネルに入ります。"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-j8ml6bw",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-j8ml6bw",
+ "updated": "20240610234344"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-fubq5bz",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-fubq5bz",
+ "updated": "20240610234344"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": ""
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "サービスポート"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " を設定します。"
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-d9q97le",
+ "Type": "NodeList",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-d9q97le",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-y8ynj1h",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-y8ynj1h",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-fwe6fgu",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-fwe6fgu",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "デフォルトのポート番号は ",
+ "Properties": {
+ "id": ""
+ }
+ },
+ {
+ "Type": "NodeTextMark",
+ "Properties": {
+ "id": ""
+ },
+ "TextMarkType": "code",
+ "TextMarkTextContent": "6808"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " です。",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-amkxqp8",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-amkxqp8",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-726ai0u",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-726ai0u",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "ポート番号を ",
+ "Properties": {
+ "id": ""
+ }
+ },
+ {
+ "Type": "NodeTextMark",
+ "Properties": {
+ "id": ""
+ },
+ "TextMarkType": "code",
+ "TextMarkTextContent": "0"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " に設定すると、ランダムなポートが使用されます。",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-c2bncke",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-c2bncke",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-c02853s",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-c02853s",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "公開サービスにアクセス制限を設定する場合",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-a5alsxq",
+ "Type": "NodeList",
+ "ListData": {
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-a5alsxq",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-4p8jyop",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-4p8jyop",
+ "updated": "20240610235142"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-6pki0nz",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-6pki0nz",
+ "updated": "20240610235142"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": ""
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "認証アカウント"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " を追加し、"
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "サービスの基本認証"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " スイッチをオンにします。"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-3s2kd31",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-3s2kd31",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-fozs5am",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-fozs5am",
+ "updated": "20240610233721"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "この後、公開サービスはアクセスするユーザーを ",
+ "Properties": {
+ "id": ""
+ }
+ },
+ {
+ "Type": "NodeTextMark",
+ "Properties": {
+ "id": ""
+ },
+ "TextMarkType": "a",
+ "TextMarkAHref": "https://developer.mozilla.org/ja/docs/Web/HTTP/Authentication#basic_認証方式",
+ "TextMarkTextContent": "Basic 認証方式"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " を使用して認証します。",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-b18hlwj",
+ "Type": "NodeList",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-b18hlwj",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-4mziigo",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-4mziigo",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-526pequ",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-526pequ",
+ "updated": "20240610235250"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "ユーザーは、公開コンテンツを閲覧する前に、"
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "認証アカウント"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " で設定したユーザー名とパスワードを正しく入力する必要があります。"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-a4d5gct",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-a4d5gct",
+ "updated": "20240610234926"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-cqspvm0",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-cqspvm0",
+ "updated": "20240610234926"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": ""
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "サービスを公開する"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " スイッチをオンにします"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-ns08k8s",
+ "Type": "NodeHeading",
+ "HeadingLevel": 2,
+ "Properties": {
+ "id": "20240610233629-ns08k8s",
+ "updated": "20240610235147"
+ },
+ "Children": [
+ {
+ "Type": "NodeHeadingC8hMarker",
+ "Data": "## ",
+ "Properties": {
+ "id": ""
+ }
+ },
+ {
+ "Type": "NodeText",
+ "Data": "",
+ "Properties": {
+ "id": ""
+ }
+ },
+ {
+ "Type": "NodeTextMark",
+ "Properties": {
+ "id": ""
+ },
+ "TextMarkType": "tag",
+ "TextMarkTextContent": "注意事項"
+ },
+ {
+ "Type": "NodeText",
+ "Data": "",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-uqp9rz9",
+ "Type": "NodeList",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-uqp9rz9",
+ "updated": "20240610235147"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-jiv7u6m",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-jiv7u6m",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-j8ancdr",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-j8ancdr",
+ "updated": "20240610233629"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "公開サービスを開始すると、訪問者はワークスペースの全コンテンツを閲覧できます。",
+ "Properties": {
+ "id": ""
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20240610233629-10tmo1g",
+ "Type": "NodeListItem",
+ "Data": "*",
+ "ListData": {
+ "Tight": true,
+ "BulletChar": 42,
+ "Padding": 2,
+ "Marker": "Kg==",
+ "Num": -1
+ },
+ "Properties": {
+ "id": "20240610233629-10tmo1g",
+ "updated": "20240610235147"
+ },
+ "Children": [
+ {
+ "ID": "20240610233629-20lo5k4",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20240610233629-20lo5k4",
+ "updated": "20240610235147"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": ""
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "サービスの基本認証"
+ },
+ {
+ "Type": "NodeText",
+ "Data": " を無効にすると、すべての訪問者はワークスペースの全コンテンツを認証なしで閲覧できます。"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/config/editor.ts b/app/src/config/editor.ts
index 472e6b9616e..1dcd1b9c342 100644
--- a/app/src/config/editor.ts
+++ b/app/src/config/editor.ts
@@ -310,10 +310,12 @@ export const editor = {
if (fontFamilyElement.tagName === "SELECT") {
let fontFamilyHTML = ``;
fetchPost("/api/system/getSysFonts", {}, (response) => {
- response.data.forEach((item: string) => {
- fontFamilyHTML += ``;
- });
- fontFamilyElement.innerHTML = fontFamilyHTML;
+ if (response.code === 0) {
+ response.data.forEach((item: string) => {
+ fontFamilyHTML += ``;
+ });
+ fontFamilyElement.innerHTML = fontFamilyHTML;
+ }
});
}
editor.element.querySelector("#clearHistory").addEventListener("click", () => {
diff --git a/app/src/config/index.ts b/app/src/config/index.ts
index e55b4835551..4c246f6ef8b 100644
--- a/app/src/config/index.ts
+++ b/app/src/config/index.ts
@@ -13,6 +13,7 @@ import {query} from "./query";
import {Dialog} from "../dialog";
import {ai} from "./ai";
import {flashcard} from "./flashcard";
+import {publish} from "./publish";
import {App} from "../index";
import {isHuawei} from "../protyle/util/compatibility";
import {Constants} from "../constants";
@@ -79,6 +80,11 @@ export const genItemPanel = (type: string, containerElement: Element, app: App)
query.element = containerElement;
query.bindEvent();
break;
+ case "publish":
+ containerElement.innerHTML = publish.genHTML();
+ publish.element = containerElement;
+ publish.bindEvent();
+ break;
default:
break;
}
@@ -115,6 +121,7 @@ export const openSetting = (app: App) => {
${window.siyuan.languages.keymap}
${window.siyuan.languages.account}
${window.siyuan.languages.cloud}
+ ${window.siyuan.languages.publish}
${window.siyuan.languages.about}
@@ -131,6 +138,7 @@ export const openSetting = (app: App) => {
+
diff --git a/app/src/config/publish.ts b/app/src/config/publish.ts
new file mode 100644
index 00000000000..ecf81758cf0
--- /dev/null
+++ b/app/src/config/publish.ts
@@ -0,0 +1,213 @@
+import {hasClosestByMatchTag} from "../protyle/util/hasClosest";
+import {fetchPost} from "../util/fetch";
+
+export const publish = {
+ element: undefined as Element,
+ genHTML: () => {
+ return `
+
+
+
+
+
+
+
+ ${window.siyuan.languages.publishServicePort}
+
${window.siyuan.languages.publishServicePortTip}
+
+
+
+
+
+
+
+
+
+ ${window.siyuan.languages.publishServiceAddresses}
+
${window.siyuan.languages.publishServiceAddressesTip}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${window.siyuan.languages.publishServiceAuthAccounts}
+
${window.siyuan.languages.publishServiceAuthAccountsTip}
+
+
+
+
+
+
+
+
+`;
+ },
+ bindEvent: () => {
+ const publishAuthAccountAdd = publish.element.querySelector("#publishAuthAccountAdd");
+
+ /* add account */
+ publishAuthAccountAdd.addEventListener("click", () => {
+ window.siyuan.config.publish.auth.accounts.push({
+ username: "",
+ password: "",
+ memo: "",
+ });
+ publish._renderPublishAuthAccounts(publish.element);
+ });
+
+ /* input change */
+ publish.element.querySelectorAll("input").forEach(item => {
+ item.addEventListener("change", () => {
+ publish._savePublish();
+ });
+ });
+
+ publish._refreshPublish();
+ },
+ _refreshPublish: () => {
+ fetchPost("/api/setting/getPublish", {}, publish._updatePublishConfig.bind(null, true));
+ },
+ _savePublish: (reloadAccounts = true) => {
+ const publishEnable = publish.element.querySelector("#publishEnable");
+ const publishPort = publish.element.querySelector("#publishPort");
+ const publishAuthEnable = publish.element.querySelector("#publishAuthEnable");
+
+ fetchPost("/api/setting/setPublish", {
+ enable: publishEnable.checked,
+ port: publishPort.valueAsNumber,
+ auth: {
+ enable: publishAuthEnable.checked,
+ accounts: window.siyuan.config.publish.auth.accounts,
+ } as Config.IPublishAuth,
+ } as Config.IPublish, publish._updatePublishConfig.bind(null, reloadAccounts));
+ },
+ _updatePublishConfig: (
+ reloadAccounts: boolean,
+ response: IWebSocketData,
+ ) => {
+ if (response.code === 0) {
+ window.siyuan.config.publish = response.data.publish;
+
+ reloadAccounts && publish._renderPublishAuthAccounts(publish.element);
+ publish._renderPublishAddressList(publish.element, response.data.port);
+ } else {
+ publish._renderPublishAddressList(publish.element, 0);
+ }
+ },
+ _renderPublishAuthAccounts: (
+ element: Element,
+ accounts: Config.IPublishAuthAccount[] = window.siyuan.config.publish.auth.accounts,
+ ) => {
+ const publishAuthAccounts = element.querySelector("#publishAuthAccounts");
+ publishAuthAccounts.innerHTML = ``;
+
+ /* account field changed */
+ publishAuthAccounts
+ .querySelectorAll("input")
+ .forEach(input => {
+ input.addEventListener("change", () => {
+ const li = hasClosestByMatchTag(input, "LI");
+ if (li) {
+ const index = parseInt(li.dataset.index);
+ const name = input.dataset.name as keyof Config.IPublishAuthAccount;
+ if (name in window.siyuan.config.publish.auth.accounts[index]) {
+ window.siyuan.config.publish.auth.accounts[index][name] = input.value;
+ publish._savePublish(false);
+ }
+ }
+ });
+ });
+
+ /* delete account */
+ publishAuthAccounts
+ .querySelectorAll('.block__icon[data-action="remove"]')
+ .forEach(remove => {
+ remove.addEventListener("click", () => {
+ const li = hasClosestByMatchTag(remove, "LI");
+ if (li) {
+ const index = parseInt(li.dataset.index);
+ window.siyuan.config.publish.auth.accounts.splice(index, 1);
+ publish._savePublish();
+ }
+ });
+ });
+
+ /* Toggle the password display status */
+ publishAuthAccounts
+ .querySelectorAll('.b3-form__icona-icon[data-action="togglePassword"]')
+ .forEach(togglePassword => {
+ togglePassword.addEventListener("click", () => {
+ const isEye = togglePassword.firstElementChild.getAttribute("xlink:href") === "#iconEye";
+ togglePassword.firstElementChild.setAttribute("xlink:href", isEye ? "#iconEyeoff" : "#iconEye");
+ togglePassword.previousElementSibling.setAttribute("type", isEye ? "text" : "password");
+ });
+ });
+ },
+ _renderPublishAddressList: (
+ element: Element,
+ port: number,
+ ) => {
+ const publishAddresses = element.querySelector("#publishAddresses");
+ if (port === 0) {
+ publishAddresses.innerText = window.siyuan.languages.publishServiceNotStarted;
+ } else {
+ publishAddresses.innerHTML = `${
+ window.siyuan.config.localIPs
+ .filter(ip => !(ip.startsWith("[") && ip.endsWith("]")))
+ .map(ip => `${ip}:${port}
`)
+ .join("")
+ }${
+ window.siyuan.config.localIPs
+ .filter(ip => (ip.startsWith("[") && ip.endsWith("]")))
+ .map(ip => `${ip}:${port}
`)
+ .join("")
+ }
`;
+ }
+ },
+}
diff --git a/app/src/layout/util.ts b/app/src/layout/util.ts
index 80f2d30ca3b..96550b2d45a 100644
--- a/app/src/layout/util.ts
+++ b/app/src/layout/util.ts
@@ -169,13 +169,17 @@ const dockToJSON = (dock: Dock) => {
};
export const resetLayout = () => {
- fetchPost("/api/system/setUILayout", {layout: {}}, () => {
- window.siyuan.storage[Constants.LOCAL_FILEPOSITION] = {};
- setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]);
- window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION] = {};
- setStorageVal(Constants.LOCAL_DIALOGPOSITION, window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION]);
+ if (window.siyuan.config.readonly) {
window.location.reload();
- });
+ } else {
+ fetchPost("/api/system/setUILayout", {layout: {}}, () => {
+ window.siyuan.storage[Constants.LOCAL_FILEPOSITION] = {};
+ setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]);
+ window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION] = {};
+ setStorageVal(Constants.LOCAL_DIALOGPOSITION, window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION]);
+ window.location.reload();
+ });
+ }
};
let saveCount = 0;
@@ -211,10 +215,12 @@ export const saveLayout = () => {
if (isWindow()) {
sessionStorage.setItem("layout", JSON.stringify(layoutJSON));
} else {
- fetchPost("/api/system/setUILayout", {
- layout: layoutJSON,
- errorExit: false // 后台不接受该参数,用于请求发生错误时退出程序
- });
+ if (!window.siyuan.config.readonly) {
+ fetchPost("/api/system/setUILayout", {
+ layout: layoutJSON,
+ errorExit: false // 后台不接受该参数,用于请求发生错误时退出程序
+ });
+ }
}
}
};
@@ -250,12 +256,17 @@ export const exportLayout = (options: {
getAllModels().editor.forEach(item => {
saveScroll(item.editor.protyle);
});
- fetchPost("/api/system/setUILayout", {
- layout: layoutJSON,
- errorExit: options.errorExit // 后台不接受该参数,用于请求发生错误时退出程序
- }, () => {
+
+ if (window.siyuan.config.readonly) {
options.cb();
- });
+ } else {
+ fetchPost("/api/system/setUILayout", {
+ layout: layoutJSON,
+ errorExit: options.errorExit // 后台不接受该参数,用于请求发生错误时退出程序
+ }, () => {
+ options.cb();
+ });
+ }
};
export const getAllLayout = () => {
diff --git a/app/src/menus/commonMenuItem.ts b/app/src/menus/commonMenuItem.ts
index 0c45f02721f..0c1a4ef1eb7 100644
--- a/app/src/menus/commonMenuItem.ts
+++ b/app/src/menus/commonMenuItem.ts
@@ -507,8 +507,9 @@ export const exportMd = (id: string) => {
});
});
return;
+ } else if (response.code === 0) {
+ showMessage(window.siyuan.languages.exportTplSucc);
}
- showMessage(window.siyuan.languages.exportTplSucc);
});
dialog.destroy();
});
diff --git a/app/src/menus/workspace.ts b/app/src/menus/workspace.ts
index 04ed7fcd5fb..85796b2684d 100644
--- a/app/src/menus/workspace.ts
+++ b/app/src/menus/workspace.ts
@@ -340,9 +340,13 @@ export const workspaceMenu = (app: App, rect: DOMRect) => {
window.siyuan.menus.menu.remove();
return;
}
- fetchPost("/api/system/setUILayout", {layout: item.layout}, () => {
+ if (window.siyuan.config.readonly) {
window.location.reload();
- });
+ } else {
+ fetchPost("/api/system/setUILayout", {layout: item.layout}, () => {
+ window.location.reload();
+ });
+ }
});
}
});
diff --git a/app/src/protyle/util/compatibility.ts b/app/src/protyle/util/compatibility.ts
index 7f07553d177..4f0a1837f1c 100644
--- a/app/src/protyle/util/compatibility.ts
+++ b/app/src/protyle/util/compatibility.ts
@@ -287,6 +287,10 @@ export const getLocalStorage = (cb: () => void) => {
};
export const setStorageVal = (key: string, val: any) => {
+ if (window.siyuan.config.readonly) {
+ return;
+ }
+
fetchPost("/api/storage/setLocalStorageVal", {
app: Constants.SIYUAN_APPID,
key,
diff --git a/app/src/types/config.d.ts b/app/src/types/config.d.ts
index 5d985ea860a..5076311f0c8 100644
--- a/app/src/types/config.d.ts
+++ b/app/src/types/config.d.ts
@@ -63,6 +63,11 @@ declare namespace Config {
* Whether to open the user guide after startup
*/
openHelp: boolean;
+ /**
+ * Publishing service
+ * 发布服务
+ */
+ publish: IPublish;
/**
* Whether it is running in read-only mode
* 全局只读
@@ -1030,6 +1035,56 @@ declare namespace Config {
*/
export type TLogLevel = "off" | "trace" | "debug" | "info" | "warn" | "error" | "fatal";
+ /**
+ * Publishing service
+ */
+ export interface IPublish {
+ /**
+ * Whether to open the publishing service
+ */
+ enable: boolean;
+ /**
+ * The basic authentication settings of publishing service
+ */
+ auth: IPublishAuth;
+ /**
+ * Port on which the publishing service listens
+ */
+ port: number;
+ }
+
+ /**
+ * Publishing service authentication settings
+ */
+ export interface IPublishAuth {
+ /**
+ * Whether to enable basic authentication for publishing services
+ */
+ enable: boolean;
+ /**
+ * List of basic verified accounts
+ */
+ accounts: IPublishAuthAccount[];
+ }
+
+ /**
+ * Basic authentication account
+ */
+ export interface IPublishAuthAccount {
+ /**
+ * Account username
+ */
+ username: string;
+ /**
+ * Account password
+ */
+ password: string;
+ /**
+ * The memo text of the account
+ */
+ memo: string;
+ }
+
/**
* Snapshot repository related configuration
*/
diff --git a/app/src/util/assets.ts b/app/src/util/assets.ts
index d2ffad69437..314871829de 100644
--- a/app/src/util/assets.ts
+++ b/app/src/util/assets.ts
@@ -150,8 +150,10 @@ export const initAssets = () => {
return;
}
}
- window.siyuan.config.appearance = response.data.appearance;
- loadAssets(response.data.appearance);
+ if (response.code === 0) {
+ window.siyuan.config.appearance = response.data.appearance;
+ loadAssets(response.data.appearance);
+ }
});
});
};
diff --git a/app/src/util/fetch.ts b/app/src/util/fetch.ts
index 3e258a94fef..36420d9afa7 100644
--- a/app/src/util/fetch.ts
+++ b/app/src/util/fetch.ts
@@ -32,18 +32,20 @@ export const fetchPost = (url: string, data?: any, cb?: (response: IWebSocketDat
init.headers = headers;
}
fetch(url, init).then((response) => {
- if (response.status === 404) {
- return {
- data: null,
- msg: response.statusText,
- code: response.status,
- };
- } else {
- if (response.headers.get("content-type")?.indexOf("application/json") > -1) {
- return response.json();
- } else {
- return response.text();
- }
+ switch (response.status) {
+ case 403:
+ case 404:
+ return {
+ data: null,
+ msg: response.statusText,
+ code: response.status,
+ };
+ default:
+ if (response.headers.get("content-type")?.indexOf("application/json") > -1) {
+ return response.json();
+ } else {
+ return response.text();
+ }
}
}).then((response: IWebSocketData) => {
if (typeof response === "string") {
diff --git a/kernel/api/file.go b/kernel/api/file.go
index e9453e89980..1a313ea240d 100644
--- a/kernel/api/file.go
+++ b/kernel/api/file.go
@@ -166,13 +166,31 @@ func getFile(c *gin.Context) {
return
}
if info.IsDir() {
- logging.LogErrorf("file [%s] is a directory", fileAbsPath)
+ logging.LogErrorf("path [%s] is a directory path", fileAbsPath)
ret.Code = http.StatusMethodNotAllowed
- ret.Msg = "file is a directory"
+ ret.Msg = "This is a directory path"
c.JSON(http.StatusAccepted, ret)
return
}
+ // REF: https://github.com/siyuan-note/siyuan/issues/11364
+ if role := model.GetGinContextRole(c); !model.IsValidRole(role, []model.Role{
+ model.RoleAdministrator,
+ }) {
+ if relPath, err := filepath.Rel(util.ConfDir, fileAbsPath); err != nil {
+ logging.LogErrorf("Get a relative path from [%s] to [%s] failed: %s", util.ConfDir, fileAbsPath, err)
+ ret.Code = http.StatusInternalServerError
+ ret.Msg = err.Error()
+ c.JSON(http.StatusAccepted, ret)
+ return
+ } else if relPath == "conf.json" {
+ ret.Code = http.StatusForbidden
+ ret.Msg = http.StatusText(http.StatusForbidden)
+ c.JSON(http.StatusAccepted, ret)
+ return
+ }
+ }
+
data, err := filelock.ReadFile(fileAbsPath)
if nil != err {
logging.LogErrorf("read file [%s] failed: %s", fileAbsPath, err)
diff --git a/kernel/api/router.go b/kernel/api/router.go
index 50ca14bb733..b81d01fc880 100644
--- a/kernel/api/router.go
+++ b/kernel/api/router.go
@@ -33,122 +33,121 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/system/loginAuth", model.LoginAuth)
ginServer.Handle("POST", "/api/system/logoutAuth", model.LogoutAuth)
ginServer.Handle("GET", "/api/system/getCaptcha", model.GetCaptcha)
- ginServer.Handle("POST", "/api/system/setUILayout", setUILayout) // 这里不加鉴权 After modifying the access authentication code on the browser side, the other side does not refresh https://github.com/siyuan-note/siyuan/issues/8028
- ginServer.Handle("GET", "/snippets/*filepath", serveSnippets)
// 需要鉴权
ginServer.Handle("POST", "/api/system/getEmojiConf", model.CheckAuth, getEmojiConf)
- ginServer.Handle("POST", "/api/system/setAPIToken", model.CheckAuth, model.CheckReadonly, setAPIToken)
- ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, model.CheckReadonly, setAccessAuthCode)
- ginServer.Handle("POST", "/api/system/setFollowSystemLockScreen", model.CheckAuth, model.CheckReadonly, setFollowSystemLockScreen)
- ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, model.CheckReadonly, setNetworkServe)
- ginServer.Handle("POST", "/api/system/setUploadErrLog", model.CheckAuth, model.CheckReadonly, setUploadErrLog)
- ginServer.Handle("POST", "/api/system/setAutoLaunch", model.CheckAuth, model.CheckReadonly, setAutoLaunch)
- ginServer.Handle("POST", "/api/system/setGoogleAnalytics", model.CheckAuth, model.CheckReadonly, setGoogleAnalytics)
- ginServer.Handle("POST", "/api/system/setDownloadInstallPkg", model.CheckAuth, model.CheckReadonly, setDownloadInstallPkg)
- ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, model.CheckReadonly, setNetworkProxy)
- ginServer.Handle("POST", "/api/system/setWorkspaceDir", model.CheckAuth, model.CheckReadonly, setWorkspaceDir)
- ginServer.Handle("POST", "/api/system/getWorkspaces", model.CheckAuth, getWorkspaces)
- ginServer.Handle("POST", "/api/system/getMobileWorkspaces", model.CheckAuth, getMobileWorkspaces)
- ginServer.Handle("POST", "/api/system/checkWorkspaceDir", model.CheckAuth, model.CheckReadonly, checkWorkspaceDir)
- ginServer.Handle("POST", "/api/system/createWorkspaceDir", model.CheckAuth, model.CheckReadonly, createWorkspaceDir)
- ginServer.Handle("POST", "/api/system/removeWorkspaceDir", model.CheckAuth, model.CheckReadonly, removeWorkspaceDir)
- ginServer.Handle("POST", "/api/system/removeWorkspaceDirPhysically", model.CheckAuth, model.CheckReadonly, removeWorkspaceDirPhysically)
- ginServer.Handle("POST", "/api/system/setAppearanceMode", model.CheckAuth, setAppearanceMode)
- ginServer.Handle("POST", "/api/system/getSysFonts", model.CheckAuth, getSysFonts)
- ginServer.Handle("POST", "/api/system/exit", model.CheckAuth, exit)
+ ginServer.Handle("POST", "/api/system/setAPIToken", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAPIToken)
+ ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAccessAuthCode)
+ ginServer.Handle("POST", "/api/system/setFollowSystemLockScreen", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFollowSystemLockScreen)
+ ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkServe)
+ ginServer.Handle("POST", "/api/system/setUploadErrLog", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setUploadErrLog)
+ ginServer.Handle("POST", "/api/system/setAutoLaunch", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAutoLaunch)
+ ginServer.Handle("POST", "/api/system/setGoogleAnalytics", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setGoogleAnalytics)
+ ginServer.Handle("POST", "/api/system/setDownloadInstallPkg", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setDownloadInstallPkg)
+ ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkProxy)
+ ginServer.Handle("POST", "/api/system/setWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setWorkspaceDir)
+ ginServer.Handle("POST", "/api/system/getWorkspaces", model.CheckAuth, model.CheckAdminRole, getWorkspaces)
+ ginServer.Handle("POST", "/api/system/getMobileWorkspaces", model.CheckAuth, model.CheckAdminRole, getMobileWorkspaces)
+ ginServer.Handle("POST", "/api/system/checkWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkWorkspaceDir)
+ ginServer.Handle("POST", "/api/system/createWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createWorkspaceDir)
+ ginServer.Handle("POST", "/api/system/removeWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeWorkspaceDir)
+ ginServer.Handle("POST", "/api/system/removeWorkspaceDirPhysically", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeWorkspaceDirPhysically)
+ ginServer.Handle("POST", "/api/system/setAppearanceMode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAppearanceMode)
+ ginServer.Handle("POST", "/api/system/setUILayout", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setUILayout)
+ ginServer.Handle("POST", "/api/system/getSysFonts", model.CheckAuth, model.CheckAdminRole, getSysFonts)
+ ginServer.Handle("POST", "/api/system/exit", model.CheckAuth, model.CheckAdminRole, exit)
ginServer.Handle("POST", "/api/system/getConf", model.CheckAuth, getConf)
- ginServer.Handle("POST", "/api/system/checkUpdate", model.CheckAuth, checkUpdate)
- ginServer.Handle("POST", "/api/system/exportLog", model.CheckAuth, exportLog)
+ ginServer.Handle("POST", "/api/system/checkUpdate", model.CheckAuth, model.CheckAdminRole, checkUpdate)
+ ginServer.Handle("POST", "/api/system/exportLog", model.CheckAuth, model.CheckAdminRole, exportLog)
ginServer.Handle("POST", "/api/system/getChangelog", model.CheckAuth, getChangelog)
- ginServer.Handle("POST", "/api/system/getNetwork", model.CheckAuth, getNetwork)
+ ginServer.Handle("POST", "/api/system/getNetwork", model.CheckAuth, model.CheckAdminRole, getNetwork)
- ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckReadonly, setLocalStorage)
+ ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setLocalStorage)
ginServer.Handle("POST", "/api/storage/getLocalStorage", model.CheckAuth, getLocalStorage)
- ginServer.Handle("POST", "/api/storage/setLocalStorageVal", model.CheckAuth, model.CheckReadonly, setLocalStorageVal)
- ginServer.Handle("POST", "/api/storage/removeLocalStorageVals", model.CheckAuth, model.CheckReadonly, removeLocalStorageVals)
- ginServer.Handle("POST", "/api/storage/setCriterion", model.CheckAuth, model.CheckReadonly, setCriterion)
+ ginServer.Handle("POST", "/api/storage/setLocalStorageVal", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setLocalStorageVal)
+ ginServer.Handle("POST", "/api/storage/removeLocalStorageVals", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeLocalStorageVals)
+ ginServer.Handle("POST", "/api/storage/setCriterion", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setCriterion)
ginServer.Handle("POST", "/api/storage/getCriteria", model.CheckAuth, getCriteria)
- ginServer.Handle("POST", "/api/storage/removeCriterion", model.CheckAuth, model.CheckReadonly, removeCriterion)
+ ginServer.Handle("POST", "/api/storage/removeCriterion", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCriterion)
ginServer.Handle("POST", "/api/storage/getRecentDocs", model.CheckAuth, getRecentDocs)
- ginServer.Handle("POST", "/api/account/login", model.CheckAuth, model.CheckReadonly, login)
- ginServer.Handle("POST", "/api/account/checkActivationcode", model.CheckAuth, model.CheckReadonly, checkActivationcode)
- ginServer.Handle("POST", "/api/account/useActivationcode", model.CheckAuth, model.CheckReadonly, useActivationcode)
- ginServer.Handle("POST", "/api/account/deactivate", model.CheckAuth, model.CheckReadonly, deactivateUser)
- ginServer.Handle("POST", "/api/account/startFreeTrial", model.CheckAuth, model.CheckReadonly, startFreeTrial)
+ ginServer.Handle("POST", "/api/account/login", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, login)
+ ginServer.Handle("POST", "/api/account/checkActivationcode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkActivationcode)
+ ginServer.Handle("POST", "/api/account/useActivationcode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, useActivationcode)
+ ginServer.Handle("POST", "/api/account/deactivate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, deactivateUser)
+ ginServer.Handle("POST", "/api/account/startFreeTrial", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, startFreeTrial)
ginServer.Handle("POST", "/api/notebook/lsNotebooks", model.CheckAuth, lsNotebooks)
- ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, model.CheckReadonly, openNotebook)
- ginServer.Handle("POST", "/api/notebook/closeNotebook", model.CheckAuth, model.CheckReadonly, closeNotebook)
+ ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, openNotebook)
+ ginServer.Handle("POST", "/api/notebook/closeNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, closeNotebook)
ginServer.Handle("POST", "/api/notebook/getNotebookConf", model.CheckAuth, getNotebookConf)
- ginServer.Handle("POST", "/api/notebook/setNotebookConf", model.CheckAuth, model.CheckReadonly, setNotebookConf)
- ginServer.Handle("POST", "/api/notebook/createNotebook", model.CheckAuth, model.CheckReadonly, createNotebook)
- ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, model.CheckReadonly, removeNotebook)
- ginServer.Handle("POST", "/api/notebook/renameNotebook", model.CheckAuth, model.CheckReadonly, renameNotebook)
- ginServer.Handle("POST", "/api/notebook/changeSortNotebook", model.CheckAuth, model.CheckReadonly, changeSortNotebook)
- ginServer.Handle("POST", "/api/notebook/setNotebookIcon", model.CheckAuth, model.CheckReadonly, setNotebookIcon)
+ ginServer.Handle("POST", "/api/notebook/setNotebookConf", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNotebookConf)
+ ginServer.Handle("POST", "/api/notebook/createNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createNotebook)
+ ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeNotebook)
+ 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/filetree/searchDocs", model.CheckAuth, searchDocs)
ginServer.Handle("POST", "/api/filetree/listDocsByPath", model.CheckAuth, listDocsByPath)
ginServer.Handle("POST", "/api/filetree/getDoc", model.CheckAuth, getDoc)
ginServer.Handle("POST", "/api/filetree/getDocCreateSavePath", model.CheckAuth, getDocCreateSavePath)
ginServer.Handle("POST", "/api/filetree/getRefCreateSavePath", model.CheckAuth, getRefCreateSavePath)
- ginServer.Handle("POST", "/api/filetree/changeSort", model.CheckAuth, model.CheckReadonly, changeSort)
- ginServer.Handle("POST", "/api/filetree/createDocWithMd", model.CheckAuth, model.CheckReadonly, createDocWithMd)
- ginServer.Handle("POST", "/api/filetree/createDailyNote", model.CheckAuth, model.CheckReadonly, createDailyNote)
- ginServer.Handle("POST", "/api/filetree/createDoc", model.CheckAuth, model.CheckReadonly, createDoc)
- ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckReadonly, renameDoc)
- ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckReadonly, removeDoc)
- ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckReadonly, removeDocs)
- ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckReadonly, moveDocs)
- ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckReadonly, duplicateDoc)
+ ginServer.Handle("POST", "/api/filetree/changeSort", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, changeSort)
+ ginServer.Handle("POST", "/api/filetree/createDocWithMd", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createDocWithMd)
+ ginServer.Handle("POST", "/api/filetree/createDailyNote", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createDailyNote)
+ ginServer.Handle("POST", "/api/filetree/createDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createDoc)
+ ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameDoc)
+ ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDoc)
+ ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocs)
+ ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocs)
+ ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateDoc)
ginServer.Handle("POST", "/api/filetree/getHPathByPath", model.CheckAuth, getHPathByPath)
ginServer.Handle("POST", "/api/filetree/getHPathsByPaths", model.CheckAuth, getHPathsByPaths)
ginServer.Handle("POST", "/api/filetree/getHPathByID", model.CheckAuth, getHPathByID)
ginServer.Handle("POST", "/api/filetree/getFullHPathByID", model.CheckAuth, getFullHPathByID)
ginServer.Handle("POST", "/api/filetree/getIDsByHPath", model.CheckAuth, getIDsByHPath)
- ginServer.Handle("POST", "/api/filetree/doc2Heading", model.CheckAuth, model.CheckReadonly, doc2Heading)
- ginServer.Handle("POST", "/api/filetree/heading2Doc", model.CheckAuth, model.CheckReadonly, heading2Doc)
- ginServer.Handle("POST", "/api/filetree/li2Doc", model.CheckAuth, model.CheckReadonly, li2Doc)
- ginServer.Handle("POST", "/api/filetree/refreshFiletree", model.CheckAuth, model.CheckReadonly, refreshFiletree)
- ginServer.Handle("POST", "/api/filetree/upsertIndexes", model.CheckAuth, model.CheckReadonly, upsertIndexes)
- ginServer.Handle("POST", "/api/filetree/removeIndexes", model.CheckAuth, model.CheckReadonly, removeIndexes)
- ginServer.Handle("POST", "/api/filetree/listDocTree", model.CheckAuth, model.CheckReadonly, listDocTree)
-
- ginServer.Handle("POST", "/api/format/autoSpace", model.CheckAuth, model.CheckReadonly, autoSpace)
- ginServer.Handle("POST", "/api/format/netImg2LocalAssets", model.CheckAuth, model.CheckReadonly, netImg2LocalAssets)
- ginServer.Handle("POST", "/api/format/netAssets2LocalAssets", model.CheckAuth, model.CheckReadonly, netAssets2LocalAssets)
-
- ginServer.Handle("POST", "/api/history/getNotebookHistory", model.CheckAuth, getNotebookHistory)
- ginServer.Handle("POST", "/api/history/rollbackNotebookHistory", model.CheckAuth, model.CheckReadonly, rollbackNotebookHistory)
- ginServer.Handle("POST", "/api/history/rollbackAssetsHistory", model.CheckAuth, model.CheckReadonly, rollbackAssetsHistory)
- ginServer.Handle("POST", "/api/history/getDocHistoryContent", model.CheckAuth, getDocHistoryContent)
- ginServer.Handle("POST", "/api/history/rollbackDocHistory", model.CheckAuth, model.CheckReadonly, rollbackDocHistory)
- ginServer.Handle("POST", "/api/history/clearWorkspaceHistory", model.CheckAuth, model.CheckReadonly, clearWorkspaceHistory)
- ginServer.Handle("POST", "/api/history/reindexHistory", model.CheckAuth, model.CheckReadonly, reindexHistory)
- ginServer.Handle("POST", "/api/history/searchHistory", model.CheckAuth, searchHistory)
- ginServer.Handle("POST", "/api/history/getHistoryItems", model.CheckAuth, getHistoryItems)
+ ginServer.Handle("POST", "/api/filetree/doc2Heading", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, doc2Heading)
+ ginServer.Handle("POST", "/api/filetree/heading2Doc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, heading2Doc)
+ ginServer.Handle("POST", "/api/filetree/li2Doc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, li2Doc)
+ ginServer.Handle("POST", "/api/filetree/refreshFiletree", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, refreshFiletree)
+ ginServer.Handle("POST", "/api/filetree/upsertIndexes", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, upsertIndexes)
+ ginServer.Handle("POST", "/api/filetree/removeIndexes", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeIndexes)
+ ginServer.Handle("POST", "/api/filetree/listDocTree", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, listDocTree)
+
+ ginServer.Handle("POST", "/api/format/autoSpace", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, autoSpace)
+ ginServer.Handle("POST", "/api/format/netImg2LocalAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, netImg2LocalAssets)
+ ginServer.Handle("POST", "/api/format/netAssets2LocalAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, netAssets2LocalAssets)
+
+ ginServer.Handle("POST", "/api/history/getNotebookHistory", model.CheckAuth, model.CheckAdminRole, getNotebookHistory)
+ ginServer.Handle("POST", "/api/history/rollbackNotebookHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, rollbackNotebookHistory)
+ ginServer.Handle("POST", "/api/history/rollbackAssetsHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, rollbackAssetsHistory)
+ ginServer.Handle("POST", "/api/history/getDocHistoryContent", model.CheckAuth, model.CheckAdminRole, getDocHistoryContent)
+ ginServer.Handle("POST", "/api/history/rollbackDocHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, rollbackDocHistory)
+ ginServer.Handle("POST", "/api/history/clearWorkspaceHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, clearWorkspaceHistory)
+ ginServer.Handle("POST", "/api/history/reindexHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, reindexHistory)
+ ginServer.Handle("POST", "/api/history/searchHistory", model.CheckAuth, model.CheckAdminRole, searchHistory)
+ ginServer.Handle("POST", "/api/history/getHistoryItems", model.CheckAuth, model.CheckAdminRole, getHistoryItems)
ginServer.Handle("POST", "/api/outline/getDocOutline", model.CheckAuth, getDocOutline)
ginServer.Handle("POST", "/api/bookmark/getBookmark", model.CheckAuth, getBookmark)
- ginServer.Handle("POST", "/api/bookmark/renameBookmark", model.CheckAuth, model.CheckReadonly, renameBookmark)
- ginServer.Handle("POST", "/api/bookmark/removeBookmark", model.CheckAuth, model.CheckReadonly, removeBookmark)
+ ginServer.Handle("POST", "/api/bookmark/renameBookmark", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameBookmark)
+ ginServer.Handle("POST", "/api/bookmark/removeBookmark", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeBookmark)
ginServer.Handle("POST", "/api/tag/getTag", model.CheckAuth, getTag)
- ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, model.CheckReadonly, renameTag)
- ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, model.CheckReadonly, removeTag)
+ ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameTag)
+ ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeTag)
ginServer.Handle("POST", "/api/lute/spinBlockDOM", model.CheckAuth, spinBlockDOM) // 未测试
ginServer.Handle("POST", "/api/lute/html2BlockDOM", model.CheckAuth, html2BlockDOM)
ginServer.Handle("POST", "/api/lute/copyStdMarkdown", model.CheckAuth, copyStdMarkdown)
ginServer.Handle("POST", "/api/query/sql", model.CheckAuth, SQL)
- ginServer.Handle("POST", "/api/sqlite/flushTransaction", model.CheckAuth, model.CheckReadonly, flushTransaction)
+ ginServer.Handle("POST", "/api/sqlite/flushTransaction", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, flushTransaction)
ginServer.Handle("POST", "/api/search/searchTag", model.CheckAuth, searchTag)
ginServer.Handle("POST", "/api/search/searchTemplate", model.CheckAuth, searchTemplate)
- ginServer.Handle("POST", "/api/search/removeTemplate", model.CheckAuth, model.CheckReadonly, removeTemplate)
+ ginServer.Handle("POST", "/api/search/removeTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeTemplate)
ginServer.Handle("POST", "/api/search/searchWidget", model.CheckAuth, searchWidget)
ginServer.Handle("POST", "/api/search/searchRefBlock", model.CheckAuth, searchRefBlock)
ginServer.Handle("POST", "/api/search/searchEmbedBlock", model.CheckAuth, searchEmbedBlock)
@@ -181,33 +180,33 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/block/getDocInfo", model.CheckAuth, getDocInfo)
ginServer.Handle("POST", "/api/block/checkBlockExist", model.CheckAuth, checkBlockExist)
ginServer.Handle("POST", "/api/block/checkBlockFold", model.CheckAuth, checkBlockFold)
- ginServer.Handle("POST", "/api/block/insertBlock", model.CheckAuth, model.CheckReadonly, insertBlock)
- ginServer.Handle("POST", "/api/block/prependBlock", model.CheckAuth, model.CheckReadonly, prependBlock)
- ginServer.Handle("POST", "/api/block/appendBlock", model.CheckAuth, model.CheckReadonly, appendBlock)
- ginServer.Handle("POST", "/api/block/appendDailyNoteBlock", model.CheckAuth, model.CheckReadonly, appendDailyNoteBlock)
- ginServer.Handle("POST", "/api/block/prependDailyNoteBlock", model.CheckAuth, model.CheckReadonly, prependDailyNoteBlock)
- ginServer.Handle("POST", "/api/block/updateBlock", model.CheckAuth, model.CheckReadonly, updateBlock)
- ginServer.Handle("POST", "/api/block/deleteBlock", model.CheckAuth, model.CheckReadonly, deleteBlock)
- ginServer.Handle("POST", "/api/block/moveBlock", model.CheckAuth, model.CheckReadonly, moveBlock)
- ginServer.Handle("POST", "/api/block/moveOutlineHeading", model.CheckAuth, model.CheckReadonly, moveOutlineHeading)
- ginServer.Handle("POST", "/api/block/foldBlock", model.CheckAuth, model.CheckReadonly, foldBlock)
- ginServer.Handle("POST", "/api/block/unfoldBlock", model.CheckAuth, model.CheckReadonly, unfoldBlock)
- ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckReadonly, setBlockReminder)
+ ginServer.Handle("POST", "/api/block/insertBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, insertBlock)
+ ginServer.Handle("POST", "/api/block/prependBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, prependBlock)
+ ginServer.Handle("POST", "/api/block/appendBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, appendBlock)
+ ginServer.Handle("POST", "/api/block/appendDailyNoteBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, appendDailyNoteBlock)
+ ginServer.Handle("POST", "/api/block/prependDailyNoteBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, prependDailyNoteBlock)
+ ginServer.Handle("POST", "/api/block/updateBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, updateBlock)
+ ginServer.Handle("POST", "/api/block/deleteBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, deleteBlock)
+ ginServer.Handle("POST", "/api/block/moveBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveBlock)
+ ginServer.Handle("POST", "/api/block/moveOutlineHeading", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveOutlineHeading)
+ ginServer.Handle("POST", "/api/block/foldBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, foldBlock)
+ ginServer.Handle("POST", "/api/block/unfoldBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, unfoldBlock)
+ ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBlockReminder)
ginServer.Handle("POST", "/api/block/getHeadingLevelTransaction", model.CheckAuth, getHeadingLevelTransaction)
ginServer.Handle("POST", "/api/block/getHeadingDeleteTransaction", model.CheckAuth, getHeadingDeleteTransaction)
ginServer.Handle("POST", "/api/block/getHeadingChildrenIDs", model.CheckAuth, getHeadingChildrenIDs)
ginServer.Handle("POST", "/api/block/getHeadingChildrenDOM", model.CheckAuth, getHeadingChildrenDOM)
- ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckReadonly, swapBlockRef)
- ginServer.Handle("POST", "/api/block/transferBlockRef", model.CheckAuth, model.CheckReadonly, transferBlockRef)
+ ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, swapBlockRef)
+ ginServer.Handle("POST", "/api/block/transferBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, transferBlockRef)
ginServer.Handle("POST", "/api/block/getBlockSiblingID", model.CheckAuth, getBlockSiblingID)
ginServer.Handle("POST", "/api/block/getBlockTreeInfos", model.CheckAuth, getBlockTreeInfos)
ginServer.Handle("POST", "/api/file/getFile", model.CheckAuth, getFile)
- ginServer.Handle("POST", "/api/file/putFile", model.CheckAuth, model.CheckReadonly, putFile)
- ginServer.Handle("POST", "/api/file/copyFile", model.CheckAuth, model.CheckReadonly, copyFile)
- ginServer.Handle("POST", "/api/file/globalCopyFiles", model.CheckAuth, model.CheckReadonly, globalCopyFiles)
- ginServer.Handle("POST", "/api/file/removeFile", model.CheckAuth, model.CheckReadonly, removeFile)
- ginServer.Handle("POST", "/api/file/renameFile", model.CheckAuth, model.CheckReadonly, renameFile)
+ ginServer.Handle("POST", "/api/file/putFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, putFile)
+ ginServer.Handle("POST", "/api/file/copyFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, copyFile)
+ ginServer.Handle("POST", "/api/file/globalCopyFiles", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, globalCopyFiles)
+ ginServer.Handle("POST", "/api/file/removeFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeFile)
+ ginServer.Handle("POST", "/api/file/renameFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameFile)
ginServer.Handle("POST", "/api/file/readDir", model.CheckAuth, readDir)
ginServer.Handle("POST", "/api/file/getUniqueFilename", model.CheckAuth, getUniqueFilename)
@@ -218,57 +217,57 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/ref/getBackmentionDoc", model.CheckAuth, getBackmentionDoc)
ginServer.Handle("POST", "/api/attr/getBookmarkLabels", model.CheckAuth, getBookmarkLabels)
- ginServer.Handle("POST", "/api/attr/resetBlockAttrs", model.CheckAuth, model.CheckReadonly, resetBlockAttrs)
- ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, model.CheckReadonly, setBlockAttrs)
- ginServer.Handle("POST", "/api/attr/batchSetBlockAttrs", model.CheckAuth, model.CheckReadonly, batchSetBlockAttrs)
+ ginServer.Handle("POST", "/api/attr/resetBlockAttrs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetBlockAttrs)
+ ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBlockAttrs)
+ ginServer.Handle("POST", "/api/attr/batchSetBlockAttrs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, batchSetBlockAttrs)
ginServer.Handle("POST", "/api/attr/getBlockAttrs", model.CheckAuth, getBlockAttrs)
- ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, getCloudSpace)
-
- ginServer.Handle("POST", "/api/sync/setSyncEnable", model.CheckAuth, model.CheckReadonly, setSyncEnable)
- ginServer.Handle("POST", "/api/sync/setSyncPerception", model.CheckAuth, model.CheckReadonly, setSyncPerception)
- ginServer.Handle("POST", "/api/sync/setSyncGenerateConflictDoc", model.CheckAuth, model.CheckReadonly, setSyncGenerateConflictDoc)
- ginServer.Handle("POST", "/api/sync/setSyncMode", model.CheckAuth, model.CheckReadonly, setSyncMode)
- ginServer.Handle("POST", "/api/sync/setSyncProvider", model.CheckAuth, model.CheckReadonly, setSyncProvider)
- ginServer.Handle("POST", "/api/sync/setSyncProviderS3", model.CheckAuth, model.CheckReadonly, setSyncProviderS3)
- ginServer.Handle("POST", "/api/sync/setSyncProviderWebDAV", model.CheckAuth, model.CheckReadonly, setSyncProviderWebDAV)
- ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, model.CheckReadonly, setCloudSyncDir)
- ginServer.Handle("POST", "/api/sync/createCloudSyncDir", model.CheckAuth, model.CheckReadonly, createCloudSyncDir)
- ginServer.Handle("POST", "/api/sync/removeCloudSyncDir", model.CheckAuth, model.CheckReadonly, removeCloudSyncDir)
- ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, listCloudSyncDir)
- ginServer.Handle("POST", "/api/sync/performSync", model.CheckAuth, model.CheckReadonly, performSync)
- ginServer.Handle("POST", "/api/sync/performBootSync", model.CheckAuth, model.CheckReadonly, performBootSync)
- ginServer.Handle("POST", "/api/sync/getBootSync", model.CheckAuth, getBootSync)
- ginServer.Handle("POST", "/api/sync/getSyncInfo", model.CheckAuth, getSyncInfo)
- ginServer.Handle("POST", "/api/sync/exportSyncProviderS3", model.CheckAuth, exportSyncProviderS3)
- ginServer.Handle("POST", "/api/sync/importSyncProviderS3", model.CheckAuth, model.CheckReadonly, importSyncProviderS3)
- ginServer.Handle("POST", "/api/sync/exportSyncProviderWebDAV", model.CheckAuth, exportSyncProviderWebDAV)
- ginServer.Handle("POST", "/api/sync/importSyncProviderWebDAV", model.CheckAuth, model.CheckReadonly, importSyncProviderWebDAV)
-
- ginServer.Handle("POST", "/api/inbox/getShorthands", model.CheckAuth, getShorthands)
- ginServer.Handle("POST", "/api/inbox/getShorthand", model.CheckAuth, getShorthand)
- ginServer.Handle("POST", "/api/inbox/removeShorthands", model.CheckAuth, model.CheckReadonly, removeShorthands)
-
- ginServer.Handle("POST", "/api/extension/copy", model.CheckAuth, model.CheckReadonly, extensionCopy)
-
- ginServer.Handle("POST", "/api/clipboard/readFilePaths", model.CheckAuth, readFilePaths)
-
- ginServer.Handle("POST", "/api/asset/uploadCloud", model.CheckAuth, model.CheckReadonly, uploadCloud)
- ginServer.Handle("POST", "/api/asset/insertLocalAssets", model.CheckAuth, model.CheckReadonly, insertLocalAssets)
+ ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, model.CheckAdminRole, getCloudSpace)
+
+ ginServer.Handle("POST", "/api/sync/setSyncEnable", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncEnable)
+ ginServer.Handle("POST", "/api/sync/setSyncPerception", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncPerception)
+ ginServer.Handle("POST", "/api/sync/setSyncGenerateConflictDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncGenerateConflictDoc)
+ ginServer.Handle("POST", "/api/sync/setSyncMode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncMode)
+ ginServer.Handle("POST", "/api/sync/setSyncProvider", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncProvider)
+ ginServer.Handle("POST", "/api/sync/setSyncProviderS3", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncProviderS3)
+ ginServer.Handle("POST", "/api/sync/setSyncProviderWebDAV", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncProviderWebDAV)
+ ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setCloudSyncDir)
+ ginServer.Handle("POST", "/api/sync/createCloudSyncDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createCloudSyncDir)
+ ginServer.Handle("POST", "/api/sync/removeCloudSyncDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCloudSyncDir)
+ ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, model.CheckAdminRole, listCloudSyncDir)
+ ginServer.Handle("POST", "/api/sync/performSync", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, performSync)
+ ginServer.Handle("POST", "/api/sync/performBootSync", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, performBootSync)
+ ginServer.Handle("POST", "/api/sync/getBootSync", model.CheckAuth, model.CheckAdminRole, getBootSync)
+ ginServer.Handle("POST", "/api/sync/getSyncInfo", model.CheckAuth, model.CheckAdminRole, getSyncInfo)
+ ginServer.Handle("POST", "/api/sync/exportSyncProviderS3", model.CheckAuth, model.CheckAdminRole, exportSyncProviderS3)
+ ginServer.Handle("POST", "/api/sync/importSyncProviderS3", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importSyncProviderS3)
+ ginServer.Handle("POST", "/api/sync/exportSyncProviderWebDAV", model.CheckAuth, model.CheckAdminRole, exportSyncProviderWebDAV)
+ ginServer.Handle("POST", "/api/sync/importSyncProviderWebDAV", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importSyncProviderWebDAV)
+
+ ginServer.Handle("POST", "/api/inbox/getShorthands", model.CheckAuth, model.CheckAdminRole, getShorthands)
+ ginServer.Handle("POST", "/api/inbox/getShorthand", model.CheckAuth, model.CheckAdminRole, getShorthand)
+ ginServer.Handle("POST", "/api/inbox/removeShorthands", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeShorthands)
+
+ ginServer.Handle("POST", "/api/extension/copy", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, extensionCopy)
+
+ ginServer.Handle("POST", "/api/clipboard/readFilePaths", model.CheckAuth, model.CheckAdminRole, readFilePaths)
+
+ ginServer.Handle("POST", "/api/asset/uploadCloud", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uploadCloud)
+ ginServer.Handle("POST", "/api/asset/insertLocalAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, insertLocalAssets)
ginServer.Handle("POST", "/api/asset/resolveAssetPath", model.CheckAuth, resolveAssetPath)
- ginServer.Handle("POST", "/api/asset/upload", model.CheckAuth, model.CheckReadonly, model.Upload)
- ginServer.Handle("POST", "/api/asset/setFileAnnotation", model.CheckAuth, model.CheckReadonly, setFileAnnotation)
+ ginServer.Handle("POST", "/api/asset/upload", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, model.Upload)
+ ginServer.Handle("POST", "/api/asset/setFileAnnotation", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFileAnnotation)
ginServer.Handle("POST", "/api/asset/getFileAnnotation", model.CheckAuth, getFileAnnotation)
ginServer.Handle("POST", "/api/asset/getUnusedAssets", model.CheckAuth, getUnusedAssets)
ginServer.Handle("POST", "/api/asset/getMissingAssets", model.CheckAuth, getMissingAssets)
- ginServer.Handle("POST", "/api/asset/removeUnusedAsset", model.CheckAuth, model.CheckReadonly, removeUnusedAsset)
- ginServer.Handle("POST", "/api/asset/removeUnusedAssets", model.CheckAuth, model.CheckReadonly, removeUnusedAssets)
+ ginServer.Handle("POST", "/api/asset/removeUnusedAsset", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeUnusedAsset)
+ ginServer.Handle("POST", "/api/asset/removeUnusedAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeUnusedAssets)
ginServer.Handle("POST", "/api/asset/getDocImageAssets", model.CheckAuth, getDocImageAssets)
- ginServer.Handle("POST", "/api/asset/renameAsset", model.CheckAuth, model.CheckReadonly, renameAsset)
- ginServer.Handle("POST", "/api/asset/getImageOCRText", model.CheckAuth, model.CheckReadonly, getImageOCRText)
- ginServer.Handle("POST", "/api/asset/setImageOCRText", model.CheckAuth, model.CheckReadonly, setImageOCRText)
- ginServer.Handle("POST", "/api/asset/fullReindexAssetContent", model.CheckAuth, model.CheckReadonly, fullReindexAssetContent)
- ginServer.Handle("POST", "/api/asset/statAsset", model.CheckAuth, statAsset)
+ ginServer.Handle("POST", "/api/asset/renameAsset", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameAsset)
+ ginServer.Handle("POST", "/api/asset/getImageOCRText", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getImageOCRText)
+ ginServer.Handle("POST", "/api/asset/setImageOCRText", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setImageOCRText)
+ 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)
@@ -286,7 +285,7 @@ func ServeAPI(ginServer *gin.Engine) {
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/export2Liandi", model.CheckAuth, export2Liandi)
+ 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)
@@ -298,150 +297,152 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/export/exportEPUB", model.CheckAuth, exportEPUB)
ginServer.Handle("POST", "/api/export/exportAttributeView", model.CheckAuth, exportAttributeView)
- ginServer.Handle("POST", "/api/import/importStdMd", model.CheckAuth, model.CheckReadonly, importStdMd)
- ginServer.Handle("POST", "/api/import/importData", model.CheckAuth, model.CheckReadonly, importData)
- ginServer.Handle("POST", "/api/import/importSY", model.CheckAuth, model.CheckReadonly, importSY)
+ 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)
+ ginServer.Handle("POST", "/api/import/importSY", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importSY)
- ginServer.Handle("POST", "/api/convert/pandoc", model.CheckAuth, model.CheckReadonly, pandoc)
+ ginServer.Handle("POST", "/api/convert/pandoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, pandoc)
- ginServer.Handle("POST", "/api/template/render", model.CheckAuth, renderTemplate)
- ginServer.Handle("POST", "/api/template/docSaveAsTemplate", model.CheckAuth, model.CheckReadonly, docSaveAsTemplate)
+ ginServer.Handle("POST", "/api/template/render", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renderTemplate)
+ ginServer.Handle("POST", "/api/template/docSaveAsTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, docSaveAsTemplate)
ginServer.Handle("POST", "/api/template/renderSprig", model.CheckAuth, renderSprig)
- ginServer.Handle("POST", "/api/transactions", model.CheckAuth, model.CheckReadonly, performTransactions)
-
- ginServer.Handle("POST", "/api/setting/setAccount", model.CheckAuth, model.CheckReadonly, setAccount)
- ginServer.Handle("POST", "/api/setting/setEditor", model.CheckAuth, model.CheckReadonly, setEditor)
- ginServer.Handle("POST", "/api/setting/setExport", model.CheckAuth, model.CheckReadonly, setExport)
- ginServer.Handle("POST", "/api/setting/setFiletree", model.CheckAuth, model.CheckReadonly, setFiletree)
- ginServer.Handle("POST", "/api/setting/setSearch", model.CheckAuth, model.CheckReadonly, setSearch)
- ginServer.Handle("POST", "/api/setting/setKeymap", model.CheckAuth, model.CheckReadonly, setKeymap)
- ginServer.Handle("POST", "/api/setting/setAppearance", model.CheckAuth, model.CheckReadonly, setAppearance)
- ginServer.Handle("POST", "/api/setting/getCloudUser", model.CheckAuth, getCloudUser)
- ginServer.Handle("POST", "/api/setting/logoutCloudUser", model.CheckAuth, model.CheckReadonly, logoutCloudUser)
- ginServer.Handle("POST", "/api/setting/login2faCloudUser", model.CheckAuth, model.CheckReadonly, login2faCloudUser)
- ginServer.Handle("POST", "/api/setting/setEmoji", model.CheckAuth, model.CheckReadonly, setEmoji)
- ginServer.Handle("POST", "/api/setting/setFlashcard", model.CheckAuth, model.CheckReadonly, setFlashcard)
- ginServer.Handle("POST", "/api/setting/setAI", model.CheckAuth, model.CheckReadonly, setAI)
- ginServer.Handle("POST", "/api/setting/setBazaar", model.CheckAuth, model.CheckReadonly, setBazaar)
- ginServer.Handle("POST", "/api/setting/refreshVirtualBlockRef", model.CheckAuth, model.CheckReadonly, refreshVirtualBlockRef)
- ginServer.Handle("POST", "/api/setting/addVirtualBlockRefInclude", model.CheckAuth, model.CheckReadonly, addVirtualBlockRefInclude)
- ginServer.Handle("POST", "/api/setting/addVirtualBlockRefExclude", model.CheckAuth, model.CheckReadonly, addVirtualBlockRefExclude)
- ginServer.Handle("POST", "/api/setting/setSnippet", model.CheckAuth, model.CheckReadonly, setConfSnippet)
- ginServer.Handle("POST", "/api/setting/setEditorReadOnly", model.CheckAuth, model.CheckReadonly, setEditorReadOnly)
-
- ginServer.Handle("POST", "/api/graph/resetGraph", model.CheckAuth, model.CheckReadonly, resetGraph)
- ginServer.Handle("POST", "/api/graph/resetLocalGraph", model.CheckAuth, model.CheckReadonly, resetLocalGraph)
+ ginServer.Handle("POST", "/api/transactions", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, performTransactions)
+
+ ginServer.Handle("POST", "/api/setting/setAccount", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAccount)
+ ginServer.Handle("POST", "/api/setting/setEditor", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setEditor)
+ ginServer.Handle("POST", "/api/setting/setExport", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setExport)
+ ginServer.Handle("POST", "/api/setting/setFiletree", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFiletree)
+ ginServer.Handle("POST", "/api/setting/setSearch", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSearch)
+ ginServer.Handle("POST", "/api/setting/setKeymap", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setKeymap)
+ ginServer.Handle("POST", "/api/setting/setAppearance", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAppearance)
+ ginServer.Handle("POST", "/api/setting/getCloudUser", model.CheckAuth, model.CheckAdminRole, getCloudUser)
+ ginServer.Handle("POST", "/api/setting/logoutCloudUser", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, logoutCloudUser)
+ ginServer.Handle("POST", "/api/setting/login2faCloudUser", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, login2faCloudUser)
+ ginServer.Handle("POST", "/api/setting/setEmoji", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setEmoji)
+ ginServer.Handle("POST", "/api/setting/setFlashcard", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFlashcard)
+ ginServer.Handle("POST", "/api/setting/setAI", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAI)
+ ginServer.Handle("POST", "/api/setting/setBazaar", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBazaar)
+ ginServer.Handle("POST", "/api/setting/setPublish", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setPublish)
+ ginServer.Handle("POST", "/api/setting/getPublish", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getPublish)
+ ginServer.Handle("POST", "/api/setting/refreshVirtualBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, refreshVirtualBlockRef)
+ ginServer.Handle("POST", "/api/setting/addVirtualBlockRefInclude", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addVirtualBlockRefInclude)
+ ginServer.Handle("POST", "/api/setting/addVirtualBlockRefExclude", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addVirtualBlockRefExclude)
+ ginServer.Handle("POST", "/api/setting/setSnippet", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setConfSnippet)
+ ginServer.Handle("POST", "/api/setting/setEditorReadOnly", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setEditorReadOnly)
+
+ ginServer.Handle("POST", "/api/graph/resetGraph", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetGraph)
+ ginServer.Handle("POST", "/api/graph/resetLocalGraph", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetLocalGraph)
ginServer.Handle("POST", "/api/graph/getGraph", model.CheckAuth, getGraph)
ginServer.Handle("POST", "/api/graph/getLocalGraph", model.CheckAuth, getLocalGraph)
ginServer.Handle("POST", "/api/bazaar/getBazaarPlugin", model.CheckAuth, getBazaarPlugin)
ginServer.Handle("POST", "/api/bazaar/getInstalledPlugin", model.CheckAuth, getInstalledPlugin)
- ginServer.Handle("POST", "/api/bazaar/installBazaarPlugin", model.CheckAuth, model.CheckReadonly, installBazaarPlugin)
- ginServer.Handle("POST", "/api/bazaar/uninstallBazaarPlugin", model.CheckAuth, model.CheckReadonly, uninstallBazaarPlugin)
+ ginServer.Handle("POST", "/api/bazaar/installBazaarPlugin", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarPlugin)
+ ginServer.Handle("POST", "/api/bazaar/uninstallBazaarPlugin", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarPlugin)
ginServer.Handle("POST", "/api/bazaar/getBazaarWidget", model.CheckAuth, getBazaarWidget)
ginServer.Handle("POST", "/api/bazaar/getInstalledWidget", model.CheckAuth, getInstalledWidget)
- ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckReadonly, installBazaarWidget)
- ginServer.Handle("POST", "/api/bazaar/uninstallBazaarWidget", model.CheckAuth, model.CheckReadonly, uninstallBazaarWidget)
+ ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarWidget)
+ ginServer.Handle("POST", "/api/bazaar/uninstallBazaarWidget", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarWidget)
ginServer.Handle("POST", "/api/bazaar/getBazaarIcon", model.CheckAuth, getBazaarIcon)
ginServer.Handle("POST", "/api/bazaar/getInstalledIcon", model.CheckAuth, getInstalledIcon)
- ginServer.Handle("POST", "/api/bazaar/installBazaarIcon", model.CheckAuth, model.CheckReadonly, installBazaarIcon)
- ginServer.Handle("POST", "/api/bazaar/uninstallBazaarIcon", model.CheckAuth, model.CheckReadonly, uninstallBazaarIcon)
+ ginServer.Handle("POST", "/api/bazaar/installBazaarIcon", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarIcon)
+ ginServer.Handle("POST", "/api/bazaar/uninstallBazaarIcon", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarIcon)
ginServer.Handle("POST", "/api/bazaar/getBazaarTemplate", model.CheckAuth, getBazaarTemplate)
ginServer.Handle("POST", "/api/bazaar/getInstalledTemplate", model.CheckAuth, getInstalledTemplate)
- ginServer.Handle("POST", "/api/bazaar/installBazaarTemplate", model.CheckAuth, model.CheckReadonly, installBazaarTemplate)
- ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTemplate", model.CheckAuth, model.CheckReadonly, uninstallBazaarTemplate)
+ ginServer.Handle("POST", "/api/bazaar/installBazaarTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarTemplate)
+ ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarTemplate)
ginServer.Handle("POST", "/api/bazaar/getBazaarTheme", model.CheckAuth, getBazaarTheme)
ginServer.Handle("POST", "/api/bazaar/getInstalledTheme", model.CheckAuth, getInstalledTheme)
- ginServer.Handle("POST", "/api/bazaar/installBazaarTheme", model.CheckAuth, model.CheckReadonly, installBazaarTheme)
- ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, model.CheckReadonly, uninstallBazaarTheme)
+ ginServer.Handle("POST", "/api/bazaar/installBazaarTheme", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarTheme)
+ ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarTheme)
ginServer.Handle("POST", "/api/bazaar/getBazaarPackageREAME", model.CheckAuth, getBazaarPackageREAME)
ginServer.Handle("POST", "/api/bazaar/getUpdatedPackage", model.CheckAuth, getUpdatedPackage)
- ginServer.Handle("POST", "/api/bazaar/batchUpdatePackage", model.CheckAuth, batchUpdatePackage)
-
- ginServer.Handle("POST", "/api/repo/initRepoKey", model.CheckAuth, model.CheckReadonly, initRepoKey)
- ginServer.Handle("POST", "/api/repo/initRepoKeyFromPassphrase", model.CheckAuth, model.CheckReadonly, initRepoKeyFromPassphrase)
- ginServer.Handle("POST", "/api/repo/resetRepo", model.CheckAuth, model.CheckReadonly, resetRepo)
- ginServer.Handle("POST", "/api/repo/purgeRepo", model.CheckAuth, model.CheckReadonly, purgeRepo)
- ginServer.Handle("POST", "/api/repo/purgeCloudRepo", model.CheckAuth, model.CheckReadonly, purgeCloudRepo)
- ginServer.Handle("POST", "/api/repo/importRepoKey", model.CheckAuth, model.CheckReadonly, importRepoKey)
- ginServer.Handle("POST", "/api/repo/createSnapshot", model.CheckAuth, model.CheckReadonly, createSnapshot)
- ginServer.Handle("POST", "/api/repo/tagSnapshot", model.CheckAuth, model.CheckReadonly, tagSnapshot)
- ginServer.Handle("POST", "/api/repo/checkoutRepo", model.CheckAuth, model.CheckReadonly, checkoutRepo)
- ginServer.Handle("POST", "/api/repo/getRepoSnapshots", model.CheckAuth, getRepoSnapshots)
- ginServer.Handle("POST", "/api/repo/getRepoTagSnapshots", model.CheckAuth, getRepoTagSnapshots)
- ginServer.Handle("POST", "/api/repo/removeRepoTagSnapshot", model.CheckAuth, model.CheckReadonly, removeRepoTagSnapshot)
- ginServer.Handle("POST", "/api/repo/getCloudRepoTagSnapshots", model.CheckAuth, getCloudRepoTagSnapshots)
- ginServer.Handle("POST", "/api/repo/getCloudRepoSnapshots", model.CheckAuth, getCloudRepoSnapshots)
- ginServer.Handle("POST", "/api/repo/removeCloudRepoTagSnapshot", model.CheckAuth, model.CheckReadonly, removeCloudRepoTagSnapshot)
- ginServer.Handle("POST", "/api/repo/uploadCloudSnapshot", model.CheckAuth, model.CheckReadonly, uploadCloudSnapshot)
- ginServer.Handle("POST", "/api/repo/downloadCloudSnapshot", model.CheckAuth, model.CheckReadonly, downloadCloudSnapshot)
- ginServer.Handle("POST", "/api/repo/diffRepoSnapshots", model.CheckAuth, diffRepoSnapshots)
- ginServer.Handle("POST", "/api/repo/openRepoSnapshotDoc", model.CheckAuth, openRepoSnapshotDoc)
- ginServer.Handle("POST", "/api/repo/getRepoFile", model.CheckAuth, getRepoFile)
-
- ginServer.Handle("POST", "/api/riff/createRiffDeck", model.CheckAuth, model.CheckReadonly, createRiffDeck)
- ginServer.Handle("POST", "/api/riff/renameRiffDeck", model.CheckAuth, model.CheckReadonly, renameRiffDeck)
- ginServer.Handle("POST", "/api/riff/removeRiffDeck", model.CheckAuth, model.CheckReadonly, removeRiffDeck)
- ginServer.Handle("POST", "/api/riff/getRiffDecks", model.CheckAuth, getRiffDecks)
- ginServer.Handle("POST", "/api/riff/addRiffCards", model.CheckAuth, model.CheckReadonly, addRiffCards)
- ginServer.Handle("POST", "/api/riff/removeRiffCards", model.CheckAuth, model.CheckReadonly, removeRiffCards)
- ginServer.Handle("POST", "/api/riff/getRiffDueCards", model.CheckAuth, getRiffDueCards)
- 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)
- ginServer.Handle("POST", "/api/riff/resetRiffCards", model.CheckAuth, model.CheckReadonly, resetRiffCards)
- ginServer.Handle("POST", "/api/riff/batchSetRiffCardsDueTime", model.CheckAuth, model.CheckReadonly, batchSetRiffCardsDueTime)
- ginServer.Handle("POST", "/api/riff/getRiffCardsByBlockIDs", model.CheckAuth, model.CheckReadonly, getRiffCardsByBlockIDs)
-
- ginServer.Handle("POST", "/api/notification/pushMsg", model.CheckAuth, pushMsg)
- ginServer.Handle("POST", "/api/notification/pushErrMsg", model.CheckAuth, pushErrMsg)
+ ginServer.Handle("POST", "/api/bazaar/batchUpdatePackage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, batchUpdatePackage)
+
+ ginServer.Handle("POST", "/api/repo/initRepoKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, initRepoKey)
+ ginServer.Handle("POST", "/api/repo/initRepoKeyFromPassphrase", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, initRepoKeyFromPassphrase)
+ ginServer.Handle("POST", "/api/repo/resetRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetRepo)
+ ginServer.Handle("POST", "/api/repo/purgeRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, purgeRepo)
+ ginServer.Handle("POST", "/api/repo/purgeCloudRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, purgeCloudRepo)
+ ginServer.Handle("POST", "/api/repo/importRepoKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importRepoKey)
+ ginServer.Handle("POST", "/api/repo/createSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createSnapshot)
+ ginServer.Handle("POST", "/api/repo/tagSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, tagSnapshot)
+ ginServer.Handle("POST", "/api/repo/checkoutRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkoutRepo)
+ ginServer.Handle("POST", "/api/repo/getRepoSnapshots", model.CheckAuth, model.CheckAdminRole, getRepoSnapshots)
+ ginServer.Handle("POST", "/api/repo/getRepoTagSnapshots", model.CheckAuth, model.CheckAdminRole, getRepoTagSnapshots)
+ ginServer.Handle("POST", "/api/repo/removeRepoTagSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeRepoTagSnapshot)
+ ginServer.Handle("POST", "/api/repo/getCloudRepoTagSnapshots", model.CheckAuth, model.CheckAdminRole, getCloudRepoTagSnapshots)
+ ginServer.Handle("POST", "/api/repo/getCloudRepoSnapshots", model.CheckAuth, model.CheckAdminRole, getCloudRepoSnapshots)
+ ginServer.Handle("POST", "/api/repo/removeCloudRepoTagSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCloudRepoTagSnapshot)
+ ginServer.Handle("POST", "/api/repo/uploadCloudSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uploadCloudSnapshot)
+ ginServer.Handle("POST", "/api/repo/downloadCloudSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, downloadCloudSnapshot)
+ ginServer.Handle("POST", "/api/repo/diffRepoSnapshots", model.CheckAuth, model.CheckAdminRole, diffRepoSnapshots)
+ ginServer.Handle("POST", "/api/repo/openRepoSnapshotDoc", model.CheckAuth, model.CheckAdminRole, openRepoSnapshotDoc)
+ ginServer.Handle("POST", "/api/repo/getRepoFile", model.CheckAuth, model.CheckAdminRole, getRepoFile)
+
+ ginServer.Handle("POST", "/api/riff/createRiffDeck", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createRiffDeck)
+ ginServer.Handle("POST", "/api/riff/renameRiffDeck", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameRiffDeck)
+ ginServer.Handle("POST", "/api/riff/removeRiffDeck", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeRiffDeck)
+ ginServer.Handle("POST", "/api/riff/getRiffDecks", model.CheckAuth, model.CheckAdminRole, getRiffDecks)
+ ginServer.Handle("POST", "/api/riff/addRiffCards", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addRiffCards)
+ ginServer.Handle("POST", "/api/riff/removeRiffCards", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeRiffCards)
+ ginServer.Handle("POST", "/api/riff/getRiffDueCards", model.CheckAuth, model.CheckAdminRole, getRiffDueCards)
+ ginServer.Handle("POST", "/api/riff/getTreeRiffDueCards", model.CheckAuth, model.CheckAdminRole, getTreeRiffDueCards)
+ ginServer.Handle("POST", "/api/riff/getNotebookRiffDueCards", model.CheckAuth, model.CheckAdminRole, getNotebookRiffDueCards)
+ ginServer.Handle("POST", "/api/riff/reviewRiffCard", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, reviewRiffCard)
+ ginServer.Handle("POST", "/api/riff/skipReviewRiffCard", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, skipReviewRiffCard)
+ ginServer.Handle("POST", "/api/riff/getRiffCards", model.CheckAuth, model.CheckAdminRole, getRiffCards)
+ ginServer.Handle("POST", "/api/riff/getTreeRiffCards", model.CheckAuth, model.CheckAdminRole, getTreeRiffCards)
+ ginServer.Handle("POST", "/api/riff/getNotebookRiffCards", model.CheckAuth, model.CheckAdminRole, getNotebookRiffCards)
+ ginServer.Handle("POST", "/api/riff/resetRiffCards", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetRiffCards)
+ ginServer.Handle("POST", "/api/riff/batchSetRiffCardsDueTime", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, batchSetRiffCardsDueTime)
+ ginServer.Handle("POST", "/api/riff/getRiffCardsByBlockIDs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getRiffCardsByBlockIDs)
+
+ ginServer.Handle("POST", "/api/notification/pushMsg", model.CheckAuth, model.CheckAdminRole, pushMsg)
+ ginServer.Handle("POST", "/api/notification/pushErrMsg", model.CheckAuth, model.CheckAdminRole, pushErrMsg)
ginServer.Handle("POST", "/api/snippet/getSnippet", model.CheckAuth, getSnippet)
- ginServer.Handle("POST", "/api/snippet/setSnippet", model.CheckAuth, model.CheckReadonly, setSnippet)
- ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckReadonly, removeSnippet)
+ ginServer.Handle("POST", "/api/snippet/setSnippet", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSnippet)
+ ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeSnippet)
ginServer.Handle("POST", "/api/av/renderAttributeView", model.CheckAuth, renderAttributeView)
- ginServer.Handle("POST", "/api/av/renderHistoryAttributeView", model.CheckAuth, renderHistoryAttributeView)
- ginServer.Handle("POST", "/api/av/renderSnapshotAttributeView", model.CheckAuth, renderSnapshotAttributeView)
+ ginServer.Handle("POST", "/api/av/renderHistoryAttributeView", model.CheckAuth, model.CheckAdminRole, renderHistoryAttributeView)
+ ginServer.Handle("POST", "/api/av/renderSnapshotAttributeView", model.CheckAuth, model.CheckAdminRole, renderSnapshotAttributeView)
ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys)
- ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr)
+ ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAttributeViewBlockAttr)
ginServer.Handle("POST", "/api/av/searchAttributeView", model.CheckAuth, model.CheckReadonly, searchAttributeView)
ginServer.Handle("POST", "/api/av/getAttributeView", model.CheckAuth, model.CheckReadonly, getAttributeView)
- ginServer.Handle("POST", "/api/av/searchAttributeViewRelationKey", model.CheckAuth, model.CheckReadonly, searchAttributeViewRelationKey)
- ginServer.Handle("POST", "/api/av/searchAttributeViewNonRelationKey", model.CheckAuth, model.CheckReadonly, searchAttributeViewNonRelationKey)
- ginServer.Handle("POST", "/api/av/getAttributeViewFilterSort", model.CheckAuth, model.CheckReadonly, getAttributeViewFilterSort)
- ginServer.Handle("POST", "/api/av/addAttributeViewKey", model.CheckAuth, model.CheckReadonly, addAttributeViewKey)
- ginServer.Handle("POST", "/api/av/removeAttributeViewKey", model.CheckAuth, model.CheckReadonly, removeAttributeViewKey)
- ginServer.Handle("POST", "/api/av/sortAttributeViewViewKey", model.CheckAuth, model.CheckReadonly, sortAttributeViewViewKey)
- ginServer.Handle("POST", "/api/av/sortAttributeViewKey", model.CheckAuth, model.CheckReadonly, sortAttributeViewKey)
- ginServer.Handle("POST", "/api/av/addAttributeViewBlocks", model.CheckAuth, model.CheckReadonly, addAttributeViewBlocks)
- ginServer.Handle("POST", "/api/av/removeAttributeViewBlocks", model.CheckAuth, model.CheckReadonly, removeAttributeViewBlocks)
- ginServer.Handle("POST", "/api/av/getAttributeViewPrimaryKeyValues", model.CheckAuth, model.CheckReadonly, getAttributeViewPrimaryKeyValues)
- ginServer.Handle("POST", "/api/av/setDatabaseBlockView", model.CheckAuth, model.CheckReadonly, setDatabaseBlockView)
- ginServer.Handle("POST", "/api/av/getMirrorDatabaseBlocks", model.CheckAuth, model.CheckReadonly, getMirrorDatabaseBlocks)
- ginServer.Handle("POST", "/api/av/getAttributeViewKeysByAvID", model.CheckAuth, model.CheckReadonly, getAttributeViewKeysByAvID)
- ginServer.Handle("POST", "/api/av/duplicateAttributeViewBlock", model.CheckAuth, model.CheckReadonly, duplicateAttributeViewBlock)
- ginServer.Handle("POST", "/api/av/appendAttributeViewDetachedBlocksWithValues", model.CheckAuth, model.CheckReadonly, appendAttributeViewDetachedBlocksWithValues)
-
- ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, chatGPT)
- ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, chatGPTWithAction)
+ ginServer.Handle("POST", "/api/av/searchAttributeViewRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewRelationKey)
+ ginServer.Handle("POST", "/api/av/searchAttributeViewNonRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewNonRelationKey)
+ ginServer.Handle("POST", "/api/av/getAttributeViewFilterSort", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewFilterSort)
+ ginServer.Handle("POST", "/api/av/addAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addAttributeViewKey)
+ ginServer.Handle("POST", "/api/av/removeAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeAttributeViewKey)
+ ginServer.Handle("POST", "/api/av/sortAttributeViewViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, sortAttributeViewViewKey)
+ ginServer.Handle("POST", "/api/av/sortAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, sortAttributeViewKey)
+ ginServer.Handle("POST", "/api/av/addAttributeViewBlocks", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addAttributeViewBlocks)
+ ginServer.Handle("POST", "/api/av/removeAttributeViewBlocks", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeAttributeViewBlocks)
+ ginServer.Handle("POST", "/api/av/getAttributeViewPrimaryKeyValues", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewPrimaryKeyValues)
+ ginServer.Handle("POST", "/api/av/setDatabaseBlockView", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setDatabaseBlockView)
+ ginServer.Handle("POST", "/api/av/getMirrorDatabaseBlocks", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getMirrorDatabaseBlocks)
+ ginServer.Handle("POST", "/api/av/getAttributeViewKeysByAvID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewKeysByAvID)
+ ginServer.Handle("POST", "/api/av/duplicateAttributeViewBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateAttributeViewBlock)
+ ginServer.Handle("POST", "/api/av/appendAttributeViewDetachedBlocksWithValues", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, appendAttributeViewDetachedBlocksWithValues)
+
+ ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, model.CheckAdminRole, chatGPT)
+ ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, model.CheckAdminRole, chatGPTWithAction)
ginServer.Handle("POST", "/api/petal/loadPetals", model.CheckAuth, loadPetals)
- ginServer.Handle("POST", "/api/petal/setPetalEnabled", model.CheckAuth, model.CheckReadonly, setPetalEnabled)
+ ginServer.Handle("POST", "/api/petal/setPetalEnabled", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setPetalEnabled)
- ginServer.Any("/api/network/echo", model.CheckAuth, echo)
- ginServer.Handle("POST", "/api/network/forwardProxy", model.CheckAuth, forwardProxy)
+ ginServer.Any("/api/network/echo", model.CheckAuth, model.CheckAdminRole, echo)
+ ginServer.Handle("POST", "/api/network/forwardProxy", model.CheckAuth, model.CheckAdminRole, forwardProxy)
- ginServer.Handle("GET", "/ws/broadcast", model.CheckAuth, broadcast)
- ginServer.Handle("POST", "/api/broadcast/postMessage", model.CheckAuth, postMessage)
- ginServer.Handle("POST", "/api/broadcast/getChannels", model.CheckAuth, getChannels)
- ginServer.Handle("POST", "/api/broadcast/getChannelInfo", model.CheckAuth, getChannelInfo)
+ ginServer.Handle("GET", "/ws/broadcast", model.CheckAuth, model.CheckAdminRole, broadcast)
+ ginServer.Handle("POST", "/api/broadcast/postMessage", model.CheckAuth, model.CheckAdminRole, postMessage)
+ ginServer.Handle("POST", "/api/broadcast/getChannels", model.CheckAuth, model.CheckAdminRole, getChannels)
+ ginServer.Handle("POST", "/api/broadcast/getChannelInfo", model.CheckAuth, model.CheckAdminRole, getChannelInfo)
- ginServer.Handle("POST", "/api/archive/zip", model.CheckAuth, model.CheckReadonly, zip)
- ginServer.Handle("POST", "/api/archive/unzip", model.CheckAuth, model.CheckReadonly, unzip)
+ ginServer.Handle("POST", "/api/archive/zip", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, zip)
+ ginServer.Handle("POST", "/api/archive/unzip", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, unzip)
}
diff --git a/kernel/api/setting.go b/kernel/api/setting.go
index 733c4eefd43..4a6322455ad 100644
--- a/kernel/api/setting.go
+++ b/kernel/api/setting.go
@@ -25,6 +25,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/model"
+ "github.com/siyuan-note/siyuan/kernel/server/proxy"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
)
@@ -533,6 +534,58 @@ func setAppearance(c *gin.Context) {
ret.Data = model.Conf.Appearance
}
+func setPublish(c *gin.Context) {
+ ret := gulu.Ret.NewResult()
+ defer c.JSON(http.StatusOK, ret)
+
+ arg, ok := util.JsonArg(c, ret)
+ if !ok {
+ return
+ }
+
+ param, err := gulu.JSON.MarshalJSON(arg)
+ if nil != err {
+ ret.Code = -1
+ ret.Msg = err.Error()
+ return
+ }
+
+ publish := &conf.Publish{}
+ if err = gulu.JSON.UnmarshalJSON(param, publish); nil != err {
+ ret.Code = -1
+ ret.Msg = err.Error()
+ return
+ }
+
+ model.Conf.Publish = publish
+ model.Conf.Save()
+
+ if port, err := proxy.InitPublishService(); err != nil {
+ ret.Code = -1
+ ret.Msg = err.Error()
+ } else {
+ ret.Data = map[string]any{
+ "port": port,
+ "publish": model.Conf.Publish,
+ }
+ }
+}
+
+func getPublish(c *gin.Context) {
+ ret := gulu.Ret.NewResult()
+ defer c.JSON(http.StatusOK, ret)
+
+ if port, err := proxy.InitPublishService(); err != nil {
+ ret.Code = -1
+ ret.Msg = err.Error()
+ } else {
+ ret.Data = map[string]any{
+ "port": port,
+ "publish": model.Conf.Publish,
+ }
+ }
+}
+
func getCloudUser(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
diff --git a/kernel/api/snippet.go b/kernel/api/snippet.go
index 6d26dd3667f..b975d061b4a 100644
--- a/kernel/api/snippet.go
+++ b/kernel/api/snippet.go
@@ -17,44 +17,17 @@
package api
import (
- "mime"
"net/http"
- "path/filepath"
"strings"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/gin-gonic/gin"
- "github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
-func serveSnippets(c *gin.Context) {
- filePath := strings.TrimPrefix(c.Request.URL.Path, "/snippets/")
- ext := filepath.Ext(filePath)
- name := strings.TrimSuffix(filePath, ext)
- confSnippets, err := model.LoadSnippets()
- if nil != err {
- logging.LogErrorf("load snippets failed: %s", err)
- c.Status(404)
- return
- }
-
- for _, s := range confSnippets {
- if s.Name == name && ("" != ext && s.Type == ext[1:]) {
- c.Header("Content-Type", mime.TypeByExtension(ext))
- c.String(http.StatusOK, s.Content)
- return
- }
- }
-
- // 没有在配置文件中命中时在文件系统上查找
- filePath = filepath.Join(util.SnippetsPath, filePath)
- c.File(filePath)
-}
-
func getSnippet(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
diff --git a/kernel/api/system.go b/kernel/api/system.go
index 18c235cf7a2..300d0353b56 100644
--- a/kernel/api/system.go
+++ b/kernel/api/system.go
@@ -17,7 +17,6 @@
package api
import (
- "github.com/88250/lute"
"net/http"
"os"
"path/filepath"
@@ -25,6 +24,8 @@ import (
"sync"
"time"
+ "github.com/88250/lute"
+
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/logging"
@@ -218,6 +219,17 @@ func getConf(c *gin.Context) {
maskedConf.Sync.Stat = model.Conf.Language(53)
}
+ // REF: https://github.com/siyuan-note/siyuan/issues/11364
+ role := model.GetGinContextRole(c)
+ if model.IsReadOnlyRole(role) {
+ maskedConf.ReadOnly = true
+ }
+ if !model.IsValidRole(role, []model.Role{
+ model.RoleAdministrator,
+ }) {
+ model.HideConfSecret(maskedConf)
+ }
+
ret.Data = map[string]interface{}{
"conf": maskedConf,
"start": !util.IsUILoaded,
diff --git a/kernel/conf/publish.go b/kernel/conf/publish.go
new file mode 100644
index 00000000000..45552c5ccc5
--- /dev/null
+++ b/kernel/conf/publish.go
@@ -0,0 +1,45 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package conf
+
+type Publish struct {
+ Enable bool `json:"enable"` // 是否启用发布服务
+ Port uint16 `json:"port"` // 发布服务端口
+ Auth *BasicAuth `json:"auth"` // Basic 认证
+}
+
+type BasicAuth struct {
+ Enable bool `json:"enable"` // 是否启用基础认证
+ Accounts []*BasicAuthAccount `json:"accounts"` // 账户列表
+}
+
+type BasicAuthAccount struct {
+ Username string `json:"username"` // 用户名
+ Password string `json:"password"` // 密码
+ Memo string `json:"memo"` // 备注
+}
+
+func NewPublish() *Publish {
+ return &Publish{
+ Enable: false,
+ Port: 6808,
+ Auth: &BasicAuth{
+ Enable: true,
+ Accounts: []*BasicAuthAccount{},
+ },
+ }
+}
diff --git a/kernel/go.mod b/kernel/go.mod
index e847bdb50f6..8c5beb6e13b 100644
--- a/kernel/go.mod
+++ b/kernel/go.mod
@@ -33,6 +33,7 @@ require (
github.com/go-ole/go-ole v1.3.0
github.com/goccy/go-json v0.10.3
github.com/gofrs/flock v0.8.1
+ github.com/golang-jwt/jwt/v5 v5.2.1
github.com/gorilla/css v1.0.1
github.com/gorilla/websocket v1.5.1
github.com/imroc/req/v3 v3.43.7
diff --git a/kernel/go.sum b/kernel/go.sum
index d756bc01484..5e996521d32 100644
--- a/kernel/go.sum
+++ b/kernel/go.sum
@@ -154,6 +154,8 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
diff --git a/kernel/model/auth.go b/kernel/model/auth.go
new file mode 100644
index 00000000000..79022d226df
--- /dev/null
+++ b/kernel/model/auth.go
@@ -0,0 +1,133 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package model
+
+import (
+ "crypto/rand"
+ "net/http"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/siyuan-note/logging"
+)
+
+type Account struct {
+ Username string
+ Password string
+ Token string
+}
+type AccountsMap map[string]*Account
+type ClaimsKeyType string
+
+const (
+ XAuthTokenKey = "X-Auth-Token"
+
+ ClaimsContextKey = "claims"
+
+ iss = "siyuan-publish-reverse-proxy-server"
+ sub = "publish"
+ aud = "siyuan-kernel"
+
+ ClaimsKeyRole string = "role"
+)
+
+var (
+ accountsMap = AccountsMap{}
+
+ key = make([]byte, 32)
+)
+
+func GetBasicAuthAccount(username string) *Account {
+ return accountsMap[username]
+}
+
+func InitAccounts() {
+ accountsMap = AccountsMap{
+ "": &Account{}, // 匿名用户
+ }
+ for _, account := range Conf.Publish.Auth.Accounts {
+ accountsMap[account.Username] = &Account{
+ Username: account.Username,
+ Password: account.Password,
+ }
+ }
+
+ InitJWT()
+}
+
+func InitJWT() {
+ if _, err := rand.Read(key); err != nil {
+ logging.LogErrorf("generate JWT signing key failed: %s", err)
+ return
+ }
+
+ for username, account := range accountsMap {
+ // REF: https://golang-jwt.github.io/jwt/usage/create/
+ t := jwt.NewWithClaims(
+ jwt.SigningMethodHS256,
+ jwt.MapClaims{
+ "iss": iss,
+ "sub": sub,
+ "aud": aud,
+ "jti": username,
+
+ ClaimsKeyRole: RoleReader,
+ },
+ )
+ if token, err := t.SignedString(key); err != nil {
+ logging.LogErrorf("JWT signature failed: %s", err)
+ return
+ } else {
+ account.Token = token
+ }
+ }
+}
+
+func ParseJWT(tokenString string) (*jwt.Token, error) {
+ // REF: https://golang-jwt.github.io/jwt/usage/parse/
+ return jwt.Parse(
+ tokenString,
+ func(token *jwt.Token) (interface{}, error) {
+ return key, nil
+ },
+ jwt.WithIssuer(iss),
+ jwt.WithSubject(sub),
+ jwt.WithAudience(aud),
+ )
+}
+
+func ParseXAuthToken(r *http.Request) *jwt.Token {
+ tokenString := r.Header.Get(XAuthTokenKey)
+ if tokenString != "" {
+ if token, err := ParseJWT(tokenString); err != nil {
+ logging.LogErrorf("JWT parse failed: %s", err)
+ } else {
+ return token
+ }
+ }
+ return nil
+}
+
+func GetTokenClaims(token *jwt.Token) jwt.MapClaims {
+ return token.Claims.(jwt.MapClaims)
+}
+
+func GetClaimRole(claims jwt.MapClaims) Role {
+ if role := claims[ClaimsKeyRole]; role != nil {
+ return Role(role.(float64))
+ }
+ return RoleVisitor
+}
diff --git a/kernel/model/conf.go b/kernel/model/conf.go
index cbc3f9e2d1c..9a518967050 100644
--- a/kernel/model/conf.go
+++ b/kernel/model/conf.go
@@ -76,6 +76,7 @@ type AppConf struct {
Stat *conf.Stat `json:"stat"` // 统计
Api *conf.API `json:"api"` // API
Repo *conf.Repo `json:"repo"` // 数据仓库
+ Publish *conf.Publish `json:"publish"` // 发布服务
OpenHelp bool `json:"openHelp"` // 启动后是否需要打开用户指南
ShowChangelog bool `json:"showChangelog"` // 是否显示版本更新日志
CloudRegion int `json:"cloudRegion"` // 云端区域,0:中国大陆,1:北美
@@ -357,6 +358,10 @@ func InitConf() {
Conf.Bazaar = conf.NewBazaar()
}
+ if nil == Conf.Publish {
+ Conf.Publish = conf.NewPublish()
+ }
+
if nil == Conf.Repo {
Conf.Repo = conf.NewRepo()
}
@@ -894,6 +899,24 @@ func GetMaskedConf() (ret *AppConf, err error) {
return
}
+// REF: https://github.com/siyuan-note/siyuan/issues/11364
+// HideConfSecret 隐藏设置中的秘密信息
+func HideConfSecret(c *AppConf) {
+ c.AI = &conf.AI{}
+ c.Api = &conf.API{}
+ c.Flashcard = &conf.Flashcard{}
+ c.LocalIPs = []string{}
+ c.Publish = &conf.Publish{}
+ c.Repo = &conf.Repo{}
+ c.Sync = &conf.Sync{}
+ c.System.AppDir = ""
+ c.System.ConfDir = ""
+ c.System.DataDir = ""
+ c.System.HomeDir = ""
+ c.System.Name = ""
+ c.System.NetworkProxy = &conf.NetworkProxy{}
+}
+
func clearPortJSON() {
pid := fmt.Sprintf("%d", os.Getpid())
portJSON := filepath.Join(util.HomeDir, ".config", "siyuan", "port.json")
diff --git a/kernel/model/role.go b/kernel/model/role.go
new file mode 100644
index 00000000000..5f0fca3aa55
--- /dev/null
+++ b/kernel/model/role.go
@@ -0,0 +1,56 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package model
+
+import "github.com/gin-gonic/gin"
+
+type Role uint
+
+const (
+ RoleContextKey = "role"
+)
+
+const (
+ RoleAdministrator Role = iota // 管理员
+ RoleEditor // 编辑者
+ RoleReader // 读者
+ RoleVisitor // 匿名访问者
+)
+
+func IsValidRole(role Role, roles []Role) bool {
+ for _, role_ := range roles {
+ if role == role_ {
+ return true
+ }
+ }
+ return false
+}
+
+func IsReadOnlyRole(role Role) bool {
+ return IsValidRole(role, []Role{
+ RoleReader,
+ RoleVisitor,
+ })
+}
+
+func GetGinContextRole(c *gin.Context) Role {
+ if role, exists := c.Get(RoleContextKey); exists {
+ return role.(Role)
+ } else {
+ return RoleVisitor
+ }
+}
diff --git a/kernel/model/session.go b/kernel/model/session.go
index 1f1ea227344..9eff880344e 100644
--- a/kernel/model/session.go
+++ b/kernel/model/session.go
@@ -164,6 +164,16 @@ func CheckReadonly(c *gin.Context) {
}
func CheckAuth(c *gin.Context) {
+ // 已通过 JWT 认证
+ if role := GetGinContextRole(c); IsValidRole(role, []Role{
+ RoleAdministrator,
+ RoleEditor,
+ RoleReader,
+ }) {
+ c.Next()
+ return
+ }
+
//logging.LogInfof("check auth for [%s]", c.Request.RequestURI)
localhost := util.IsLocalHost(c.Request.RemoteAddr)
@@ -171,6 +181,7 @@ func CheckAuth(c *gin.Context) {
if "" == Conf.AccessAuthCode {
// Skip the empty access authorization code check https://github.com/siyuan-note/siyuan/issues/9709
if util.SiyuanAccessAuthCodeBypass {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
@@ -190,6 +201,7 @@ func CheckAuth(c *gin.Context) {
return
}
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
@@ -206,19 +218,23 @@ func CheckAuth(c *gin.Context) {
// 放过来自本机的某些请求
if localhost {
if strings.HasPrefix(c.Request.RequestURI, "/assets/") {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
if strings.HasPrefix(c.Request.RequestURI, "/api/system/exit") {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
if strings.HasPrefix(c.Request.RequestURI, "/api/system/getNetwork") {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
if strings.HasPrefix(c.Request.RequestURI, "/api/sync/performSync") {
if util.ContainerIOS == util.Container || util.ContainerAndroid == util.Container {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
@@ -229,6 +245,7 @@ func CheckAuth(c *gin.Context) {
session := util.GetSession(c)
workspaceSession := util.GetWorkspaceSession(session)
if workspaceSession.AccessAuthCode == Conf.AccessAuthCode {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
@@ -248,6 +265,7 @@ func CheckAuth(c *gin.Context) {
if "" != token {
if Conf.Api.Token == token {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
@@ -261,6 +279,7 @@ func CheckAuth(c *gin.Context) {
// 通过 API token (query-params: token)
if token := c.Query("token"); "" != token {
if Conf.Api.Token == token {
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
return
}
@@ -300,9 +319,43 @@ func CheckAuth(c *gin.Context) {
return
}
+ c.Set(RoleContextKey, RoleAdministrator)
c.Next()
}
+func CheckAdminRole(c *gin.Context) {
+ if IsValidRole(GetGinContextRole(c), []Role{
+ RoleAdministrator,
+ }) {
+ c.Next()
+ } else {
+ c.AbortWithStatus(http.StatusForbidden)
+ }
+}
+
+func CheckEditRole(c *gin.Context) {
+ if IsValidRole(GetGinContextRole(c), []Role{
+ RoleAdministrator,
+ RoleEditor,
+ }) {
+ c.Next()
+ } else {
+ c.AbortWithStatus(http.StatusForbidden)
+ }
+}
+
+func CheckReadRole(c *gin.Context) {
+ if IsValidRole(GetGinContextRole(c), []Role{
+ RoleAdministrator,
+ RoleEditor,
+ RoleReader,
+ }) {
+ c.Next()
+ } else {
+ c.AbortWithStatus(http.StatusForbidden)
+ }
+}
+
var timingAPIs = map[string]int{
"/api/search/fullTextSearchBlock": 200, // Monitor the search performance and suggest solutions https://github.com/siyuan-note/siyuan/issues/7873
}
diff --git a/kernel/server/port.go b/kernel/server/port.go
index ec26f4e1fdc..b982bb9221f 100644
--- a/kernel/server/port.go
+++ b/kernel/server/port.go
@@ -18,7 +18,6 @@ package server
import (
"fmt"
- "net"
"os"
"os/exec"
"path/filepath"
@@ -65,7 +64,7 @@ func killRunningKernel() {
}
func killByPort(port string) {
- if !isPortOpen(port) {
+ if !util.IsPortOpen(port) {
return
}
@@ -87,19 +86,6 @@ func killByPort(port string) {
logging.LogInfof("killed process [name=%s, pid=%s]", name, pid)
}
-func isPortOpen(port string) bool {
- timeout := time.Second
- conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout)
- if nil != err {
- return false
- }
- if nil != conn {
- conn.Close()
- return true
- }
- return false
-}
-
func kill(pid string) {
var killCmd *exec.Cmd
if gulu.OS.IsWindows() {
diff --git a/kernel/server/proxy/fixedport.go b/kernel/server/proxy/fixedport.go
new file mode 100644
index 00000000000..dface6bc1e6
--- /dev/null
+++ b/kernel/server/proxy/fixedport.go
@@ -0,0 +1,41 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package proxy
+
+import (
+ "net/http"
+ "net/http/httputil"
+
+ "github.com/siyuan-note/logging"
+ "github.com/siyuan-note/siyuan/kernel/util"
+)
+
+func InitFixedPortService(host string) {
+ if util.FixedPort != util.ServerPort {
+ if util.IsPortOpen(util.FixedPort) {
+ return
+ }
+
+ // 启动一个固定 6806 端口的反向代理服务器,这样浏览器扩展才能直接使用 127.0.0.1:6806,不用配置端口
+ proxy := httputil.NewSingleHostReverseProxy(util.ServerURL)
+ logging.LogInfof("fixed port service [%s:%s] is running", host, util.FixedPort)
+ if proxyErr := http.ListenAndServe(host+":"+util.FixedPort, proxy); nil != proxyErr {
+ logging.LogWarnf("boot fixed port service [%s] failed: %s", util.ServerURL, proxyErr)
+ }
+ logging.LogInfof("fixed port service [%s:%s] is stopped", host, util.FixedPort)
+ }
+}
diff --git a/kernel/server/proxy/publish.go b/kernel/server/proxy/publish.go
new file mode 100644
index 00000000000..6490d50521f
--- /dev/null
+++ b/kernel/server/proxy/publish.go
@@ -0,0 +1,161 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package proxy
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "strconv"
+
+ "github.com/siyuan-note/logging"
+ "github.com/siyuan-note/siyuan/kernel/model"
+ "github.com/siyuan-note/siyuan/kernel/util"
+)
+
+type PublishServiceTransport struct{}
+
+var (
+ Host = "0.0.0.0"
+ Port = "0"
+
+ listener net.Listener
+ transport = PublishServiceTransport{}
+ proxy = &httputil.ReverseProxy{
+ Rewrite: rewrite,
+ Transport: transport,
+ }
+)
+
+func InitPublishService() (uint16, error) {
+ model.InitAccounts()
+
+ if listener != nil {
+ if !model.Conf.Publish.Enable {
+ // 关闭发布服务
+ closePublishListener()
+ return 0, nil
+ }
+
+ if port, err := util.ParsePort(Port); err != nil {
+ return 0, err
+ } else if port != model.Conf.Publish.Port {
+ // 关闭原端口的发布服务
+ if err = closePublishListener(); err != nil {
+ return 0, err
+ }
+
+ // 重新启动新端口的发布服务
+ initPublishService()
+ }
+ } else {
+ if !model.Conf.Publish.Enable {
+ return 0, nil
+ }
+
+ // 启动新端口的发布服务
+ initPublishService()
+ }
+ return util.ParsePort(Port)
+}
+
+func initPublishService() (err error) {
+ if err = initPublishListener(); err == nil {
+ go startPublishReverseProxyService()
+ }
+ return
+}
+
+func initPublishListener() (err error) {
+ // Start new listener
+ listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", Host, model.Conf.Publish.Port))
+ if err != nil {
+ logging.LogErrorf("start listener failed: %s", err)
+ return
+ }
+
+ _, Port, err = net.SplitHostPort(listener.Addr().String())
+ if nil != err {
+ logging.LogErrorf("split host and port failed: %s", err)
+ return
+ }
+ return
+}
+
+func closePublishListener() (err error) {
+ listener_ := listener
+ listener = nil
+ if err = listener_.Close(); err != nil {
+ logging.LogErrorf("close listener %s failed: %s", listener_.Addr().String(), err)
+ listener = listener_
+ }
+ return
+}
+
+func startPublishReverseProxyService() {
+ logging.LogInfof("publish service [%s:%s] is running", Host, Port)
+ // 服务进行时一直阻塞
+ if err := http.Serve(listener, proxy); nil != err {
+ if listener != nil {
+ logging.LogErrorf("boot publish service failed: %s", err)
+ }
+ }
+ logging.LogInfof("publish service [%s:%s] is stopped", Host, Port)
+}
+
+func rewrite(r *httputil.ProxyRequest) {
+ r.SetURL(util.ServerURL)
+ r.SetXForwarded()
+ // r.Out.Host = r.In.Host // if desired
+}
+
+func (PublishServiceTransport) RoundTrip(request *http.Request) (response *http.Response, err error) {
+ if model.Conf.Publish.Auth.Enable {
+ // Basic Auth
+ username, password, ok := request.BasicAuth()
+ account := model.GetBasicAuthAccount(username)
+
+ if !ok ||
+ account == nil ||
+ account.Username == "" || // 匿名用户
+ account.Password != password {
+
+ return &http.Response{
+ StatusCode: http.StatusUnauthorized,
+ Status: http.StatusText(http.StatusUnauthorized),
+ Proto: request.Proto,
+ ProtoMajor: request.ProtoMajor,
+ ProtoMinor: request.ProtoMinor,
+ Request: request,
+ Header: http.Header{
+ "WWW-Authenticate": {"Basic realm=" + strconv.Quote("Authorization Required")},
+ },
+ Close: false,
+ ContentLength: -1,
+ }, nil
+ } else {
+ // set JWT
+ request.Header.Set(model.XAuthTokenKey, account.Token)
+ }
+ } else {
+ request.Header.Set(model.XAuthTokenKey, model.GetBasicAuthAccount("").Token)
+ }
+
+ response, err = http.DefaultTransport.RoundTrip(request)
+ return
+}
diff --git a/kernel/server/serve.go b/kernel/server/serve.go
index 77d7e98b38b..b5b69692296 100644
--- a/kernel/server/serve.go
+++ b/kernel/server/serve.go
@@ -20,9 +20,9 @@ import (
"bytes"
"fmt"
"html/template"
+ "mime"
"net"
"net/http"
- "net/http/httputil"
"net/http/pprof"
"net/url"
"os"
@@ -42,10 +42,13 @@ import (
"github.com/siyuan-note/siyuan/kernel/api"
"github.com/siyuan-note/siyuan/kernel/cmd"
"github.com/siyuan-note/siyuan/kernel/model"
+ "github.com/siyuan-note/siyuan/kernel/server/proxy"
"github.com/siyuan-note/siyuan/kernel/util"
)
-var cookieStore = cookie.NewStore([]byte("ATN51UlxVq1Gcvdf"))
+var (
+ cookieStore = cookie.NewStore([]byte("ATN51UlxVq1Gcvdf"))
+)
func Serve(fastMode bool) {
gin.SetMode(gin.ReleaseMode)
@@ -57,6 +60,7 @@ func Serve(fastMode bool) {
model.Timing,
model.Recover,
corsMiddleware(), // 后端服务支持 CORS 预检请求验证 https://github.com/siyuan-note/siyuan/pull/5593
+ jwtMiddleware, // 解析 JWT https://github.com/siyuan-note/siyuan/issues/11364
gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".pdf", ".mp3", ".wav", ".ogg", ".mov", ".weba", ".mkv", ".mp4", ".webm"})),
)
@@ -78,7 +82,10 @@ func Serve(fastMode bool) {
serveEmojis(ginServer)
serveTemplates(ginServer)
servePublic(ginServer)
+ serveSnippets(ginServer)
serveRepoDiff(ginServer)
+ serveCheckAuth(ginServer)
+ serveFixedStaticFiles(ginServer)
api.ServeAPI(ginServer)
var host string
@@ -108,34 +115,27 @@ func Serve(fastMode bool) {
}
util.ServerPort = port
+ util.ServerURL, err = url.Parse("http://127.0.0.1:" + port)
+ if err != nil {
+ logging.LogErrorf("parse server url failed: %s", err)
+ }
+
pid := fmt.Sprintf("%d", os.Getpid())
if !fastMode {
rewritePortJSON(pid, port)
}
-
logging.LogInfof("kernel [pid=%s] http server [%s] is booting", pid, host+":"+port)
util.HttpServing = true
+ go util.HookUILoaded()
+
go func() {
time.Sleep(1 * time.Second)
- if util.FixedPort != port {
- if isPortOpen(util.FixedPort) {
- return
- }
-
- // 启动一个 6806 端口的反向代理服务器,这样浏览器扩展才能直接使用 127.0.0.1:6806,不用配置端口
- serverURL, _ := url.Parse("http://127.0.0.1:" + port)
- proxy := httputil.NewSingleHostReverseProxy(serverURL)
- logging.LogInfof("reverse proxy server [%s] is booting", host+":"+util.FixedPort)
- if proxyErr := http.ListenAndServe(host+":"+util.FixedPort, proxy); nil != proxyErr {
- logging.LogWarnf("boot reverse proxy server [%s] failed: %s", serverURL, proxyErr)
- }
- // 反代服务器启动失败不影响核心服务器启动
- }
+ go proxy.InitFixedPortService(host)
+ go proxy.InitPublishService()
+ // 反代服务器启动失败不影响核心服务器启动
}()
- go util.HookUILoaded()
-
if err = http.Serve(ln, ginServer.Handler()); nil != err {
if !fastMode {
logging.LogErrorf("boot kernel failed: %s", err)
@@ -196,11 +196,33 @@ func servePublic(ginServer *gin.Engine) {
ginServer.Static("/public/", filepath.Join(util.DataDir, "public"))
}
-func serveAppearance(ginServer *gin.Engine) {
- ginServer.StaticFile("favicon.ico", filepath.Join(util.WorkingDir, "stage", "icon.png"))
- ginServer.StaticFile("manifest.json", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest"))
- ginServer.StaticFile("manifest.webmanifest", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest"))
+func serveSnippets(ginServer *gin.Engine) {
+ ginServer.Handle("GET", "/snippets/*filepath", func(c *gin.Context) {
+ filePath := strings.TrimPrefix(c.Request.URL.Path, "/snippets/")
+ ext := filepath.Ext(filePath)
+ name := strings.TrimSuffix(filePath, ext)
+ confSnippets, err := model.LoadSnippets()
+ if nil != err {
+ logging.LogErrorf("load snippets failed: %s", err)
+ c.Status(http.StatusNotFound)
+ return
+ }
+
+ for _, s := range confSnippets {
+ if s.Name == name && ("" != ext && s.Type == ext[1:]) {
+ c.Header("Content-Type", mime.TypeByExtension(ext))
+ c.String(http.StatusOK, s.Content)
+ return
+ }
+ }
+ // 没有在配置文件中命中时在文件系统上查找
+ filePath = filepath.Join(util.SnippetsPath, filePath)
+ c.File(filePath)
+ })
+}
+
+func serveAppearance(ginServer *gin.Engine) {
siyuan := ginServer.Group("", model.CheckAuth)
siyuan.Handle("GET", "/", func(c *gin.Context) {
@@ -295,12 +317,13 @@ func serveAppearance(ginServer *gin.Engine) {
})
siyuan.Static("/stage/", filepath.Join(util.WorkingDir, "stage"))
- ginServer.StaticFile("service-worker.js", filepath.Join(util.WorkingDir, "stage", "service-worker.js"))
+}
- siyuan.GET("/check-auth", serveCheckAuth)
+func serveCheckAuth(ginServer *gin.Engine) {
+ ginServer.GET("/check-auth", serveAuthPage)
}
-func serveCheckAuth(c *gin.Context) {
+func serveAuthPage(c *gin.Context) {
data, err := os.ReadFile(filepath.Join(util.WorkingDir, "stage/auth.html"))
if nil != err {
logging.LogErrorf("load auth page failed: %s", err)
@@ -363,20 +386,20 @@ func serveCheckAuth(c *gin.Context) {
}
func serveAssets(ginServer *gin.Engine) {
- ginServer.POST("/upload", model.CheckAuth, model.Upload)
+ ginServer.POST("/upload", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, model.Upload)
ginServer.GET("/assets/*path", model.CheckAuth, func(context *gin.Context) {
requestPath := context.Param("path")
relativePath := path.Join("assets", requestPath)
p, err := model.GetAssetAbsPath(relativePath)
if nil != err {
- context.Status(404)
+ context.Status(http.StatusNotFound)
return
}
http.ServeFile(context.Writer, context.Request, p)
return
})
- ginServer.GET("/history/*path", model.CheckAuth, func(context *gin.Context) {
+ ginServer.GET("/history/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) {
p := filepath.Join(util.HistoryDir, context.Param("path"))
http.ServeFile(context.Writer, context.Request, p)
return
@@ -384,7 +407,7 @@ func serveAssets(ginServer *gin.Engine) {
}
func serveRepoDiff(ginServer *gin.Engine) {
- ginServer.GET("/repo/diff/*path", model.CheckAuth, func(context *gin.Context) {
+ ginServer.GET("/repo/diff/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) {
requestPath := context.Param("path")
p := filepath.Join(util.TempDir, "repo", "diff", requestPath)
http.ServeFile(context.Writer, context.Request, p)
@@ -451,6 +474,17 @@ func serveWebSocket(ginServer *gin.Engine) {
}
}
+ // REF: https://github.com/siyuan-note/siyuan/issues/11364
+ if !authOk {
+ if token := model.ParseXAuthToken(s.Request); token != nil {
+ authOk = token.Valid && model.IsValidRole(model.GetClaimRole(model.GetTokenClaims(token)), []model.Role{
+ model.RoleAdministrator,
+ model.RoleEditor,
+ model.RoleReader,
+ })
+ }
+ }
+
if !authOk {
// 用于授权页保持连接,避免非常驻内存内核自动退出 https://github.com/siyuan-note/insider/issues/1099
authOk = strings.Contains(s.Request.RequestURI, "/ws?app=siyuan&id=auth")
@@ -516,12 +550,24 @@ func serveWebSocket(ginServer *gin.Engine) {
s.Write(result.Bytes())
return
}
- if util.ReadOnly && !command.IsRead() {
- result := util.NewResult()
- result.Code = -1
- result.Msg = model.Conf.Language(34)
- s.Write(result.Bytes())
- return
+ if !command.IsRead() {
+ readonly := util.ReadOnly
+ if !readonly {
+ if token := model.ParseXAuthToken(s.Request); token != nil {
+ readonly = token.Valid && model.IsValidRole(model.GetClaimRole(model.GetTokenClaims(token)), []model.Role{
+ model.RoleReader,
+ model.RoleVisitor,
+ })
+ }
+ }
+
+ if readonly {
+ result := util.NewResult()
+ result.Code = -1
+ result.Msg = model.Conf.Language(34)
+ s.Write(result.Bytes())
+ return
+ }
}
end := time.Now()
@@ -564,3 +610,30 @@ func corsMiddleware() gin.HandlerFunc {
c.Next()
}
}
+
+// jwtMiddleware is a middleware to check jwt token
+// REF: https://github.com/siyuan-note/siyuan/issues/11364
+func jwtMiddleware(c *gin.Context) {
+ if token := model.ParseXAuthToken(c.Request); token != nil {
+ // c.Request.Header.Del(model.XAuthTokenKey)
+ if token.Valid {
+ claims := model.GetTokenClaims(token)
+ c.Set(model.ClaimsContextKey, claims)
+ c.Set(model.RoleContextKey, model.GetClaimRole(claims))
+ c.Next()
+ return
+ }
+ }
+ c.Set(model.RoleContextKey, model.RoleVisitor)
+ c.Next()
+ return
+}
+
+func serveFixedStaticFiles(ginServer *gin.Engine) {
+ ginServer.StaticFile("favicon.ico", filepath.Join(util.WorkingDir, "stage", "icon.png"))
+
+ ginServer.StaticFile("manifest.json", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest"))
+ ginServer.StaticFile("manifest.webmanifest", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest"))
+
+ ginServer.StaticFile("service-worker.js", filepath.Join(util.WorkingDir, "stage", "service-worker.js"))
+}
diff --git a/kernel/sql/asset.go b/kernel/sql/asset.go
index 66d619d55f7..049be5d2937 100644
--- a/kernel/sql/asset.go
+++ b/kernel/sql/asset.go
@@ -18,10 +18,11 @@ package sql
import (
"database/sql"
- "github.com/siyuan-note/filelock"
"path/filepath"
"strings"
+ "github.com/siyuan-note/filelock"
+
"github.com/88250/lute/ast"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/treenode"
diff --git a/kernel/util/net.go b/kernel/util/net.go
index 450b227dc50..1b51b936c99 100644
--- a/kernel/util/net.go
+++ b/kernel/util/net.go
@@ -20,6 +20,7 @@ import (
"net"
"net/http"
"net/url"
+ "strconv"
"strings"
"time"
@@ -90,6 +91,19 @@ func IsOnline(checkURL string, skipTlsVerify bool) bool {
return false
}
+func IsPortOpen(port string) bool {
+ timeout := time.Second
+ conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout)
+ if nil != err {
+ return false
+ }
+ if nil != conn {
+ conn.Close()
+ return true
+ }
+ return false
+}
+
func isOnline(checkURL string, skipTlsVerify bool) (ret bool) {
c := req.C().SetTimeout(3 * time.Second)
if skipTlsVerify {
@@ -168,3 +182,12 @@ func initHttpClient() {
http.DefaultClient = httpclient.GetCloudFileClient2Min()
http.DefaultTransport = httpclient.NewTransport(false)
}
+
+func ParsePort(portString string) (uint16, error) {
+ if port, err := strconv.ParseUint(portString, 10, 16); err != nil {
+ logging.LogErrorf("parse port [%s] failed: %s", portString, err)
+ return 0, err
+ } else {
+ return uint16(port), nil
+ }
+}
diff --git a/kernel/util/path.go b/kernel/util/path.go
index 3561c8f1af0..6d0848f84d0 100644
--- a/kernel/util/path.go
+++ b/kernel/util/path.go
@@ -269,6 +269,7 @@ func IsDisplayableAsset(p string) bool {
func GetAbsPathInWorkspace(relPath string) (string, error) {
absPath := filepath.Join(WorkspaceDir, relPath)
+ absPath = filepath.Clean(absPath)
if WorkspaceDir == absPath {
return absPath, nil
}
diff --git a/kernel/util/working.go b/kernel/util/working.go
index 14f0e5114e6..54f15a0b47b 100644
--- a/kernel/util/working.go
+++ b/kernel/util/working.go
@@ -22,6 +22,7 @@ import (
"fmt"
"math/rand"
"mime"
+ "net/url"
"os"
"path/filepath"
"runtime"
@@ -341,7 +342,9 @@ func WriteWorkspacePaths(workspacePaths []string) (err error) {
}
var (
- ServerPort = "0" // HTTP/WebSocket 端口,0 为使用随机端口
+ ServerURL *url.URL // 内核服务 URL
+ ServerPort = "0" // HTTP/WebSocket 端口,0 为使用随机端口
+
ReadOnly bool
AccessAuthCode string
Lang = ""