2022-05-26 15:18:53 +08:00
<!DOCTYPE html>
< html >
< head >
< title > SiYuan< / title >
< meta charset = "UTF-8" >
< style >
body {
margin: 0;
background-color: #fff;
text-align: center;
color: #202124;
user-select: none;
font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", "Hiragino Sans GB", "Microsoft Yahei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", "EmojiSymbols";
}
.fn__flex-column {
display: flex;
flex-direction: column;
width: 100%;
margin: 0 auto;
border: 1px solid rgba(0, 0, 0, .06);
min-height: 100vh;
box-sizing: border-box;
2023-06-26 19:58:18 +08:00
padding: 0 calc((100vw - 608px) * 0.3);
2022-05-26 15:18:53 +08:00
}
.fn__flex-1 {
flex: 1;
min-height: 16px;
flex-shrink: 0;
}
h2 {
2023-06-26 19:58:18 +08:00
margin: 0 0 12px 0;
2022-05-26 15:18:53 +08:00
line-height: 24px;
}
.b3-select {
border: 1px solid rgba(0, 0, 0, .06);
2023-06-28 22:40:34 +08:00
border-radius: 6px;
2022-05-26 15:18:53 +08:00
padding: 4px 8px;
line-height: 20px;
box-sizing: border-box;
color: #202124;
transition: box-shadow 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
background-color: #fff;
height: 28px;
font-size: 14px;
width: 96px;
}
.b3-select:focus {
border: 1px solid #218bff;
outline: none;
}
.b3-button {
cursor: pointer;
color: #fff;
2023-06-28 22:40:34 +08:00
border-radius: 6px;
2022-05-26 15:18:53 +08:00
line-height: 20px;
padding: 4px 8px;
background-color: #218bff;
white-space: nowrap;
display: inline-flex;
align-items: center;
justify-content: center;
transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
border: 0;
box-sizing: border-box;
text-align: center;
width: 96px;
}
.b3-button:hover, .b3-button:focus {
text-decoration: none;
background-color: #0969da;
box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12)
}
.b3-button:active {
box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12)
}
.b3-label {
padding: 12px 24px;
line-height: 20px;
display: flex;
text-align: left;
border-bottom: 1px solid rgba(0, 0, 0, .06);
}
.b3-label__text {
color: #5f6368;
font-size: 14px;
margin-top: 4px;
}
.fn__space {
width: 8px;
flex-shrink: 0;
}
.fn__flex-center {
align-self: center;
}
.svg {
position: fixed;
2024-02-05 12:25:12 +08:00
right: 32px;
2022-05-26 15:18:53 +08:00
top: 0;
2024-02-05 12:25:12 +08:00
width: 13px;
2022-05-26 15:18:53 +08:00
fill: #5f6368;
2024-02-05 12:25:12 +08:00
padding: 9.5px;
2022-05-26 15:18:53 +08:00
cursor: pointer;
z-index: 1;
}
.svg:hover {
background: #dfe0e1;
fill: #202124;
}
#close {
right: 0;
2024-02-05 12:25:12 +08:00
width: 10px;
padding: 11px 11px;
2022-05-26 15:18:53 +08:00
}
#close:hover {
background-color: #d23f31;
fill: #fff;
}
a {
text-decoration: none;
color: #218bff;
}
a:hover {
text-decoration: underline;
color: #0969da;
}
.fn__none {
display: none !important;
}
.slogan {
color: #5f6368;
font-size: 15px;
}
2022-09-17 23:17:07 +08:00
.notice {
font-size: 14px;
color: #5f6368;
}
2022-05-26 15:18:53 +08:00
.icon {
width: 88px;
margin: 0 auto
}
kbd {
padding: 2px 4px;
font: 75% Consolas, "Liberation Mono", Menlo, Courier, monospace;
line-height: 1;
color: #5f6368;
vertical-align: middle;
background-color: #f3f3f3;
border: solid 1px rgba(0, 0, 0, .06);
2023-06-28 22:40:34 +08:00
border-radius: 6px;
2022-05-26 15:18:53 +08:00
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .06);
}
.feedback {
display: flex;
justify-content: space-around;
font-size: 14px
}
.drag {
-webkit-app-region: drag;
height: 22px;
cursor: pointer;
position: fixed;
top: 0;
left: 0;
right: 44px;
}
< / style >
< / head >
< body >
< div id = "zhCN" class = "fn__flex-column" >
< div class = "fn__flex-1" > < / div >
< img class = "icon" >
< h2 > 思源笔记< / h2 >
2023-06-24 20:39:55 +08:00
< div class = "slogan" > 重构你的思维< / div >
2022-05-26 15:18:53 +08:00
< div class = "fn__flex-1" > < / div >
< label class = "b3-label" >
< div >
🌐 外观语言
2023-06-26 19:58:18 +08:00
< div class = "b3-label__text" > 用户界面语言,后续可以在 < kbd > 设置< / kbd > - < kbd > 外观< / kbd > 中进行切换< / div >
2022-05-26 15:18:53 +08:00
< / div >
< div class = "fn__space" > < / div >
< div class = "fn__flex-1" > < / div >
< select class = "b3-select lang" >
2025-06-15 10:28:22 +08:00
< option value = "ar_SA" > العربية< / option >
< option value = "de_DE" > Deutsch< / option >
2022-12-07 11:01:29 +08:00
< option value = "en_US" > English< / option >
< option value = "es_ES" > Español< / option >
< option value = "fr_FR" > Français< / option >
2024-10-07 05:20:35 +01:00
< option value = "he_IL" > עברית< / option >
2025-06-15 10:28:22 +08:00
< option value = "it_IT" > Italiano< / option >
< option value = "ja_JP" > 日本語< / option >
2025-12-06 21:19:58 +09:00
< option value = "ko_KR" > 한국어< / option >
2024-10-07 05:20:35 +01:00
< option value = "pl_PL" > Polski< / option >
2025-06-15 10:28:22 +08:00
< option value = "ru_RU" > Русский< / option >
< option value = "zh_CHT" > 繁體中文< / option >
< option value = "zh_CN" > 简体中文< / option >
2022-05-26 15:18:53 +08:00
< / select >
< / label >
< label class = "b3-label" >
< div >
🗂️ 工作空间
2023-06-26 19:58:18 +08:00
< div class = "b3-label__text" > 工作空间用于存放数据,后续可以在顶栏左上角的主菜单中进行切换< / div >
2022-05-26 15:18:53 +08:00
< / div >
< div class = "fn__space" > < / div >
< div class = "fn__flex-1" > < / div >
< button class = "b3-button fn__flex-center choosePath" > 选择< / button >
< / label >
< div class = "fn__flex-1" > < / div >
2022-09-17 23:17:07 +08:00
< div class = "fn__flex-1 notice" >
< span > ⚠️ 请勿使用第三方同步盘同步数据,否则会导致运行异常和数据损坏< / span >
< / div >
2022-05-26 15:18:53 +08:00
< div class = "feedback" >
< span > ❤️ < a href = "https://ld246.com/article/1649901726096" target = "_blank" > 求助反馈建议< / a > < / span >
< span > 🏘️ < a href = "https://ld246.com/article/1640266171309" target = "_blank" > 用户社区汇总< / a > < / span >
2023-06-26 19:58:18 +08:00
< span > 🔖 < a href = "https://b3log.org/siyuan/download.html" target = "_blank" class = "version" > < / a > < / span >
2022-05-26 15:18:53 +08:00
< / div >
< div class = "fn__flex-1" > < / div >
< / div >
< div class = "fn__none fn__flex-column" id = "enUS" >
< div class = "fn__flex-1" > < / div >
< img class = "icon" >
< h2 > SiYuan< / h2 >
2023-06-24 20:39:55 +08:00
< div class = "slogan" > Refactor your thinking< / div >
2022-05-26 15:18:53 +08:00
< div class = "fn__flex-1" > < / div >
< label class = "b3-label" >
< div >
🌐 Language
2023-08-01 22:09:25 +08:00
< div class = "b3-label__text" > User interface language, which can be switched later in < kbd > Settings< / kbd > -
< kbd > Appearance< / kbd >
2022-05-26 15:18:53 +08:00
< / div >
< / div >
< div class = "fn__space" > < / div >
< div class = "fn__flex-1" > < / div >
< select class = "b3-select lang" >
2025-06-15 10:28:22 +08:00
< option value = "ar_SA" > العربية< / option >
< option value = "de_DE" > Deutsch< / option >
2022-12-07 11:01:29 +08:00
< option value = "en_US" > English< / option >
< option value = "es_ES" > Español< / option >
< option value = "fr_FR" > Français< / option >
2024-10-07 05:20:35 +01:00
< option value = "he_IL" > עברית< / option >
2025-06-15 10:28:22 +08:00
< option value = "it_IT" > Italiano< / option >
< option value = "ja_JP" > 日本語< / option >
2024-10-07 05:20:35 +01:00
< option value = "pl_PL" > Polski< / option >
2025-06-15 10:28:22 +08:00
< option value = "ru_RU" > Русский< / option >
< option value = "zh_CHT" > 繁體中文< / option >
< option value = "zh_CN" > 简体中文< / option >
2022-05-26 15:18:53 +08:00
< / select >
< / label >
< label class = "b3-label" >
< div >
🗂️ Workspace
2023-08-01 22:09:25 +08:00
< div class = "b3-label__text" > The workspace is used to store data, which can be switched later in the top bar
menu later
2022-05-26 15:18:53 +08:00
< / div >
< / div >
< div class = "fn__space" > < / div >
< div class = "fn__flex-1" > < / div >
< button class = "b3-button fn__flex-center choosePath" > Select< / button >
< / label >
< div class = "fn__flex-1" > < / div >
2022-09-17 23:17:07 +08:00
< div class = "fn__flex-1 notice" >
2023-06-26 19:58:18 +08:00
< span > ⚠️ Do not use a third-party sync disk to sync data, otherwise it will cause abnormal operation and data damage< / span >
2022-09-17 23:17:07 +08:00
< / div >
2022-05-26 15:18:53 +08:00
< div class = "feedback" >
2023-06-26 19:58:18 +08:00
< span > ❤️ < a href = "https://liuyun.io/article/1686530886208" target = "_blank" > Support and Feedback< / a > < / span >
< span > 🏘️ < a href = "https://liuyun.io/article/1687779743723" target = "_blank" > User community summary< / a > < / span >
< span > 🔖 < a href = "https://b3log.org/siyuan/en/download.html" target = "_blank" class = "version" > < / a > < / span >
2022-05-26 15:18:53 +08:00
< / div >
< div class = "fn__flex-1" > < / div >
< / div >
< svg class = "svg" id = "min" viewBox = "0 0 32 32" >
< path d = "M1.333 14.667h29.333q1.333 0 1.333 1.333v0q0 1.333-1.333 1.333h-29.333q-1.333 0-1.333-1.333v0q0-1.333 1.333-1.333z" > < / path >
< / svg >
< svg class = "svg" id = "close" viewBox = "0 0 32 32" >
< path d = "M32 3.221l-12.779 12.779 12.779 12.779-3.221 3.221-12.779-12.779-12.779 12.779-3.221-3.221 12.779-12.779-12.779-12.779 3.221-3.221 12.779 12.779 12.779-12.779z" > < / path >
< / svg >
< div class = "drag" > < / div >
< script >
2023-06-26 19:58:18 +08:00
const isChinese = (lang) => {
return 'zh_CN' === lang || 'zh_CHT' === lang
}
2023-03-18 12:01:43 +08:00
const getSearch = (key) => {
if (window.location.search.indexOf('?') === -1) {
return ''
}
let value = ''
const data = window.location.search.split('?')[1].split('& ')
data.find(item => {
const keyValue = item.split('=')
if (keyValue[0] === key) {
value = keyValue[1]
return true
}
})
return value
2022-05-26 15:18:53 +08:00
}
2023-03-18 12:01:43 +08:00
let currentLang = decodeURIComponent(getSearch('lang'))
const setLang = (lang) => {
2023-06-26 19:58:18 +08:00
if (isChinese(lang)) {
2023-03-18 12:01:43 +08:00
document.title = `思源笔记 v${getSearch('v')}`
document.getElementById('zhCN').classList.remove('fn__none')
document.getElementById('enUS').classList.add('fn__none')
} else {
document.title = `SiYuan v${getSearch('v')}`
document.getElementById('zhCN').classList.add('fn__none')
document.getElementById('enUS').classList.remove('fn__none')
}
currentLang = lang
2022-05-26 15:18:53 +08:00
}
2023-03-18 12:01:43 +08:00
setLang(currentLang)
document.querySelectorAll('.version').forEach(item => {
2023-06-26 19:58:18 +08:00
item.textContent = `v${getSearch('v')}`
2022-05-26 15:18:53 +08:00
})
2023-03-18 12:01:43 +08:00
document.querySelectorAll('.icon').forEach(item => {
item.src = decodeURIComponent(`${getSearch('icon')}`)
2022-05-26 15:18:53 +08:00
})
2023-03-17 11:08:16 +08:00
2023-03-18 12:01:43 +08:00
document.querySelectorAll('.lang').forEach(item => {
item.value = currentLang
item.addEventListener('change', () => {
document.querySelectorAll('.lang').forEach(item1 => {
item1.value = item.value
})
setLang(item.value)
})
})
document.getElementById('close').addEventListener('click', () => {
const {ipcRenderer} = require('electron')
ipcRenderer.send('siyuan-first-quit')
})
document.getElementById('min').addEventListener('click', () => {
2023-10-09 01:24:30 +08:00
const {ipcRenderer} = require('electron')
ipcRenderer.send("siyuan-cmd", "minimize");
2023-03-18 12:01:43 +08:00
})
document.querySelectorAll('.choosePath').forEach((item) => {
2023-10-09 01:24:30 +08:00
item.addEventListener('click', async () => {
2023-03-18 12:01:43 +08:00
const path = require('path')
2023-08-01 22:08:23 +08:00
const fs = require('fs')
2023-08-01 22:09:25 +08:00
2023-08-01 22:08:23 +08:00
const defaultWorkspace = path.join(decodeURIComponent(getSearch('home')), "SiYuan")
if (!fs.existsSync(defaultWorkspace)) {
fs.mkdirSync(defaultWorkspace, {mode: 0o755, recursive: true})
}
2023-08-01 22:09:25 +08:00
2023-10-09 01:24:30 +08:00
const {ipcRenderer} = require('electron')
2023-10-09 10:26:26 +08:00
const result = await ipcRenderer.invoke("siyuan-get", {
2023-10-09 01:24:30 +08:00
cmd: "showOpenDialog",
2023-08-01 22:08:23 +08:00
defaultPath: defaultWorkspace,
2023-03-18 12:01:43 +08:00
properties: ['openDirectory', 'createDirectory'],
2023-10-09 01:24:30 +08:00
});
2023-03-18 12:01:43 +08:00
2023-10-09 01:24:30 +08:00
if (result.canceled) {
return
}
const initPath = result.filePaths[0]
2023-03-18 12:01:43 +08:00
2025-09-29 17:04:03 +08:00
if (isPartitionRootPath(initPath)) {
let msg = '⚠️ Do not create the workspace in the partition root path, please create a new folder as the workspace'
if (isChinese(currentLang)) {
msg = '⚠️ 请勿在分区根路径上创建工作空间,请新建一个文件夹作为工作空间'
}
alert(msg)
return
}
if (!isWorkspaceDir(initPath) & & !isEmptyDir(initPath)) {
let msg = '⚠️ This folder contains other files, please create a new folder as the workspace'
if (isChinese(currentLang)) {
msg = '⚠️ 该文件夹包含了其他文件,请新建一个文件夹作为工作空间'
}
alert(msg)
return
}
2023-10-09 01:24:30 +08:00
if (isICloudPath(initPath)) {
let msg = '⚠️ This folder is under the iCloud sync path, please change another path'
2023-06-26 19:58:18 +08:00
if (isChinese(currentLang)) {
2023-10-09 01:24:30 +08:00
msg = '⚠️ 该文件夹位于 iCloud 同步路径下,请更换其他路径'
2023-03-18 12:01:43 +08:00
}
2023-10-09 01:24:30 +08:00
alert(msg)
return
}
2023-03-18 12:01:43 +08:00
2023-10-09 01:24:30 +08:00
if (isCloudDrivePath(initPath)) {
let msg = '⚠️ The folder path can not contain onedrive, dropbox, google drive, pcloud and 坚果云, please change another path'
if (isChinese(currentLang)) {
msg = '⚠️ 文件夹路径不能包含 onedrive、dropbox、google drive、pcloud 和 坚果云,请更换其他路径'
2023-03-18 12:01:43 +08:00
}
2023-10-09 01:24:30 +08:00
alert(msg)
return
}
let msg = '⚠️ Do not set the workspace under the path of a third-party sync disk, otherwise the data will be damaged (iCloud/OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.), continue?'
if (isChinese(currentLang)) {
msg = '⚠️ 请勿将工作空间设置在第三方同步盘路径下, 否则数据会被损坏( iCloud/OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等),是否继续?'
}
if (!confirm(msg)) {
return
}
if (!fs.existsSync(initPath)) {
fs.mkdirSync(initPath, {mode: 0o755, recursive: true})
}
ipcRenderer.send('siyuan-first-init', {
workspace: initPath,
lang: document.querySelector('.lang').value
2023-03-18 12:01:43 +08:00
})
})
})
2025-09-29 17:04:03 +08:00
const isPartitionRootPath = (absPath) => {
const path = require('path')
const parsedPath = path.parse(absPath)
return parsedPath.root === absPath
}
const isEmptyDir = (absPath) => {
const fs = require('fs')
let files;
try {
files = fs.readdirSync(absPath)
} catch (err) {
return false
}
return 0 === files.length
}
const isWorkspaceDir = (absPath) => {
const path = require('path');
const fs = require('fs');
const conf = path.join(absPath, 'conf', 'conf.json');
let data;
try {
data = fs.readFileSync(conf, 'utf8');
} catch (err) {
return false;
}
return data.includes('kernelVersion');
}
2023-03-18 12:01:43 +08:00
const isCloudDrivePath = (absPath) => {
const absPathLower = absPath.toLowerCase()
return -1 < absPathLower.indexOf ( " onedrive " ) | | -1 < absPathLower . indexOf ( " dropbox " ) | |
2023-03-25 15:23:35 +08:00
-1 < absPathLower.indexOf ( " google drive " ) | | -1 < absPathLower . indexOf ( " pcloud " ) | |
-1 < absPathLower.indexOf ( " 坚果云 " )
2023-03-18 12:01:43 +08:00
}
2023-03-23 09:51:22 +08:00
const isICloudPath = (absPath) => {
2023-03-23 09:52:49 +08:00
const os = require('os')
if ('darwin' !== os.platform()) {
2023-03-23 09:51:22 +08:00
return false
}
// macOS 端对工作空间放置在 iCloud 路径下做检查 https://github.com/siyuan-note/siyuan/issues/7747
const path = require('path')
2023-08-29 21:05:01 +08:00
const homePath = decodeURIComponent(getSearch('home'))
2023-06-24 11:05:33 +08:00
const absPathLower = absPath.toLowerCase()
2023-08-29 21:01:36 +08:00
const iCloudRoot = path.join(homePath, 'Library', 'Mobile Documents')
2023-08-29 21:05:01 +08:00
if (!simpleCheckIcloudPath(absPath, homePath)) {
// 简单判断无法通过则复杂验证
2023-08-29 21:01:36 +08:00
const allFiles = walk(iCloudRoot)
for (const file of allFiles) {
if (-1 < absPathLower.indexOf ( file . toLowerCase ( ) ) ) {
return true
}
2023-03-23 09:51:22 +08:00
}
2023-08-29 21:05:01 +08:00
}
2023-03-23 09:51:22 +08:00
return false
}
2023-08-29 21:05:01 +08:00
// 简单判断 iCloud 同步目录
// 不允许 为桌面 文档 和 iCloud 文件夹 和软链接
const simpleCheckIcloudPath = (absPath, homePath) => {
2023-08-29 21:01:36 +08:00
const fs = require('fs')
let stat = fs.lstatSync(absPath)
2023-08-29 21:05:01 +08:00
if (stat.isSymbolicLink()) {
2023-08-29 21:01:36 +08:00
return false
}
2023-08-29 21:05:01 +08:00
2023-08-29 21:01:36 +08:00
const path = require('path')
const absPathLower = absPath.toLowerCase()
const iCloudRoot = path.join(homePath, 'Library', 'Mobile Documents')
2023-08-29 21:05:01 +08:00
if (absPathLower.startsWith(iCloudRoot.toLowerCase())) {
2023-08-29 21:01:36 +08:00
return false
}
2023-08-29 21:05:01 +08:00
2023-08-29 21:01:36 +08:00
const documentsRoot = path.join(homePath, 'Documents')
if (absPathLower.startsWith(documentsRoot.toLowerCase())) {
return false
}
2023-08-29 21:05:01 +08:00
2023-08-29 21:01:36 +08:00
const desktopRoot = path.join(homePath, 'Desktop')
if (absPathLower.startsWith(desktopRoot.toLowerCase())) {
return false
}
2023-08-29 21:05:01 +08:00
return true
2023-08-29 21:01:36 +08:00
}
2023-08-29 21:05:01 +08:00
2023-03-23 09:51:22 +08:00
const walk = (dir, files = []) => {
2023-04-05 14:51:00 +08:00
let dirFiles;
2023-03-23 09:51:22 +08:00
const fs = require('fs')
2023-04-05 14:43:38 +08:00
try {
if (!fs.existsSync(dir)) {
console.log("dir [" + dir + "] not exists")
return files
}
dirFiles = fs.readdirSync(dir)
} catch (e) {
console.error("read dir [" + dir + "] failed: ", e)
return files
}
2023-03-23 09:54:23 +08:00
const path = require('path')
2023-03-23 09:51:22 +08:00
for (const f of dirFiles) {
2023-03-23 10:16:40 +08:00
let stat = fs.lstatSync(dir + path.sep + f)
if (stat.isSymbolicLink()) {
files.push(fs.readlinkSync(dir + path.sep + f))
continue
}
2023-03-23 09:51:22 +08:00
if (stat.isDirectory()) {
2023-03-23 09:54:23 +08:00
// 如果已经遍历过则不再遍历
if (files.includes(dir + path.sep + f)) {
continue
}
2023-03-23 09:51:22 +08:00
files.push(dir + path.sep + f)
2023-08-29 21:01:36 +08:00
walk(dir + path.sep + f, files)
2023-03-23 09:51:22 +08:00
}
}
return files
}
2022-05-26 15:18:53 +08:00
< / script >
< / body >
< / html >