Commit c6bdca60 authored by ransome1's avatar ransome1
Browse files

Added cluster based rendering of todos to improve performance

parent 7b7cb513
...@@ -95,27 +95,10 @@ jobs: ...@@ -95,27 +95,10 @@ jobs:
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v1
# mirror:
# needs: codeql
# name: Mirror code to opencode.net
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - name: Mirror + trigger CI
# uses: SvanBoxel/gitlab-mirror-and-ci-action@master
# with:
# args: "https://www.opencode.net/ransome/sleek.git"
# env:
# GITLAB_HOSTNAME: "opencode.net"
# GITLAB_USERNAME: "ransome"
# GITLAB_PASSWORD: ${{ secrets.gitlab_password }}
# GITLAB_PROJECT_ID: "https://www.opencode.net/ransome/sleek/edit"
# GITHUB_TOKEN: ${{ secrets.github_token }}
mirror: mirror:
name: Mirror code to opencode.net name: Mirror code to opencode.net
runs-on: ubuntu-latest runs-on: ubuntu-latest
# needs: codeql needs: codeql
steps: # <-- must use actions/checkout@v1 before mirroring! steps: # <-- must use actions/checkout@v1 before mirroring!
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: pixta-dev/repository-mirroring-action@v1 - uses: pixta-dev/repository-mirroring-action@v1
...@@ -124,21 +107,3 @@ jobs: ...@@ -124,21 +107,3 @@ jobs:
git@www.opencode.net:ransome/sleek.git git@www.opencode.net:ransome/sleek.git
ssh_private_key: # <-- use 'secrets' to pass credential information. ssh_private_key: # <-- use 'secrets' to pass credential information.
${{ secrets.GITLAB_SSH_PRIVATE_KEY }} ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
# publish-aur-package:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
#
# - name: Publish AUR package
# uses: KSXGitHub/github-actions-deploy-aur@v2
# with:
# pkgname: sleek
# pkgbuild: ./PKGBUILD
# commit_username: ${{ secrets.AUR_USERNAME }}
# commit_email: ${{ secrets.AUR_EMAIL }}
# ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
# commit_message: Update to latest sleek release
{ {
"name": "sleek", "name": "sleek",
"productName": "sleek", "productName": "sleek",
"version": "1.0.5-3", "version": "1.0.5-4",
"description": "Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source", "description": "Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source",
"synopsis": "Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source", "synopsis": "Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source",
"category": "ProjectManagement", "category": "ProjectManagement",
...@@ -53,7 +53,10 @@ ...@@ -53,7 +53,10 @@
"mac": { "mac": {
"target": { "target": {
"target": "default", "target": "default",
"arch": ["arm64", "x64"] "arch": [
"arm64",
"x64"
]
}, },
"icon": "assets/icons/sleek.icns", "icon": "assets/icons/sleek.icns",
"category": "public.app-category.productivity" "category": "public.app-category.productivity"
......
...@@ -11,7 +11,7 @@ const i18nextOptions = { ...@@ -11,7 +11,7 @@ const i18nextOptions = {
}, },
namespace: "translation", namespace: "translation",
defaultNS: "translation", defaultNS: "translation",
supportedLngs: ["de", "en", "it", "es", "fr"], supportedLngs: ["de", "en", "it", "es", "fr", "cn"],
debug: false, debug: false,
preload: fs.readdirSync(path.join(__dirname, "../locales")).filter((fileName) => { preload: fs.readdirSync(path.join(__dirname, "../locales")).filter((fileName) => {
const joinedPath = path.join(path.join(__dirname, "../locales"), fileName) const joinedPath = path.join(path.join(__dirname, "../locales"), fileName)
......
...@@ -15,8 +15,7 @@ html { ...@@ -15,8 +15,7 @@ html {
} }
body { body {
font-family: "FreeSans"; font: 16px "FreeSans", sans-serif;
font-size: 16px;
height: 100%; height: 100%;
margin: auto; margin: auto;
-webkit-user-select: none; -webkit-user-select: none;
...@@ -157,7 +156,7 @@ svg { ...@@ -157,7 +156,7 @@ svg {
code, pre { code, pre {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace !important; font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace !important;
background-color: transparent !important; background-color: #ebebeb;
color: inherit !important; color: inherit !important;
} }
...@@ -1058,7 +1057,7 @@ nav ul:nth-child(2) { ...@@ -1058,7 +1057,7 @@ nav ul:nth-child(2) {
padding-right: 3em; padding-right: 3em;
} }
.modal.content .modal-card-body table.settings tr td:last-child { .modal.content .modal-card-body table.settings tr td:last-child {
min-width: 10em; min-width: 12em;
text-align: center; text-align: center;
} }
.modal.content .modal-card-body table.shortcuts td .tag { .modal.content .modal-card-body table.shortcuts td .tag {
......
This diff is collapsed.
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
</div> </div>
</nav> </nav>
</div> </div>
<div class="column content"> <div id="todoTableWrapper" class="column content">
<div id="todoTableSearchContainer" class="control has-icons-left"> <div id="todoTableSearchContainer" class="control has-icons-left">
<div class="column"> <div class="column">
<input id="todoTableSearch" class="input is-medium" type="search" placeholder="Search" tabindex="10"> <input id="todoTableSearch" class="input is-medium" type="search" placeholder="Search" tabindex="10">
......
"use strict"; "use strict";
import { userData, appData, handleError, translations, setUserData, _paq } from "../render.js"; import { userData, appData, handleError, translations, setUserData, _paq, startBuilding } from "../render.js";
import { RecExtension } from "./todotxtExtensions.mjs"; import { RecExtension } from "./todotxtExtensions.mjs";
import { categories } from "./filters.mjs"; import { categories } from "./filters.mjs";
import { generateRecurrence } from "./recurrences.mjs"; import { generateRecurrence } from "./recurrences.mjs";
import { convertDate, isToday, isTomorrow, isPast } from "./date.mjs"; import { convertDate, isToday, isTomorrow, isPast } from "./date.mjs";
import { show } from "./form.mjs"; import { show } from "./form.mjs";
const modalForm = document.getElementById("modalForm"); const modalForm = document.getElementById("modalForm");
const todoTableWrapper = document.getElementById("todoTableWrapper");
const todoTableContainer = document.getElementById("todoTableContainer"); const todoTableContainer = document.getElementById("todoTableContainer");
// ######################################################################################################################## // ########################################################################################################################
// CONFIGURE MARKDOWN PARSER // CONFIGURE MARKDOWN PARSER
...@@ -44,8 +45,67 @@ const todoTableBodyCellSpacerTemplate = document.createElement("div"); ...@@ -44,8 +45,67 @@ const todoTableBodyCellSpacerTemplate = document.createElement("div");
const todoTableBodyCellDueDateTemplate = document.createElement("span"); const todoTableBodyCellDueDateTemplate = document.createElement("span");
const todoTableBodyCellRecurrenceTemplate = document.createElement("span"); const todoTableBodyCellRecurrenceTemplate = document.createElement("span");
const item = { previous: "" } const item = { previous: "" }
let items; let
let visibleRows; items,
clusterCounter,
clusterSize = Math.ceil(window.innerHeight/35), // 35 being the pixel height of one todo in compact mode
clusterThreshold = 0,
stopBuilding = false,
visibleRows = 0;
todoTableWrapper.addEventListener("scroll", function(event) {
if((event.target.scrollHeight - event.target.scrollTop === event.target.clientHeight) && visibleRows<items.filtered.length) {
stopBuilding = false;
startBuilding(null, true);
}
});
function configureTodoTableTemplate(append) {
try {
if(!append) {
todoTableContainer.innerHTML = "";
visibleRows = 0;
clusterThreshold = 0;
stopBuilding = false;
}
todoTableBodyCellMoreTemplate.setAttribute("class", "flex-row todoTableItemMore");
todoTableBodyCellMoreTemplate.setAttribute("role", "cell");
todoTableBodyCellMoreTemplate.innerHTML = `
<div class="dropdown is-right">
<div class="dropdown-trigger">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item">` + translations.useAsTemplate + `</a>
<a href="#" class="dropdown-item">` + translations.edit + `</a>
<a class="dropdown-item">` + translations.delete + `</a>
</div>
</div>
</div>
`;
todoTableBodyRowTemplate.setAttribute("role", "rowgroup");
todoTableBodyRowTemplate.setAttribute("class", "flex-table");
todoTableBodyCellCheckboxTemplate.setAttribute("class", "flex-row checkbox");
todoTableBodyCellCheckboxTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("class", "flex-row text");
todoTableBodyCellTextTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("tabindex", 0);
todoTableBodyCellTextTemplate.setAttribute("href", "#");
todoTableBodyCellTextTemplate.setAttribute("title", translations.editTodo);
tableContainerCategoriesTemplate.setAttribute("class", "categories");
todoTableBodyCellPriorityTemplate.setAttribute("role", "cell");
todoTableBodyCellSpacerTemplate.setAttribute("role", "cell");
todoTableBodyCellDueDateTemplate.setAttribute("class", "flex-row itemDueDate");
todoTableBodyCellDueDateTemplate.setAttribute("role", "cell");
todoTableBodyCellRecurrenceTemplate.setAttribute("class", "flex-row recurrence");
todoTableBodyCellRecurrenceTemplate.setAttribute("role", "cell");
return Promise.resolve("Success: Table templates set up");
} catch(error) {
error.functionName = configureTodoTableTemplate.name;
return Promise.reject(error);
}
}
function generateItems(content) { function generateItems(content) {
try { try {
items = { objects: TodoTxt.parse(content, [ new DueExtension(), new RecExtension(), new HiddenExtension() ]) } items = { objects: TodoTxt.parse(content, [ new DueExtension(), new RecExtension(), new HiddenExtension() ]) }
...@@ -99,42 +159,50 @@ function generateGroups(items) { ...@@ -99,42 +159,50 @@ function generateGroups(items) {
return 0; return 0;
}); });
} }
// sort the items within the groups
items.forEach((group) => {
group[1] = sortTodoData(group[1]);
});
return Promise.resolve(items) return Promise.resolve(items)
} }
function generateTable(groups) { function generateTable(groups, append) {
// prepare the templates for the table // prepare the templates for the table
return configureTodoTableTemplate().then(function(response) { return configureTodoTableTemplate(append).then(function(response) {
clusterCounter = 0;
console.info(response); console.info(response);
visibleRows = 0;
for (let group in groups) { for (let group in groups) {
if(stopBuilding) {
stopBuilding = false;
break;
}
// create a divider row // create a divider row
let dividerRow;
// completed todos // completed todos
if(userData.sortCompletedLast && groups[group][0]==="completed") { if(userData.sortCompletedLast && groups[group][0]==="completed") {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")) dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")
// for priority, context and project // for priority, context and project
} else if(groups[group][0]!="null" && userData.sortBy!="dueString") { } else if(groups[group][0]!="null" && userData.sortBy!="dueString") {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table " + userData.sortBy + " group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button " + groups[group][0] + "\">" + groups[group][0].replace(/,/g, ', ') + "</span></div></div>")) dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table " + userData.sortBy + " group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button " + groups[group][0] + "\">" + groups[group][0].replace(/,/g, ', ') + "</span></div></div>")
// if sorting is by due date // if sorting is by due date
} else if(userData.sortBy==="dueString" && groups[group][1][0].due) { } else if(userData.sortBy==="dueString" && groups[group][1][0].due) {
if(isToday(groups[group][1][0].due)) { if(isToday(groups[group][1][0].due)) {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isToday\" role=\"cell\"><span class=\"button\">" + translations.today + "</span></div></div>")); dividerRow= document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isToday\" role=\"cell\"><span class=\"button\">" + translations.today + "</span></div></div>")
} else if(isTomorrow(groups[group][1][0].due)) { } else if(isTomorrow(groups[group][1][0].due)) {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isTomorrow\" role=\"cell\"><span class=\"button\">" + translations.tomorrow + "</span></div></div>")); dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isTomorrow\" role=\"cell\"><span class=\"button\">" + translations.tomorrow + "</span></div></div>")
} else if(isPast(groups[group][1][0].due)) { } else if(isPast(groups[group][1][0].due)) {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isPast\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>")); dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isPast\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>")
} else { } else {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>")) dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>")
} }
// create an empty divider row // create an empty divider row
} else { } /*else {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")) dividerRow = document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")
} }*/
// sort items within this group // add divider row only if it doesn't exist yet
const sortedGroup = sortTodoData(groups[group][1]); if(!document.getElementById(userData.sortBy + groups[group][0]) && dividerRow) tableContainerContent.appendChild(dividerRow);
//const sortedGroup = groups[group][1]; for (let item in groups[group][1]) {
// build the fragments per group let todo = groups[group][1][item];
for (let item in sortedGroup) {
let todo = sortedGroup[item];
// if this todo is not a recurring one the rec value will be set to null // if this todo is not a recurring one the rec value will be set to null
if(!todo.rec) { if(!todo.rec) {
todo.rec = null; todo.rec = null;
...@@ -163,6 +231,14 @@ function generateTable(groups) { ...@@ -163,6 +231,14 @@ function generateTable(groups) {
}); });
} }
} }
if(clusterCounter<clusterThreshold) {
clusterCounter++;
continue;
} else if((visibleRows===clusterSize+clusterThreshold) || visibleRows===items.filtered.length ) {
clusterThreshold = visibleRows;
stopBuilding = true;
break;
}
tableContainerContent.appendChild(generateTableRow(todo)); tableContainerContent.appendChild(generateTableRow(todo));
} }
// TODO add a catch // TODO add a catch
...@@ -177,6 +253,7 @@ function generateTable(groups) { ...@@ -177,6 +253,7 @@ function generateTable(groups) {
} }
function generateTableRow(todo) { function generateTableRow(todo) {
try { try {
clusterCounter++;
visibleRows++; visibleRows++;
// create nodes from templates // create nodes from templates
let todoTableBodyRow = todoTableBodyRowTemplate.cloneNode(true); let todoTableBodyRow = todoTableBodyRowTemplate.cloneNode(true);
...@@ -494,49 +571,6 @@ function checkIsTodoVisible(todo) { ...@@ -494,49 +571,6 @@ function checkIsTodoVisible(todo) {
} }
return true; return true;
} }
function configureTodoTableTemplate() {
try {
todoTableContainer.innerHTML = "";
todoTableBodyCellMoreTemplate.setAttribute("class", "flex-row todoTableItemMore");
todoTableBodyCellMoreTemplate.setAttribute("role", "cell");
todoTableBodyCellMoreTemplate.innerHTML = `
<div class="dropdown is-right">
<div class="dropdown-trigger">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item">` + translations.useAsTemplate + `</a>
<a href="#" class="dropdown-item">` + translations.edit + `</a>
<a class="dropdown-item">` + translations.delete + `</a>
</div>
</div>
</div>
`;
todoTableBodyRowTemplate.setAttribute("role", "rowgroup");
todoTableBodyRowTemplate.setAttribute("class", "flex-table");
todoTableBodyCellCheckboxTemplate.setAttribute("class", "flex-row checkbox");
todoTableBodyCellCheckboxTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("class", "flex-row text");
todoTableBodyCellTextTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("tabindex", 0);
todoTableBodyCellTextTemplate.setAttribute("href", "#");
todoTableBodyCellTextTemplate.setAttribute("title", translations.editTodo);
tableContainerCategoriesTemplate.setAttribute("class", "categories");
todoTableBodyCellPriorityTemplate.setAttribute("role", "cell");
todoTableBodyCellSpacerTemplate.setAttribute("role", "cell");
todoTableBodyCellDueDateTemplate.setAttribute("class", "flex-row itemDueDate");
todoTableBodyCellDueDateTemplate.setAttribute("role", "cell");
todoTableBodyCellRecurrenceTemplate.setAttribute("class", "flex-row recurrence");
todoTableBodyCellRecurrenceTemplate.setAttribute("role", "cell");
return Promise.resolve("Success: Table templates set up");
} catch(error) {
error.functionName = configureTodoTableTemplate.name;
return Promise.reject(error);
}
}
function generateNotification(todo, offset) { function generateNotification(todo, offset) {
try { try {
let notifications = userData.notifications; let notifications = userData.notifications;
......
{
"addTodo": "新增任务",
"toggleFilter": "显示过滤器",
"openFile": "打开todo.txt",
"toggleDarkMode": "浅色/深色模式",
"viewHeadlineTodoList": "任务列表",
"viewHeadlineAppView": "App视图",
"toggleCompletedTodos": "显示/隐藏已完成任务",
"sortBy": "排序",
"completedTodos": "已完成任务",
"sortCompletedLast": "已完成任务显示在最后",
"hiddenTodos": "隐藏任务",
"compactView": "紧凑视图",
"priority": "优先度",
"dueDate": "任务期限",
"resetFilters": "重置过滤和搜索",
"contexts": "情境",
"projects": "项目",
"visibleTodos": "可见任务",
"of": "的",
"selectedFilters": "选中的过滤器",
"inProgress": "进行中",
"done": "标记为完成",
"editTodo": "编辑任务",
"edit": "编辑",
"copy": "复制",
"cut": "剪切",
"paste": "粘贴",
"delete": "删除",
"useAsTemplate": "保存为模板",
"due": "到期",
"today": "今天",
"tomorrow": "明天",
"dueToday": "今天到期",
"dueTomorrow": "明天到期",
"dueFuture": "之后到期",
"duePast": "过期",
"formErrorWritingFile": "<strong>错误:</strong> 无法写入文件. 请确保todo.txt文件存在且有足够的写入权限",
"formInfoNoInput": "请在文本里添加todo文件, 如果你不清楚如何使用, 请参见<a href=\"https://github.com/todotxt/todo.txt\" target=\"_blank\">todo.txt syntax</a>.",
"formInfoDuplicate": "该任务已经存在, todo.txt文件里不能写入重复任务. ",
"formInfoIncomplete": "你的输入不完整, 请输入任务文本. ",
"formSelectDueDate": "无到期",
"cancel": "取消",
"save": "保存",
"formTodoInputPlaceholder": "(A) 使用 todo.txt 格式 @context +project",
"addTodoContainerHeadline": "没有任务",
"addTodoContainerSubtitle": "列表为空, 来创建一些任务吧",
"welcomeToSleek": "欢迎使用sleek",
"onboardingContainerSubtitle": "首先选择 <strong>现有</strong> todo.txt 文件, 或者创建<strong>新文件</strong>. ",
"createFile": "创建todo.txt",
"onboardingContainerBtnOpen": "选择现有todo.txt",
"windowTitleCreateFile": "创建todo.txt",
"windowButtonCreateFile": "在此处创建todo.txt",
"selectFile": "选择todo.txt文件",
"select": "选择",
"selected": "选中的",
"windowButtonOpenFile": "打开",
"windowFileformat": "文本文件",
"sleekOnGithub": "sleek on Github",
"about": "关于",
"help": "帮助",
"view": "视图",
"todos": "任务",
"file": "文件",
"close": "关闭",
"reload": "重载",
"settings": "设置",
"devTools": "打开/关闭开发者工具",
"search": "(A) 使用 todo.txt 格式搜索 @context +project due:",
"noResults": "无结果",
"noResultContainerSubtitle": "你的搜索或者过滤器无法找到结果",
"clear": "清除",
"find": "搜索",
"every": "每",
"day": "天",
"day_plural": "天",
"daily": "每天",
"week": "周",
"week_plural": "周",
"weekly": "每周",
"month": "月",
"month_plural": "月",
"monthly": "每月",
"year": "年",
"year_plural": "年",
"yearly": "每年",
"noRecurrence": "不重复",
"errorEventLogging": "错误及事件记录(Log)",
"messageLoggingBody": "如果打开匿名的错误/事件报告会有助于未来的开发. 报告里将包括发生的错误和使用的功能. 你可以在设定里打开. ",
"messageShareTitle": "sleek <i class=\"fas fa-heart\"></i> 你",
"messageShareBody": "这个项目欢迎各位的意见<br><i class=\"fas fa-star\"></i>&nbsp;<a href=\"https://sourceforge.net/projects/sleek/reviews\" target=\"_blank\">SourceForge</a>, <i class=\"fab fa-github\"></i>&nbsp;<a href=\"https://github.com/ransome1/sleek/issues\" target=\"_blank\">Github 报告错误</a> 以及推荐",
"language": "语言",
"settingsTabSettingsLanguageBody": "sleek会按照你的计算机设定自动切换语言, 也可以手动更换语言 <strong>sleek会自动重启. 如果没有自动重启请手动打开sleek. </strong>",
"notifications": "提醒",
"settingsTabSettingsNotificationsBody": "sleek可以提醒你今天和明天到期的任务. 请保持sleek运行. ",
"darkmode": "暗色模式",
"settingsTabSettingsDarkmodeBody": "如果sleek界面太明亮不适合你的口味/环境, 不妨切换成暗色模式. ",
"settingsTabSettingsLoggingBody": "如果允许记录匿名错误和事件, 会对开发有帮助. 更多信息请参见' <a href=\"https://github.com/ransome1/sleek/blob/master/PRIVACY.md\" target=\"_blank\">privacy policy</a>.",
"settingsTabAboutContribute": "sleek是开源软件, 你可以一起帮忙改进它",
"settingsTabAboutCopyrightLicense": "版权以及使用许可",
"settingsTabAboutCopyrightLicenseBody": "版权 (c) 2021 Robin Ahle. sleek 遵循 <a href=\"https://opensource.org/licenses/MIT\" target=\"_blank\">MIT license</a>. 请参照完整授权文本 <a href=\"https://github.com/ransome1/sleek/blob/master/LICENSE\" target=\"_blank\">LICENSE</a>",
"settingsTabAboutPrivacy": "隐私政策",
"settingsTabAboutPrivacyBody": "(作者) 无意知道用户身份, 也不会收集不必要的数据. (作者) 希望知道有多少用户在使用sleek, 如果用户允许, 希望知道用户怎样使用sleek. 收集的数据将匿名使用SSL加密链接发送到Matomo实例. 详情请参见<a href=\"https://github.com/ransome1/sleek/blob/master/PRIVACY.md\" target=\"_blank\">Privacy Policy</a>",
"settingsTabAboutExternalLibraries": "本软件中使用到的外部软件",
"settingsTabSettingsArchive": "存档任务",
"settingsTabSettingsArchiveBody": "已完成任务会从当前todo.txt文件移除, 保存到done.txt. 如果还未创建done.txt则会自动创建. ",
"settingsTabSettingsTray": "最小化到系统托盘",
"settingsTabSettingsTrayBody": "选中这个设定会让sleek最小化时隐藏到托盘(而非在任务栏<strong>sleek会自动重启. 如果没有自动重启请手动打开sleek. </strong>",
"archive": "存档",
"shortcuts": "快捷键",
"function": "功能",
"priorities": "优先度",
"helpTab3Title": "情境和项目",
"helpTab4Title": "日期和重复",
"helpTabPrioritiesTitle": "添加优先度",
"helpTabPrioritiesBody": "重要任务要加重显示在列表上. 你可以在任务前加 \"(A)\". 你可以选择 (A) 到 (Z), 但是只有A到C有对应颜色. 其他优先度显示为灰色. 可以在新建/编辑窗口里使用Ctrl+Alt+A〜Z设置优先度",
"helpTabContextsProjectsTitle": "添加情境和项目",
"helpTabContextsProjectsBody": "如果你的项目里有多个任务, 你可以在任务里添加 \"+\" 加上项目名. 情境表示该任务和你相关的情形. 根据David Allen著作<a href=\"https://en.wikipedia.org/wiki/Getting_Things_Done\" target=\"_blank\">Getting Things Done</a>, \"context\" 情境可以是家庭, 职场, 外出购物, 打电话, 电脑, 或者某个特定的人. 如果要添加情境, 请输入 \"@\" 和情境名. 更多关于todo.txt的清晰请参见<a href=\"https://github.com/todotxt/todo.txt\" target=\"_blank\">click here</a>.<br><br>项目名和情境都不允许使用空格, 所以请使用一个单词. 你可以指定多个项目和情境. ",
"helpTabDatesRecurrencesTitle1": "添加日期",
"helpTabDatesRecurrencesBody1": "sleek会给任务自动添加创建日期. 如果你想要修改可以优先度旁边找到日期选择. 有到期的任务将显示在列表上部, 到期越接近当前日期位置越高. 如果到期设置为今天或者过去的日期, 则会被标记为红色, 且显示在列表最上面. 如果要添加到期可以使用 \"due:\" 日期格式为 <strong>YYYY-MM-DD (e.g. due:2021-03-07)</strong>. 你也可以使用到期选择器, 会自动帮你调整日期格式. ",
"helpTabDatesRecurrencesTitle2": "添加重复",
"helpTabDatesRecurrencesBody2": "设置到期后可以定义重复. 如果今天到期任务设置为按周重复, (任务完成时)sleek会复制并设置任务日期. 你可以使用重复选择器, 或者输入 <strong>rec:</strong> 和 <strong>d</strong> (每天), <strong>w</strong> (每周), <strong>m</strong> (每月) or <strong>y</strong> (每年).",
"helpTabKeyboardTR7TD1": "设置优先度",
"helpTabKeyboardTR8TD1": "Toggle filter drawer",
"helpTabKeyboardTR10TD1": "提交任务",
"submitIssuesOnGithub": "在Github上提交问题",
"reviewSourceforge": "在SourceForge上给我们建议",
"reviewWindowsStore": "在Windows Store上给我们建议",
"shareTwitter": "通过Twitter分享sleek",
"shareFacebook": "通过Facebook分享sleek",
"shareLinkedin": "通过LinkedI分享sleek"
}
...@@ -56,6 +56,7 @@ const todoTableSearch = document.getElementById("todoTableSearch"); ...@@ -56,6 +56,7 @@ const todoTableSearch = document.getElementById("todoTableSearch");
const todoTableSearchContainer = document.getElementById("todoTableSearchContainer"); const todoTableSearchContainer = document.getElementById("todoTableSearchContainer");
const welcomeToSleek = document.getElementById("welcomeToSleek"); const welcomeToSleek = document.getElementById("welcomeToSleek");
let let
append = false,
_paq, a0, _paq, a0,
a1, a1,
appData, appData,
...@@ -819,6 +820,9 @@ function setFriendlyLanguageNames() { ...@@ -819,6 +820,9 @@ function setFriendlyLanguageNames() {
case "fr": case "fr":
friendlyLanguageName = "Français" friendlyLanguageName = "Français"
break; break;
case "cn":
friendlyLanguageName = "Simplified Chinese"
break;