1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-23 23:33:01 +02:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Laurent Cozic
afd63aa3c8 test 2022-01-08 12:49:45 +00:00
Laurent Cozic
04c3c218b9 test 2022-01-08 12:39:24 +00:00
Laurent Cozic
088ae44c63 test 2022-01-08 12:37:56 +00:00
Laurent Cozic
4aa9339fbb test 2022-01-08 12:36:15 +00:00
Laurent Cozic
7edcbc5c27 test 2022-01-08 12:33:40 +00:00
Laurent Cozic
f3650097a0 test 2022-01-08 12:22:44 +00:00
Laurent Cozic
6a4326e2db test 2022-01-08 12:21:27 +00:00
378 changed files with 18410 additions and 27652 deletions

View File

@@ -1,19 +1,12 @@
_mydocs/
_releases/
.git/
.yarn/cache/
**/.DS_Store
**/node_modules
Assets/
docs/
lerna-debug.log
packages/app-cli/
packages/app-clipper/
packages/app-desktop/
packages/app-mobile/
packages/generator-joplin/
packages/plugin-repo-cli/
.git/
_releases/
packages/app-desktop
packages/app-cli
packages/app-mobile
packages/app-clipper
packages/generator-joplin
packages/plugin-repo-cli
packages/server/db-*.sqlite
packages/server/dist/
packages/server/logs/
packages/server/temp/
packages/server/temp

View File

@@ -235,9 +235,6 @@ packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
packages/app-desktop/gui/ErrorBoundary.d.ts
packages/app-desktop/gui/ErrorBoundary.js
packages/app-desktop/gui/ErrorBoundary.js.map
packages/app-desktop/gui/FolderIconBox.d.ts
packages/app-desktop/gui/FolderIconBox.js
packages/app-desktop/gui/FolderIconBox.js.map
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
@@ -490,12 +487,6 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js.map
packages/app-desktop/gui/NoteEditor/utils/index.d.ts
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/index.js.map
@@ -730,9 +721,6 @@ packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map
packages/app-desktop/gui/utils/loadScript.d.ts
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gui/utils/loadScript.js.map
packages/app-desktop/loadResources.testEnv.d.ts
packages/app-desktop/loadResources.testEnv.js
packages/app-desktop/loadResources.testEnv.js.map
packages/app-desktop/plugins/GotoAnything.d.ts
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/plugins/GotoAnything.js.map
@@ -1159,9 +1147,6 @@ packages/lib/models/NoteTag.js.map
packages/lib/models/Resource.d.ts
packages/lib/models/Resource.js
packages/lib/models/Resource.js.map
packages/lib/models/Resource.test.d.ts
packages/lib/models/Resource.test.js
packages/lib/models/Resource.test.js.map
packages/lib/models/ResourceLocalState.d.ts
packages/lib/models/ResourceLocalState.js
packages/lib/models/ResourceLocalState.js.map
@@ -1648,9 +1633,6 @@ packages/lib/services/searchengine/SearchEngineUtils.js.map
packages/lib/services/searchengine/SearchEngineUtils.test.d.ts
packages/lib/services/searchengine/SearchEngineUtils.test.js
packages/lib/services/searchengine/SearchEngineUtils.test.js.map
packages/lib/services/searchengine/SearchFilter.test.d.ts
packages/lib/services/searchengine/SearchFilter.test.js
packages/lib/services/searchengine/SearchFilter.test.js.map
packages/lib/services/searchengine/filterParser.d.ts
packages/lib/services/searchengine/filterParser.js
packages/lib/services/searchengine/filterParser.js.map
@@ -1945,9 +1927,6 @@ packages/renderer/headerAnchor.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
packages/renderer/htmlUtils.test.d.ts
packages/renderer/htmlUtils.test.js
packages/renderer/htmlUtils.test.js.map
packages/renderer/index.d.ts
packages/renderer/index.js
packages/renderer/index.js.map
@@ -1978,9 +1957,6 @@ packages/tools/generate-images.js.map
packages/tools/git-changelog.d.ts
packages/tools/git-changelog.js
packages/tools/git-changelog.js.map
packages/tools/licenseChecker.d.ts
packages/tools/licenseChecker.js
packages/tools/licenseChecker.js.map
packages/tools/release-android.d.ts
packages/tools/release-android.js
packages/tools/release-android.js.map
@@ -2026,15 +2002,6 @@ packages/tools/website/updateDownloadPage.js.map
packages/tools/website/utils/frontMatter.d.ts
packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/frontMatter.js.map
packages/tools/website/utils/openGraph.d.ts
packages/tools/website/utils/openGraph.js
packages/tools/website/utils/openGraph.js.map
packages/tools/website/utils/openGraph.test.d.ts
packages/tools/website/utils/openGraph.test.js
packages/tools/website/utils/openGraph.test.js.map
packages/tools/website/utils/parser.d.ts
packages/tools/website/utils/parser.js
packages/tools/website/utils/parser.js.map
packages/tools/website/utils/pressCarousel.d.ts
packages/tools/website/utils/pressCarousel.js
packages/tools/website/utils/pressCarousel.js.map

View File

@@ -38,8 +38,6 @@ echo "GITHUB_REF=$GITHUB_REF"
echo "RUNNER_OS=$RUNNER_OS"
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "BUILD_SEQUENCIAL=$BUILD_SEQUENCIAL"
echo "SERVER_REPOSITORY=$SERVER_REPOSITORY"
echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
@@ -171,10 +169,10 @@ cd "$ROOT_DIR/packages/app-desktop"
if [[ $GIT_TAG_NAME = v* ]]; then
echo "Step: Building and publishing desktop application..."
USE_HARD_LINKS=false yarn run dist
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
elif [[ $GIT_TAG_NAME = server-v* ]] && [[ $IS_LINUX = 1 ]]; then
echo "Step: Building Docker Image..."
cd "$ROOT_DIR"
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images
else
echo "Step: Building but *not* publishing desktop application..."
USE_HARD_LINKS=false yarn run dist --publish=never

View File

@@ -15,8 +15,7 @@ jobs:
stale-issue-message: "Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may comment on the issue and I will leave it open. Thank you for your contributions."
days-before-stale: 30
days-before-close: 7
operations-per-run: 1000
exempt-issue-labels: 'good first issue,upstream,backlog,high,medium,spec,cannot reproduce,enhancement'
exempt-issue-labels: 'good first issue,upstream,backlog,high,medium,spec,cannot reproduce'
stale-issue-label: 'stale'
close-issue-message: 'Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, feel free to create a new issue with up-to-date information.'
# Don't process pull requests at all

View File

@@ -86,22 +86,10 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
# To ensure that the operations stop on failure, all commands
# should be on one line with "&&" in between.
run: |
yarn install && cd packages/app-desktop && yarn run dist
# Build and package the Windows app, without publishing it, just to
# verify that the build process hasn't been broken.
- name: Build Windows app (no publishing)
if: runner.os == 'Windows' && !startsWith(github.ref, 'refs/tags/v')
env:
IS_CONTINUOUS_INTEGRATION: 1
BUILD_SEQUENCIAL: 1
SERVER_REPOSITORY: joplin/server
SERVER_TAG_PREFIX: server
run: |
yarn install && cd packages/app-desktop && yarn run dist --publish=never
yarn install
cd packages/app-desktop
yarn run dist
ServerDockerImage:
runs-on: ${{ matrix.os }}
@@ -140,5 +128,5 @@ jobs:
BUILD_SEQUENCIAL: 1
run: |
yarn install
yarn run buildServerDocker --tag-name server-v0.0.0 --repository joplin/server
yarn run buildServerDocker --tag-name server-v0.0.0

33
.gitignore vendored
View File

@@ -225,9 +225,6 @@ packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
packages/app-desktop/gui/ErrorBoundary.d.ts
packages/app-desktop/gui/ErrorBoundary.js
packages/app-desktop/gui/ErrorBoundary.js.map
packages/app-desktop/gui/FolderIconBox.d.ts
packages/app-desktop/gui/FolderIconBox.js
packages/app-desktop/gui/FolderIconBox.js.map
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.d.ts
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js.map
@@ -480,12 +477,6 @@ packages/app-desktop/gui/NoteEditor/utils/clipboardUtils.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js
packages/app-desktop/gui/NoteEditor/utils/contextMenu.test.js.map
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.d.ts
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js
packages/app-desktop/gui/NoteEditor/utils/contextMenuUtils.js.map
packages/app-desktop/gui/NoteEditor/utils/index.d.ts
packages/app-desktop/gui/NoteEditor/utils/index.js
packages/app-desktop/gui/NoteEditor/utils/index.js.map
@@ -720,9 +711,6 @@ packages/app-desktop/gui/utils/convertToScreenCoordinates.js.map
packages/app-desktop/gui/utils/loadScript.d.ts
packages/app-desktop/gui/utils/loadScript.js
packages/app-desktop/gui/utils/loadScript.js.map
packages/app-desktop/loadResources.testEnv.d.ts
packages/app-desktop/loadResources.testEnv.js
packages/app-desktop/loadResources.testEnv.js.map
packages/app-desktop/plugins/GotoAnything.d.ts
packages/app-desktop/plugins/GotoAnything.js
packages/app-desktop/plugins/GotoAnything.js.map
@@ -1149,9 +1137,6 @@ packages/lib/models/NoteTag.js.map
packages/lib/models/Resource.d.ts
packages/lib/models/Resource.js
packages/lib/models/Resource.js.map
packages/lib/models/Resource.test.d.ts
packages/lib/models/Resource.test.js
packages/lib/models/Resource.test.js.map
packages/lib/models/ResourceLocalState.d.ts
packages/lib/models/ResourceLocalState.js
packages/lib/models/ResourceLocalState.js.map
@@ -1638,9 +1623,6 @@ packages/lib/services/searchengine/SearchEngineUtils.js.map
packages/lib/services/searchengine/SearchEngineUtils.test.d.ts
packages/lib/services/searchengine/SearchEngineUtils.test.js
packages/lib/services/searchengine/SearchEngineUtils.test.js.map
packages/lib/services/searchengine/SearchFilter.test.d.ts
packages/lib/services/searchengine/SearchFilter.test.js
packages/lib/services/searchengine/SearchFilter.test.js.map
packages/lib/services/searchengine/filterParser.d.ts
packages/lib/services/searchengine/filterParser.js
packages/lib/services/searchengine/filterParser.js.map
@@ -1935,9 +1917,6 @@ packages/renderer/headerAnchor.js.map
packages/renderer/htmlUtils.d.ts
packages/renderer/htmlUtils.js
packages/renderer/htmlUtils.js.map
packages/renderer/htmlUtils.test.d.ts
packages/renderer/htmlUtils.test.js
packages/renderer/htmlUtils.test.js.map
packages/renderer/index.d.ts
packages/renderer/index.js
packages/renderer/index.js.map
@@ -1968,9 +1947,6 @@ packages/tools/generate-images.js.map
packages/tools/git-changelog.d.ts
packages/tools/git-changelog.js
packages/tools/git-changelog.js.map
packages/tools/licenseChecker.d.ts
packages/tools/licenseChecker.js
packages/tools/licenseChecker.js.map
packages/tools/release-android.d.ts
packages/tools/release-android.js
packages/tools/release-android.js.map
@@ -2016,15 +1992,6 @@ packages/tools/website/updateDownloadPage.js.map
packages/tools/website/utils/frontMatter.d.ts
packages/tools/website/utils/frontMatter.js
packages/tools/website/utils/frontMatter.js.map
packages/tools/website/utils/openGraph.d.ts
packages/tools/website/utils/openGraph.js
packages/tools/website/utils/openGraph.js.map
packages/tools/website/utils/openGraph.test.d.ts
packages/tools/website/utils/openGraph.test.js
packages/tools/website/utils/openGraph.test.js.map
packages/tools/website/utils/parser.d.ts
packages/tools/website/utils/parser.js
packages/tools/website/utils/parser.js.map
packages/tools/website/utils/pressCarousel.d.ts
packages/tools/website/utils/pressCarousel.js
packages/tools/website/utils/pressCarousel.js.map

View File

@@ -307,7 +307,7 @@ p,
div.navbar-mobile-content a.sponsor-button {
padding: 4px 12px;
font-size: 0.9em;
margin-right: 0.5em;
margin-right: 1em;
}
#nav-section.white-bg a {
@@ -670,9 +670,9 @@ footer .bottom-links-row p {
margin-right: auto;
}
.news-page .main-content img,
.news-item-page .main-content img {
max-width: 100%;
.news-page img,
.news-item-page img {
max-width: 650px;
}
/*****************************************************************
@@ -728,23 +728,6 @@ footer .bottom-links-row p {
}
}
/*****************************************************************
MEDIUM VIEW
- Make menu bar elements smaller and closer to each others
so that everything fit.
*****************************************************************/
@media (max-width: 990px) {
#nav-section > .container {
max-width: 960px;
}
#nav-section .button-link {
padding: 4px 10px;
font-size: 15px;
}
}
/*****************************************************************
NARROW VIEW
- Top right menu is displayed
@@ -757,23 +740,6 @@ footer .bottom-links-row p {
padding-bottom: 130px;
}
#menu-mobile .social-links {
display: flex;
justify-content: center;
margin-top: 20px;
}
#menu-mobile .social-links a {
margin-left: 15px;
font-size: 20px;
}
#menu-mobile .social-links .social-link-mastodon,
#menu-mobile .social-links .social-link-reddit,
#menu-mobile .social-links .social-link-patreon {
display: none;
}
.front-page h1 {
font-size: 2.5em;
}
@@ -891,7 +857,7 @@ footer .bottom-links-row p {
}
#menu-mobile .button-link {
padding: 4px 12px;
padding: 10px 15px;
font-size: 16px;
margin-left: 0px;
}
@@ -937,25 +903,6 @@ footer .bottom-links-row p {
}
}
/*****************************************************************
VERY NARROW VIEW
eg for Galaxy Fold
*****************************************************************/
@media (max-width: 350px) {
#nav-section .navbar-mobile-content a.sponsor-button {
background-color: transparent;
color: #0557ba !important;
font-size: 18px;
}
#nav-section .navbar-mobile-content a.sponsor-button .sponsor-button-label {
display: none;
}
}
/*****************************************************************
PLANS PAGE
*****************************************************************/
@@ -1073,10 +1020,6 @@ footer .bottom-links-row p {
display: none;
}
.joplin-cloud-feature-list table {
width: 100%;
}
.price-row .plan-type {
display: flex;
align-items: center;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -10,8 +10,8 @@
<link rel="icon" href="{{imageBaseUrl}}/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Joplin, the open source note-taking application" />
<link rel="stylesheet" href="{{{assetUrls.css.fontawesome}}}">
{{> openGraphTags}}
<link
rel="stylesheet"
href="{{cssBaseUrl}}/bootstrap5.0.2.min.css"

View File

@@ -23,7 +23,7 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
<link rel="icon" href="{{imageBaseUrl}}/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
{{> openGraphTags}}
<meta name="description" content="Joplin, the open source note-taking application" />
<link
rel="stylesheet"
href="{{cssBaseUrl}}/bootstrap5.0.2.min.css"
@@ -65,6 +65,11 @@ https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}
{{{contentHtml}}}
{{#showBottomLinks}}
<div class="bottom-links">
{{#discussOnForumLink}}
<a class="bottom-link" href="{{{discussOnForumLink}}}">
<i class="fab fa-discourse"></i></i> Discuss on the forum
</a>
{{/discussOnForumLink}}
{{#showImproveThisDoc}}
<a class="bottom-link" href="https://github.com/laurent22/joplin/blob/dev/{{{sourceMarkdownFile}}}">
<i class="fab fa-github"></i> Improve this doc

View File

@@ -1,6 +1,15 @@
<footer class="darkblue-bg">
<div class="container">
{{> socialFeeds}}
<div class="row">
<div class="col-12 col-md-12 social-links">
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed"><i class="fab fa-twitter"></i></a>
<a href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
<a href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
<a href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
<a href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
<a href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
</div>
</div>
<div class="row bottom-links-row">
<div class="col-12 col-md-6">

View File

@@ -12,8 +12,7 @@
</a>
</div>
<div class="col-9 text-right d-none d-md-block">
{{> twitterLink}}
<a href="{{baseUrl}}/news/" class="fw500">News</a>
<a href="{{baseUrl}}/news/" class="fw500">What's New</a>
<a href="{{baseUrl}}/help/" class="fw500">Help</a>
<a href="{{forumUrl}}" class="fw500">Forum</a>
{{#showJoplinCloudLinks}}
@@ -22,7 +21,6 @@
{{> supportButton}}
</div>
<div class="col-9 text-right d-block d-md-none navbar-mobile-content">
{{> twitterLink}}
{{> supportButton}}
<span class="pointer"
@@ -45,7 +43,7 @@
</div>
<div class="text-center menu-mobile-top">
<a href="{{baseUrl}}/news/" class="fw500 mobile-menu-link">News</a>
<a href="{{baseUrl}}/news/" class="fw500 mobile-menu-link">What's New</a>
<a href="{{baseUrl}}/help/" class="fw500 mobile-menu-link">Help</a>
<a href="{{forumUrl}}" class="fw500 mobile-menu-link">Forum</a>
</div>
@@ -61,8 +59,6 @@
{{#showToc}}
<div id="toc-mobile">{{{tocHtml}}}</div>
{{/showToc}}
{{> socialFeeds}}
<div>
<p class="light-blue mobile-menu-link-bottom text-center">

View File

@@ -1,14 +0,0 @@
{{#openGraph}}
<meta name="description" content="{{openGraph.description}}" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@joplinapp" />
<meta property="og:url" content="{{{openGraph.url}}}" />
<meta property="og:title" content="{{openGraph.title}}" />
<meta property="twitter:title" content="{{openGraph.title}}" />
<meta property="og:description" content="{{openGraph.description}}" />
<meta property="twitter:description" content="{{openGraph.description}}" />
{{#openGraph.image}}
<meta property="og:image" content="{{{openGraph.image}}}" />
<meta property="twitter:image" content="{{{openGraph.image}}}" />
{{/openGraph.image}}
{{/openGraph}}

View File

@@ -6,11 +6,11 @@
</div>
<div class="plan-price plan-price-monthly">
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month{{#footnote}} (*){{/footnote}}</sub>
{{priceMonthly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month</sub>
</div>
<div class="plan-price plan-price-yearly">
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month{{#footnote}} (*){{/footnote}}</sub>
{{priceYearly.formattedMonthlyAmount}}<sub class="per-month">&nbsp;/month</sub>
</div>
</div>
@@ -20,19 +20,17 @@
</div>
</div>
{{#featureLabelsOn}}
{{#featuresOn}}
<p><i class="fas fa-check feature feature-on"></i>{{.}}</p>
{{/featureLabelsOn}}
{{/featuresOn}}
{{#featureLabelsOff}}
{{#featuresOff}}
<p class="unchecked-text"><i class="fas fa-times feature feature-off"></i>{{.}}</p>
{{/featureLabelsOff}}
{{/featuresOff}}
<p class="text-center subscribe-wrapper">
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton">{{cfaLabel}}</a>
</p>
{{#footnote}}<sub>(*) {{.}}</sub>{{/footnote}}
</div>
<script>

View File

@@ -1,10 +0,0 @@
<div class="row">
<div class="col-12 col-md-12 social-links">
<a class="social-link-twitter" href="https://twitter.com/joplinapp" title="Joplin Twitter feed"><i class="fab fa-twitter"></i></a>
<a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
<a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
<a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
<a class="social-link-reddit" href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
<a class="social-link-github" href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
</div>
</div>

View File

@@ -1,3 +1,3 @@
<a class="button-link btn-blue sponsor-button" href="{{baseUrl}}/donate">
<i class="fas fa-heart heart-full"></i><i class="far fa-heart heart-line"></i><span class="sponsor-button-label">&nbsp;Support us</span>
<i class="fas fa-heart heart-full"></i><i class="far fa-heart heart-line"></i>&nbsp;Support us
</a>

View File

@@ -1 +0,0 @@
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500"><i class="fab fa-twitter"></i></a>

View File

@@ -42,23 +42,13 @@
{{> plan}}
{{/plans.pro}}
{{#plans.teams}}
{{#plans.business}}
{{> plan}}
{{/plans.teams}}
{{/plans.business}}
<p class="joplin-cloud-login-info">Already have a Joplin Cloud account? <a href="https://joplincloud.com">Login now</a></p>
</div>
<div class="row">
<div>
<h1>Feature comparison</h1>
<div class="joplin-cloud-feature-list">
{{{featureListHtml}}}
</div>
<p>&nbsp;</p>
</div>
</div>
<div class="row">
{{{faqHtml}}}
</div>

View File

@@ -18,15 +18,14 @@ There are also a few forks of existing packages under the "fork-*" name.
## Required dependencies
- Install Node 16+. On Windows, also install the build tools - https://nodejs.org/en/
- [Enable Yarn](https://yarnpkg.com/getting-started/install): `corepack enable`
- macOS: Install Cocoapods - `brew install cocoapods`. Apple Silicon [may require libvips](https://github.com/laurent22/joplin/pull/5966#issuecomment-1007158597) - `brew install vips`.
- Install node 16+ - https://nodejs.org/en/
- [Enable yarn](https://yarnpkg.com/getting-started/install): `corepack enable`
- macOS: Install Cocoapods - `brew install cocoapods`
- Windows: Install Windows Build Tools - `yarn install -g windows-build-tools --vs2015`
- Linux: Install dependencies - `sudo apt install build-essential libnss3 libsecret-1-dev python rsync`
## Building
Make sure the path to the project directory does not contain spaces or the build may fail.
Before doing anything else, from the root of the project, run:
yarn install

View File

@@ -38,8 +38,7 @@ If you want to start contributing to the project's code, please follow these gui
- **Changes that will consist of more than 50 lines of code should be discussed on the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
- All the applications share the same backend (database, synchronisation, settings, models, business logic, etc.) so if you change something in the backend in one app, make sure it still works in the other apps. Usually it does, but keep this in mind.
- Pull requests that make many changes using an automated tool, like for spell fixing, styling, etc. will not be accepted. An exception would be if the changes have been discussed in the forum and someone has agreed to review **and test** the pull request.
- Pull requests that address multiple issues will most likely stall and eventually be closed. This is because we might be fine with one of the changes but not with others and untangling that kind of pull request is too much hassle both for maintainers and the person who submitted it. So most of the time someone gives up and the PR gets closed. So please keep the pull request focused on one issue.
- **Do not mark your reviewer's comments as "resolved"**. If you do that, the comments will be hidden and the reviewer will not know what are the pending issues in the pull request. Only the reviewer should resolve the comments.
- Pull requests that make address multiple issues will most likely stall and eventually be closed. This is because we might be fine with one of the changes but not with others and untangling that kind of pull request is too much hassle both for maintainers and the person who submitted it. So most of the time someone gives up and the PR gets closed. So please keep the pull request focused on one issue.
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/dev/BUILD.md) for more details.
@@ -53,57 +52,37 @@ For changes made to the Desktop client that affect the user interface, refer to
## Automated tests
When submitting a pull request for a new feature or a bug fix, please add automated tests. We use [Jest](https://jestjs.io/) as a testing framework so you will need to be familiar with it or go through their documentation.
When submitting a pull request for a new feature or a bug fix, please add automated tests for your code whenever possible. Tests in Joplin are divided into **unit tests** and **feature tests**.
### Running the tests
* **Unit tests** are used to test models, services or utility classes - they are relatively low level. Unit tests should be prefixed with the type of class that is being tested - for example "models_Folder" or "services_SearchEngine".
To run all the test units, run from the root:
* **Feature tests** on the other hand are to test higher level functionalities such as interactions with the GUI and how they affect the underlying model. Often these tests would dispatch Redux actions, and inspect how the application state has been changed. The feature tests should be prefixed with "feature_", for example "feature_TagList". There's a good explanation on what qualifies as a feature test in [this post](https://github.com/laurent22/joplin/pull/2819#issuecomment-603502230).
The tests are under packages/app-cli/tests. To get them running, you first need to build the CLI app:
```sh
yarn install
cd packages/app-cli
```
To run all the test units:
```sh
yarn test
```
Or you can go inside a package folder, and run the tests from there. For example to run all the library tests, go in `packages/lib` and run `yarn test`
To run just one particular file:
```sh
# Run all the tests in markdownUtils.test.ts
yarn test markdownUtils
yarn test --filter=markdownUtils # Don't add the .js extension
```
To run only a particular test in a file:
To filter tests. For example, to run all the test units that contain "should handle conflict" in their description:
```sh
# Run only the test described as "should handle conflict"
# inside markdownUtils.test.ts:
yarn test markdownUtils --filter="should handle conflict"
yarn test --filter="should handle conflict"
```
### Adding a new test file
To add a test, simply create a new file with an extension `.test.ts` in the same directory. For example if you are working on the file `example.ts`, create a file `example.test.ts` for the unit tests. If this file already exist, simply add your tests directly to it.
### Setting the testing environment
Many utility functions are available under the package `@joplin/lib/testing/test-utils`. Have a look for example at [Note.test.ts](https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Note.test.ts) to see how to setup test units with database support and synchroniser support. Note that this is not needed for all tests - if you just have a simple functions to test you won't need that extra setup.
### Testing React Hooks
To test React Hooks please use the package `@testing-library/react-hooks`. See [useLayoutItemSizes.test.ts](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/gui/ResizableLayout/utils/useLayoutItemSizes.test.ts) for an example.
### If it is not possible to add tests
More often than not, it is actually possible to add tests - just go back to your code and see if it can be refactored and certain functionalities moved to simple functions (with no dependencies). Once you have a simple function, you can easily add unit tests for it.
Additionally, if the unit tests are not sufficient, please provide a **manual testing plan**, which should include detailed steps on:
- How to test that your feature is working. Include at least 5 tests. Try to think of the possible inputs - if it's a list, how does it work with 0 elements, or 1, or 10, or 100,000. If it's a text input, how does it work with an empty string, or a very large string, etc. Basically don't just put one test that check the best case scenario.
- How to verify that related parts of the applications are not broken. For example if you changed the note loading logic, check that the toolbar is still working as expected (and not modifying the previously loaded note for example), check that switching from one note to another still works. Look at the note list and verify that the note title is updated there too, etc.
A reviewer should be able to run the app with your changes, then do the above steps to verify that everything's working as expected.
## About abandoned pull requests
It happens that a pull request is started but not finished and despite our attempts to contact the contributor, we don't hear from them again.

View File

@@ -1,75 +1,96 @@
# =============================================================================
# Build stage
# =============================================================================
FROM node:16-bullseye AS builder
FROM node:16-bullseye
RUN apt-get update \
&& apt-get install -y \
python tini \
python \
&& rm -rf /var/lib/apt/lists/*
# Enables Yarn
RUN corepack enable
WORKDIR /build
COPY .yarn/plugins ./.yarn/plugins
COPY .yarn/releases ./.yarn/releases
COPY package.json .
COPY .yarnrc.yml .
COPY yarn.lock .
COPY gulpfile.js .
COPY tsconfig.json .
COPY packages/turndown ./packages/turndown
COPY packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm
COPY packages/fork-htmlparser2 ./packages/fork-htmlparser2
COPY packages/server/package*.json ./packages/server/
COPY packages/fork-sax ./packages/fork-sax
COPY packages/fork-uslug ./packages/fork-uslug
COPY packages/htmlpack ./packages/htmlpack
COPY packages/renderer ./packages/renderer
COPY packages/tools ./packages/tools
COPY packages/lib ./packages/lib
COPY packages/server ./packages/server
# For some reason there's both a .yarn/cache and .yarn/berry/cache that are
# being generated, and both have the same content. Not clear why it does this
# but we can delete it anyway. We can delete the cache because we use
# `nodeLinker: node-modules`. If we ever implement Zero Install, we'll need to
# keep the cache.
#
# Note that `yarn install` ignores `NODE_ENV=production` and will install dev
# dependencies too, but this is fine because we need them to build the app.
RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \
&& yarn cache clean \
&& rm -rf .yarn/berry
# =============================================================================
# Final stage - we copy only the relevant files from the build stage and start
# from a smaller base image.
# =============================================================================
FROM node:16-bullseye-slim
RUN echo "Node: $(node --version)"
RUN echo "Npm: $(npm --version)"
RUN echo "Yarn: $(yarn --version)"
ARG user=joplin
RUN useradd --create-home --shell /bin/bash $user
RUN useradd --create-home --shell /bin/bash $user
USER $user
COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages
COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini
ENV NODE_ENV development
WORKDIR /home/$user
RUN mkdir /home/$user/logs
# Install the root scripts but don't run postinstall (which would bootstrap
# and build TypeScript files, but we don't have the TypeScript files at
# this point)
COPY --chown=$user:$user package*.json ./
COPY --chown=$user:$user .yarn ./.yarn
COPY --chown=$user:$user .yarnrc.yml .
COPY --chown=$user:$user yarn.lock .
COPY --chown=$user:$user gulpfile.js .
RUN yarn install --inline-builds --mode=skip-build
# To take advantage of the Docker cache, we first copy all the package.json
# and package-lock.json files, as they rarely change, and then bootstrap
# all the packages.
#
# Note that bootstrapping the packages will run all the postinstall
# scripts, which means that for packages that have such scripts, we need to
# copy all the files.
#
# We can't run boostrap with "--ignore-scripts" because that would
# prevent certain sub-packages, such as sqlite3, from being built
COPY --chown=$user:$user packages/fork-sax/package*.json ./packages/fork-sax/
COPY --chown=$user:$user packages/fork-uslug/package*.json ./packages/fork-uslug/
COPY --chown=$user:$user packages/htmlpack/package*.json ./packages/htmlpack/
COPY --chown=$user:$user packages/renderer/package*.json ./packages/renderer/
COPY --chown=$user:$user packages/tools/package*.json ./packages/tools/
COPY --chown=$user:$user packages/lib/package*.json ./packages/lib/
COPY --chown=$user:$user tsconfig.json .
# The following have postinstall scripts so we need to copy all the files.
# Since they should rarely change this is not an issue
COPY --chown=$user:$user packages/turndown ./packages/turndown
COPY --chown=$user:$user packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm
COPY --chown=$user:$user packages/fork-htmlparser2 ./packages/fork-htmlparser2
COPY --chown=$user:$user packages/server/package*.json ./packages/server/
# Then bootstrap only, without compiling the TypeScript files
RUN yarn install --inline-builds --mode=skip-build
# Now copy the source files. Put lib and server last as they are more likely to change.
COPY --chown=$user:$user packages/fork-sax ./packages/fork-sax
COPY --chown=$user:$user packages/fork-uslug ./packages/fork-uslug
COPY --chown=$user:$user packages/htmlpack ./packages/htmlpack
COPY --chown=$user:$user packages/renderer ./packages/renderer
COPY --chown=$user:$user packages/tools ./packages/tools
COPY --chown=$user:$user packages/lib ./packages/lib
COPY --chown=$user:$user packages/server ./packages/server
# Finally build everything, in particular the TypeScript files. We can't just
# run `yarn run build` because that wouldn't run the postinstall scripts in
# dependencies (for example the sqlite3 native module would not be built). So
# instead we run `yarn install`, which is going to install again all the
# packages (but because it's already done it should be fast), and then run the
# postinstall scripts, as well as build scripts.
RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds
ENV NODE_ENV=production
ENV RUNNING_IN_DOCKER=1
EXPOSE ${APP_PORT}
# Use Tini to start Joplin Server:
# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals
WORKDIR /home/$user/packages/server
ENTRYPOINT ["tini", "--"]
CMD ["node", "dist/app.js"]
# Not clear what's the equivalent of "--prefix" in Yarn 3, so keep using npm for
# now.
CMD [ "npm", "--prefix", "packages/server", "start" ]
# Build-time metadata
# https://github.com/opencontainers/image-spec/blob/master/annotations.md

View File

@@ -219,8 +219,7 @@ then
Type=Application
Categories=Office;
MimeType=x-scheme-handler/joplin;
X-GNOME-SingleWindow=true // should be removed eventually as it was upstream to be an XDG specification
SingleMainWindow=true
X-GNOME-SingleWindow=true
EOF
# Update application icons

View File

@@ -10,11 +10,6 @@ under that directory is licensed under the default license, which is MIT.
* * *
Joplin® is a trademark of Cozic Ltd registered in the European Union, with
filing number 018544315.
* * *
Logo and Icon License
The Joplin logos and icons are copyright (c) Laurent Cozic, all rights reserved,

153
README.md
View File

@@ -4,19 +4,13 @@
* * *
🌞 Joplin participates in **Google Summer of Code 2022**! More info on [the announcement post](https://github.com/laurent22/joplin/blob/dev/readme/news/20220308-gsoc2022-start.md). 🌞
* * *
<img width="64" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/LinuxIcons/256x256.png" align="left" /> **Joplin** is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](#markdown).
Notes exported from Evernote [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
Notes exported from Evernote via .enex files [can be imported](#importing) into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be securely [synchronised](#synchronisation) using [end-to-end encryption](#encryption) with various cloud services including Nextcloud, Dropbox, OneDrive and [Joplin Cloud](https://joplinapp.org/plans/).
The notes can be [synchronised](#synchronisation) with various cloud services including [Nextcloud](https://nextcloud.com/), Dropbox, OneDrive, WebDAV or the file system (for example with a network directory). When synchronising the notes, notebooks, tags and other metadata are saved to plain text files which can be easily inspected, backed up and moved around.
Full text search is available on all platforms to quickly find the information you need. The app can be customised using plugins and themes, and you can also easily create your own.
The application is available for Windows, Linux, macOS, Android and iOS. A [Web Clipper](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md), to save web pages and screenshots from your browser, is also available for [Firefox](https://addons.mozilla.org/firefox/addon/joplin-web-clipper/) and [Chrome](https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek?hl=en-GB).
The application is available for Windows, Linux, macOS, Android and iOS (the terminal app also works on FreeBSD). A [Web Clipper](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md), to save web pages and screenshots from your browser, is also available for [Firefox](https://addons.mozilla.org/firefox/addon/joplin-web-clipper/) and [Chrome](https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek?hl=en-GB).
<div class="top-screenshot"><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/home-top-img.png" style="max-width: 100%; max-height: 35em;"></div>
@@ -28,11 +22,11 @@ Three types of applications are available: for **desktop** (Windows, macOS and L
Operating System | Download
---|---
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-Setup-2.7.15.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-2.7.15.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/Joplin-2.7.15.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/Joplin-Setup-2.6.10.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/Joplin-2.6.10.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/Joplin-2.6.10.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.6.10/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Linux**, the recommended way is to use the following installation script as it will handle the desktop icon too:
@@ -42,7 +36,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.7.15/J
Operating System | Download | Alt. Download
---|---|---
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.7.2/joplin-v2.7.2.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.7.2/joplin-v2.7.2-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.6.9/joplin-v2.6.9.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.6.9/joplin-v2.6.9-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -55,18 +49,19 @@ To start it, type `joplin`.
For usage information, please refer to the full [Joplin Terminal Application Documentation](https://joplinapp.org/terminal/).
### Unsupported methods
There are other ways to install the terminal application. However, they are not supported and problems must be reported to the upstream projects.
Operating system | Method
-----------------|----------------
Arch Linux | An Arch Linux package is available [here](https://aur.archlinux.org/packages/joplin/). To install it, use an AUR wrapper such as yay: `yay -S joplin`. Both the CLI tool (type `joplin`) and desktop app (type `joplin-desktop`) are packaged. You can also install a compiled version with the [chaotic-aur](https://wiki.archlinux.org/index.php/Unofficial_user_repositories#chaotic-aur) repository. For support, please go to the [GitHub repo](https://github.com/masterkorp/joplin-pkgbuild). If you are interested in [pre-release](https://joplinapp.org/prereleases/) you have [joplin-beta](https://aur.archlinux.org/packages/joplin-beta).
Flatpak | A Flatpak is available on [Flathub](https://flathub.org/apps/details/net.cozic.joplin_desktop). To install it, run `flatpak install net.cozic.joplin_desktop` after [setting up Flathub](https://flatpak.org/setup/). GUI software managers on most distros support Flatpak installation.
## Web Clipper
The Web Clipper is a browser extension that allows you to save web pages and screenshots from your browser. For more information on how to install and use it, see the [Web Clipper Help Page](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md).
## Unofficial Alternative Distributions
There are a number of unofficial alternative Joplin distributions. If you do not want to or cannot use appimages or any of the other officially supported releases then you may wish to consider these.
However these come with a caveat in that they are not officially supported so certain issues may not be supportable by the main project. Rather support requests, bug reports and general advice would need to go to the maintainers of these distributions.
A community maintained list of these distributions can be found here: [Unofficial Joplin distributions](https://discourse.joplinapp.org/t/unofficial-alternative-joplin-distributions/23703)
# Sponsors
<!-- SPONSORS-ORG -->
@@ -80,11 +75,10 @@ A community maintained list of these distributions can be found here: [Unofficia
| :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/82579431?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl) | <img width="50" src="https://avatars2.githubusercontent.com/u/808091?s=96&v=4"/></br>[cuongtransc](https://github.com/cuongtransc) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | | |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) | <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | |
<!-- SPONSORS-GITHUB -->
<!-- TOC -->
@@ -131,9 +125,7 @@ A community maintained list of these distributions can be found here: [Unofficia
- Development
- [How to build the apps](https://github.com/laurent22/joplin/blob/dev/BUILD.md)
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
- [End-to-end encryption spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
@@ -143,11 +135,11 @@ A community maintained list of these distributions can be found here: [Unofficia
- [Server: Delta Sync](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_delta_sync.md)
- [Server: Sharing](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_sharing.md)
- Google Summer of Code 2022
- Google Summer of Code 2021
- [Google Summer of Code 2022](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2022/index.md)
- [How to submit a GSoC pull request](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2022/pull_request_guidelines.md)
- [Project Ideas](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2022/ideas.md)
- [Google Summer of Code 2021](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2021/index.md)
- [How to submit a GSoC pull request](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2021/pull_request_guidelines.md)
- [Project Ideas](https://github.com/laurent22/joplin/blob/dev/readme/gsoc2021/ideas.md)
- About
@@ -308,6 +300,7 @@ To add a **Bucket Policy** from the AWS S3 Web Console, navigate to the **Permis
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
@@ -480,6 +473,24 @@ Notes are sorted by "relevance". Currently it means the notes that contain the r
In the desktop application, press <kbd>Ctrl+P</kbd> or <kbd>Cmd+P</kbd> and type a note title or part of its content to jump to it. Or type <kbd>#</kbd> followed by a tag name, or <kbd>@</kbd> followed by a notebook name.
# Privacy
Joplin values your privacy and security by giving you complete control over your information and digital footprint.
Joplin applications do not send any data to any service without your authorisation. Any data that Joplin saves, such as notes or images, are saved to your own device and you are free to delete this data at any time.
Joplin has many modern features, some of which use third-party services. You can disable any or all of these features in the application settings. These features are:
|Feature | Description | Default|
|--------|-------------|--------|
|Auto-update|Joplin periodically connects to GitHub to check for new releases.|Enabled|
|Geo-location|Joplin saves geo-location information in note properties when you create a note.|Enabled|
|Synchronisation|Joplin supports synchronisation of your notes across multiple devices. If you choose to synchronise with a third-party, such as OneDrive, the notes will be sent to your OneDrive account, in which case the third-party privacy policy applies.|Disabled|
Joplin is developed as an open-source application and the source code is freely available online to inspect.
For any question about Joplin privacy, please leave a message on the [Joplin Forum](https://discourse.joplinapp.org/).
# Donations
Donations to Joplin support the development of the project. Developing quality applications mostly takes time, but there are also some expenses, such as digital certificates to sign the applications, app store fees, hosting, etc. Most of all, your donation will make it possible to keep up the current development standard.
@@ -520,47 +531,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 90%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 26%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 65%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 51%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pthrp_bnsrv@aleeas.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 50%
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 93%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 27%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 67%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 53%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:s.robin@tutanota.de) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 51%
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 29%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | miucci | 92%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 30%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 33%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 76%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 89%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 83%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | Alexander Dawson | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 62%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Felipe Viggiano](mailto:felipeviggiano@gmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 57%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 41%
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [horaceyoung](mailto:yonghaoharry@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 87%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 34%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 92%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 81%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 85%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | Alexander Dawson | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 64%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Felipe Viggiano](mailto:felipeviggiano@gmail.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 84%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 59%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 43%
<img src="https://joplinapp.org/images/flags/country-4x3/vi.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 83%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 87%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 93%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 76%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [南宫小骏](mailto:jackytsu@vip.qq.com) | 97%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 90%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 98%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 89%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors

View File

@@ -1,19 +1,8 @@
# This is a sample docker-compose file that can be used to run Joplin Server
# along with a PostgreSQL server.
#
# Update the following fields in the stanza below:
#
# POSTGRES_USER
# POSTGRES_PASSWORD
# APP_BASE_URL
#
# APP_BASE_URL: This is the base public URL where the service will be running.
# - If Joplin Server needs to be accessible over the internet, configure APP_BASE_URL as follows: https://example.com/joplin.
# - If Joplin Server does not need to be accessible over the internet, set the the APP_BASE_URL to your server's hostname.
# For Example: http://[hostname]:22300. The base URL can include the port.
# APP_PORT: The local port on which the Docker container will listen.
# - This would typically be mapped to port to 443 (TLS) with a reverse proxy.
# - If Joplin Server does not need to be accessible over the internet, the port can be mapped to 22300.
# All environment variables are optional. If you don't set them, you will get a
# warning from docker-compose, however the app should use working defaults.
version: '3'

View File

@@ -1 +0,0 @@
eine Notiz- und Aufgaben-App mit Sync zwischen Linux, macOS, Windows

View File

@@ -1,9 +0,0 @@
<strong>Joplin</strong> is a note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in <a href="#markdown">Markdown format</a>.
Notes exported from Evernote and other applications <a href="https://joplinapp.org/help/#importing">can be imported</a> into Joplin, including the formatted content (which is converted to Markdown), resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.). Plain Markdown files can also be imported.
The notes can be securely <a href="https://joplinapp.org/help/#synchronisation">synchronised</a> using <a href="https://joplinapp.org/help/#encryption">end-to-end encryption</a> with various cloud services including Nextcloud, Dropbox, OneDrive and <a href="https://joplinapp.org/plans/">Joplin Cloud</a>.
Full text search is available on all platforms to quickly find the information you need. The app can be customised using plugins and themes, and you can also easily create your own.
The application is available for Windows, Linux, macOS, Android and iOS. A <a href="https://joplinapp.org/clipper/">Web Clipper</a>, to save web pages and screenshots from your browser, is also available for <a href="https://addons.mozilla.org/firefox/addon/joplin-web-clipper/">Firefox</a> and <a href="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek?hl=en-GB">Chrome</a>.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -1 +0,0 @@
a note taking and to-do app with sync between Linux, macOS, Windows, and mobile

View File

@@ -31,7 +31,7 @@
"linter": "eslint --resolve-plugins-relative-to . --fix --quiet --ext .js --ext .jsx --ext .ts --ext .tsx",
"postinstall": "gulp build",
"publishAll": "git pull && yarn run buildParallel && lerna version --yes --no-private --no-git-tag-version && gulp completePublishAll",
"releaseAndroid": "PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" node packages/tools/release-android.js",
"releaseAndroid": "yarn run buildParallel && export PATH=\"/usr/local/opt/openjdk@11/bin:$PATH\" && node packages/tools/release-android.js",
"releaseAndroidClean": "node packages/tools/release-android.js",
"releaseCli": "node packages/tools/release-cli.js",
"releaseClipper": "node packages/tools/release-clipper.js",

View File

@@ -313,10 +313,6 @@ async function fetchAllNotes() {
lines.push('');
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
lines.push('');
lines.push('Or to **update** a resource:');
lines.push('');
lines.push('\tcurl -X PUT -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my modified title"}\' http://localhost:41184/resources/8fe1417d7b184324bf6b0122b76c4696');
lines.push('');
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
lines.push('');
lines.push('**From a plugin** the syntax to create a resource is also a bit special:');
@@ -372,11 +368,6 @@ async function fetchAllNotes() {
lines.push(`Sets the properties of the ${singular} with ID :id`);
lines.push('');
if (model.type === BaseModel.TYPE_RESOURCE) {
lines.push('You may also update the file data by specifying a file (See `POST /resources` example).');
lines.push('');
}
lines.push(`## DELETE /${tableName}/:id`);
lines.push('');
lines.push(`Deletes the ${singular} with ID :id`);

View File

@@ -1,4 +0,0 @@
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="">
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="" title="some-title">
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="some-alt-text">
<img src=":/39e66095b2cd427e9f13464487514d2e" alt="some-alt-text" title="some-title">

View File

@@ -1,4 +0,0 @@
![](:/39e66095b2cd427e9f13464487514d2e)
![](:/39e66095b2cd427e9f13464487514d2e "some-title")
![some-alt-text](:/39e66095b2cd427e9f13464487514d2e)
![some-alt-text](:/39e66095b2cd427e9f13464487514d2e "some-title")

View File

@@ -63,15 +63,13 @@ class AppComponent extends Component {
contentScriptLoaded: false,
selectedTags: [],
contentScriptError: '',
newNoteId: null,
});
this.confirm_click = async () => {
this.confirm_click = () => {
const content = Object.assign({}, this.props.clippedContent);
content.tags = this.state.selectedTags.join(',');
content.parent_id = this.props.selectedFolderId;
const response = await bridge().sendContentToJoplin(content);
this.setState({ newNoteId: response.id });
bridge().sendContentToJoplin(content);
};
this.contentTitle_change = (event) => {
@@ -404,24 +402,6 @@ class AppComponent extends Component {
);
};
const openNewNoteButton = () => {
if (!this.state.newNoteId) { return null; } else {
return (
// The jopin:// link must be opened in a new tab. When it's opened for the first time, a system dialog will ask for the user's permission.
// The system dialog is too big to fit into the popup so the user will not be able to see the dialog buttons and get stuck.
<a
className="Button"
href={`joplin://x-callback-url/openNote?id=${encodeURIComponent(this.state.newNoteId)}`}
target="_blank"
onClick={() => this.setState({ newNoteId: null })}
>
Open newly created note
</a>
);
}
};
const tagDataListOptions = [];
for (let i = 0; i < this.props.tags.length; i++) {
const tag = this.props.tags[i];
@@ -457,7 +437,6 @@ class AppComponent extends Component {
</div>
{ warningComponent }
{ previewComponent }
{ openNewNoteButton() }
{ clipperStatusComp() }
</div>
);

View File

@@ -463,11 +463,9 @@ class Bridge {
// This is the perfect Heisenbug - it happens always when opening the popup the first time EXCEPT
// when the debugger is open. Then everything is working fine and the bug NEVER EVER happens,
// so it's impossible to understand what's going on.
const response = await this.clipperApiExec('POST', 'notes', { nounce: this.nounce_++ }, content);
await this.clipperApiExec('POST', 'notes', { nounce: this.nounce_++ }, content);
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });
return response;
} catch (error) {
if (error.message === '{"error":"Duplicate Nounce"}') {
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });

View File

@@ -446,9 +446,8 @@ class Application extends BaseApplication {
await this.checkForLegacyTemplates();
// Note: Auto-update is a misnomer in the code.
// The code below only checks, if a new version is available.
// We only allow Windows and macOS users to automatically check for updates
// Note: Auto-update currently doesn't work in Linux: it downloads the update
// but then doesn't install it on exit.
if (shim.isWindows() || shim.isMac()) {
const runAutoUpdateCheck = () => {
if (Setting.value('autoUpdateEnabled')) {

View File

@@ -9,13 +9,6 @@ interface LastSelectedPath {
directory: string;
}
interface OpenDialogOptions {
properties?: string[];
defaultPath?: string;
createDirectory?: boolean;
filters?: any[];
}
export class Bridge {
private electronWrapper_: ElectronAppWrapper;
@@ -162,14 +155,14 @@ export class Bridge {
return filePath;
}
async showOpenDialog(options: OpenDialogOptions = null) {
async showOpenDialog(options: any = null) {
const { dialog } = require('electron');
if (!options) options = {};
let fileType = 'file';
if (options.properties && options.properties.includes('openDirectory')) fileType = 'directory';
if (!('defaultPath' in options) && (this.lastSelectedPaths_ as any)[fileType]) options.defaultPath = (this.lastSelectedPaths_ as any)[fileType];
if (!('createDirectory' in options)) options.createDirectory = true;
const { filePaths } = await dialog.showOpenDialog(this.window(), options as any);
const { filePaths } = await dialog.showOpenDialog(this.window(), options);
if (filePaths && filePaths.length) {
(this.lastSelectedPaths_ as any)[fileType] = dirname(filePaths[0]);
}

View File

@@ -56,7 +56,7 @@ class ClipperConfigScreenComponent extends React.Component {
const containerStyle = Object.assign({}, theme.containerStyle, {
overflowY: 'scroll',
// padding: theme.configScreenPadding,
padding: theme.configScreenPadding,
backgroundColor: theme.backgroundColor3,
});

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { useCallback, useState, useRef, useEffect } from 'react';
import { useCallback, useState } from 'react';
import { _ } from '@joplin/lib/locale';
import DialogButtonRow, { ClickEvent } from '../DialogButtonRow';
import Dialog from '../Dialog';
@@ -8,11 +8,9 @@ import StyledInput from '../style/StyledInput';
import { IconSelector, ChangeEvent } from './IconSelector';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import Folder from '@joplin/lib/models/Folder';
import { FolderEntity, FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import { FolderEntity, FolderIcon } from '@joplin/lib/services/database/types';
import Button from '../Button/Button';
import bridge from '../../services/bridge';
import shim from '@joplin/lib/shim';
import FolderIconBox from '../FolderIconBox';
interface Props {
themeId: number;
@@ -24,7 +22,6 @@ interface Props {
export default function(props: Props) {
const [folderTitle, setFolderTitle] = useState('');
const [folderIcon, setFolderIcon] = useState<FolderIcon>();
const titleInputRef = useRef(null);
const isNew = !props.folderId;
@@ -44,14 +41,6 @@ export default function(props: Props) {
});
}, [props.dispatch]);
useEffect(() => {
titleInputRef.current.focus();
setTimeout(() => {
titleInputRef.current.select();
}, 100);
}, []);
const onButtonRowClick = useCallback(async (event: ClickEvent) => {
if (event.buttonName === 'cancel') {
onClose();
@@ -95,54 +84,23 @@ export default function(props: Props) {
setFolderIcon(null);
}, []);
const onBrowseClick = useCallback(async () => {
const filePaths = await bridge().showOpenDialog({
filters: [
{
name: _('Images'),
extensions: ['jpg', 'jpeg', 'png'],
},
],
});
if (filePaths.length !== 1) return;
const filePath = filePaths[0];
try {
const dataUrl = await shim.imageToDataUrl(filePath, 256);
setFolderIcon(icon => {
return {
...icon,
emoji: '',
name: '',
type: FolderIconType.DataUrl,
dataUrl,
};
});
} catch (error) {
await bridge().showErrorMessageBox(error.message);
}
}, []);
function renderForm() {
return (
<div>
<div className="form">
<div className="form-input-group">
<label>{_('Title')}</label>
<StyledInput type="text" ref={titleInputRef} value={folderTitle} onChange={onFolderTitleChange}/>
<StyledInput type="text" value={folderTitle} onChange={onFolderTitleChange}/>
</div>
<div className="form-input-group">
<label>{_('Icon')}</label>
<div className="icon-selector-row">
{ folderIcon && <div className="foldericon"><FolderIconBox folderIcon={folderIcon} /></div> }
<IconSelector
title={_('Select emoji...')}
icon={folderIcon}
onChange={onFolderIconChange}
/>
<Button ml={1} title={_('Select file...')} onClick={onBrowseClick}/>
{ folderIcon && <Button ml={1} title={_('Clear')} onClick={onClearClick}/> }
<Button ml={1} title={_('Clear')} onClick={onClearClick}/>
</div>
</div>
</div>

View File

@@ -3,7 +3,7 @@ import { useEffect, useState, useCallback, useRef } from 'react';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import { loadScript } from '../utils/loadScript';
import Button from '../Button/Button';
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import { FolderIcon } from '@joplin/lib/services/database/types';
import bridge from '../../services/bridge';
export interface ChangeEvent {
@@ -15,7 +15,6 @@ type ChangeHandler = (event: ChangeEvent)=> void;
interface Props {
onChange: ChangeHandler;
icon: FolderIcon | null;
title: string;
}
export const IconSelector = (props: Props) => {
@@ -63,7 +62,7 @@ export const IconSelector = (props: Props) => {
});
const onEmoji = (selection: FolderIcon) => {
props.onChange({ value: { ...selection, type: FolderIconType.Emoji } });
props.onChange({ value: selection });
};
p.on('emoji', onEmoji);
@@ -79,25 +78,16 @@ export const IconSelector = (props: Props) => {
picker.togglePicker(buttonRef.current);
}, [picker]);
// const buttonText = props.icon ? props.icon.emoji : '...';
const buttonText = props.icon ? props.icon.emoji : '...';
return (
<Button
disabled={!picker}
ref={buttonRef}
onClick={onClick}
title={props.title}
title={buttonText}
isSquare={true}
fontSize={20}
/>
);
// return (
// <Button
// disabled={!picker}
// ref={buttonRef}
// onClick={onClick}
// title={buttonText}
// isSquare={true}
// fontSize={20}
// />
// );
};

View File

@@ -1,13 +1,4 @@
.icon-selector-row {
display: flex;
flex-direction: row;
align-items: center;
}
.icon-selector-row > .foldericon {
margin-right: 5px;
display: flex;
border: 1px solid var(--joplin-divider-color);
padding: 5px;
background-color: var(--joplin-background-color);
}

View File

@@ -1,17 +0,0 @@
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
interface Props {
folderIcon: FolderIcon;
}
export default function(props: Props) {
const folderIcon = props.folderIcon;
if (folderIcon.type === FolderIconType.Emoji) {
return <span style={{ fontSize: 20 }}>{folderIcon.emoji}</span>;
} else if (folderIcon.type === FolderIconType.DataUrl) {
return <img style={{ width: 20, height: 20 }} src={folderIcon.dataUrl} />;
} else {
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
}
}

View File

@@ -5,7 +5,7 @@ export default function styles(themeId: number) {
return {
container: {
...theme.containerStyle,
// padding: theme.configScreenPadding,
padding: theme.configScreenPadding,
backgroundColor: theme.backgroundColor3,
},
actionsContainer: {

View File

@@ -347,12 +347,6 @@ function useMenu(props: Props) {
}
}
importItems.push({ type: 'separator' });
importItems.push({
label: _('Other applications...'),
click: () => { void bridge().openExternal('https://discourse.joplinapp.org/t/importing-notes-from-other-notebook-applications/22425'); },
});
exportItems.push(
menuItemDic.exportPdf
);
@@ -572,15 +566,8 @@ function useMenu(props: Props) {
menuItemDic.textPaste,
menuItemDic.textSelectAll,
separator(),
// Using the generic "undo"/"redo" roles mean the menu
// item will work in every text fields, whether it's the
// editor or a regular text field.
{
role: 'undo',
},
{
role: 'redo',
},
menuItemDic['editor.undo'],
menuItemDic['editor.redo'],
separator(),
menuItemDic.textBold,
menuItemDic.textItalic,

View File

@@ -67,7 +67,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
usePluginServiceRegistration(ref);
const { resetScroll, editor_scroll, setEditorPercentScroll, setViewerPercentScroll, editor_resize, editor_update, getLineScrollPercent,
const { resetScroll, editor_scroll, setEditorPercentScroll, setViewerPercentScroll, editor_resize,
} = useScrollHandler(editorRef, webviewRef, props.onScroll);
const codeMirror_change = useCallback((newBody: string) => {
@@ -515,9 +515,8 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
color: ${theme.searchMarkerColor} !important;
}
/* We need !important because the search marker is overridden by CodeMirror's own text selection marker */
.cm-search-marker-selected {
background: ${theme.selectedColor2} !important;
background: ${theme.selectedColor2};
color: ${theme.color2} !important;
}
@@ -577,14 +576,9 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const arg0 = args && args.length >= 1 ? args[0] : null;
if (msg.indexOf('checkboxclick:') === 0) {
const { line, from, to } = shared.toggleCheckboxRange(msg, props.content);
const newBody = shared.toggleCheckbox(msg, props.content);
if (editorRef.current) {
// To cancel CodeMirror's layout drift, the scroll position
// is recorded before updated, and then it is restored.
// Ref. https://github.com/laurent22/joplin/issues/5890
const percent = getLineScrollPercent();
editorRef.current.replaceRange(line, from, to);
setEditorPercentScroll(percent);
editorRef.current.updateBody(newBody);
}
} else if (msg === 'percentScroll') {
const percent = arg0;
@@ -732,19 +726,15 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
// It might be buggy, refer to the below issue
// https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703
useEffect(() => {
function pointerInsideEditor(params: any) {
const x = params.x, y = params.y, isEditable = params.isEditable, inputFieldType = params.inputFieldType;
function pointerInsideEditor(x: number, y: number) {
const elements = document.getElementsByClassName('codeMirrorEditor');
// inputFieldType: The input field type of CodeMirror is "textarea" so the inputFieldType = "none",
// and any single-line input above codeMirror has inputFieldType value according to the type of input e.g.(text = plainText, password = password, ...).
if (!elements.length || !isEditable || inputFieldType !== 'none') return null;
if (!elements.length) return null;
const rect = convertToScreenCoordinates(Setting.value('windowContentZoomFactor'), elements[0].getBoundingClientRect());
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y;
}
async function onContextMenu(_event: any, params: any) {
if (!pointerInsideEditor(params)) return;
if (!pointerInsideEditor(params.x, params.y)) return;
const menu = new Menu();
@@ -848,7 +838,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
onEditorPaste={onEditorPaste}
isSafeMode={props.isSafeMode}
onResize={editor_resize}
onUpdate={editor_update}
/>
</div>
);

View File

@@ -94,7 +94,6 @@ export interface EditorProps {
onEditorPaste: any;
isSafeMode: boolean;
onResize: any;
onUpdate: any;
}
function Editor(props: EditorProps, ref: any) {
@@ -149,14 +148,6 @@ function Editor(props: EditorProps, ref: any) {
event.dataTransfer.dropEffect = 'copy';
}, []);
const editor_resize = useCallback((cm: any) => {
props.onResize(cm);
}, [props.onResize]);
const editor_update = useCallback((cm: any) => {
props.onUpdate(cm);
}, [props.onUpdate]);
useEffect(() => {
if (!editorParent.current) return () => {};
@@ -199,8 +190,7 @@ function Editor(props: EditorProps, ref: any) {
cm.on('paste', editor_paste);
cm.on('drop', editor_drop);
cm.on('dragover', editor_drag);
cm.on('refresh', editor_resize);
cm.on('update', editor_update);
cm.on('refresh', props.onResize);
// It's possible for searchMarkers to be available before the editor
// In these cases we set the markers asap so the user can see them as
@@ -214,8 +204,7 @@ function Editor(props: EditorProps, ref: any) {
cm.off('paste', editor_paste);
cm.off('drop', editor_drop);
cm.off('dragover', editor_drag);
cm.off('refresh', editor_resize);
cm.off('update', editor_update);
cm.off('refresh', props.onResize);
editorParent.current.removeChild(cm.getWrapperElement());
setEditor(null);
};

View File

@@ -8,7 +8,6 @@ export default function useEditorSearch(CodeMirror: any) {
const [scrollbarMarks, setScrollbarMarks] = useState(null);
const [previousKeywordValue, setPreviousKeywordValue] = useState(null);
const [previousIndex, setPreviousIndex] = useState(null);
const [previousSearchTimestamp, setPreviousSearchTimestamp] = useState(0);
const [overlayTimeout, setOverlayTimeout] = useState(null);
const overlayTimeoutRef = useRef(null);
overlayTimeoutRef.current = overlayTimeout;
@@ -52,7 +51,7 @@ export default function useEditorSearch(CodeMirror: any) {
// Highlights the currently active found work
// It's possible to get tricky with this fucntions and just use findNext/findPrev
// but this is fast enough and works more naturally with the current search logic
function highlightSearch(cm: any, searchTerm: RegExp, index: number, scrollTo: boolean, withSelection: boolean) {
function highlightSearch(cm: any, searchTerm: RegExp, index: number, scrollTo: boolean) {
const cursor = cm.getSearchCursor(searchTerm);
let match: any = null;
@@ -65,13 +64,7 @@ export default function useEditorSearch(CodeMirror: any) {
}
if (match) {
if (scrollTo) {
if (withSelection) {
cm.setSelection(match.from, match.to);
} else {
cm.scrollTo(match);
}
}
if (scrollTo) cm.setSelection(match.from, match.to);
return cm.markText(match.from, match.to, { className: 'cm-search-marker-selected' });
}
@@ -97,7 +90,7 @@ export default function useEditorSearch(CodeMirror: any) {
CodeMirror.defineExtension('setMarkers', function(keywords: any, options: any) {
if (!options) {
options = { selectedIndex: 0, searchTimestamp: 0 };
options = { selectedIndex: 0 };
}
clearMarkers();
@@ -114,15 +107,16 @@ export default function useEditorSearch(CodeMirror: any) {
const searchTerm = getSearchTerm(keyword);
// We only want to scroll the first keyword into view in the case of a multi keyword search
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex ||
// If there is only one choice, scrollTo should be true. The below is a dummy of nMatches === 1.
options.selectedIndex === 0);
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo);
if (match) marks.push(match);
}
setMarkers(marks);
setPreviousIndex(options.selectedIndex);
setPreviousSearchTimestamp(options.searchTimestamp);
// SEARCHOVERLAY
// We only want to highlight all matches when there is only 1 search term

View File

@@ -7,10 +7,6 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
const ignoreNextEditorScrollTime_ = useRef(Date.now());
const ignoreNextEditorScrollEventCount_ = useRef(0);
const delayedSetEditorPercentScrollTimeoutID_ = useRef(null);
const scrollTopIsUncertain_ = useRef(true);
const lastResizeHeight_ = useRef(NaN);
const lastLinesHeight_ = useRef(NaN);
const restoreEditorPercentScrollTimeoutId_ = useRef<any>(null);
// Ignores one next scroll event for a short time.
const ignoreNextEditorScrollEvent = () => {
@@ -58,33 +54,23 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
if (isCodeMirrorReady(cm)) {
// calculates editor's GUI-dependent pixel-based raw percent
const newEditorPercent = translateScrollPercentL2E(cm, scrollPercent_.current);
const oldEditorPercent = scrollTopIsUncertain_.current ? NaN : cm.getScrollPercent();
const oldEditorPercent = cm.getScrollPercent();
if (!(Math.abs(newEditorPercent - oldEditorPercent) < 1e-8)) {
ignoreNextEditorScrollEvent();
cm.setScrollPercent(newEditorPercent);
}
scrollTopIsUncertain_.current = false;
} else {
retry += 1;
if (retry <= 3) {
if (retry <= 10) {
delayedSetEditorPercentScrollTimeoutID_.current = shim.setTimeout(fn, 50);
}
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
};
fn();
};
const restoreEditorPercentScroll = () => {
if (restoreEditorPercentScrollTimeoutId_.current) {
shim.clearTimeout(restoreEditorPercentScrollTimeoutId_.current);
restoreEditorPercentScrollTimeoutId_.current = null;
}
const cm = editorRef.current;
if (isCodeMirrorReady(cm)) {
lastLinesHeight_.current = cm.heightAtLine(cm.lineCount()) - cm.heightAtLine(0);
if (isCodeMirrorReady(editorRef.current)) {
setEditorPercentScrollInternal(scrollPercent_.current);
}
};
@@ -104,27 +90,22 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
}, [scheduleOnScroll]);
const editor_scroll = useCallback(() => {
const ignored = isNextEditorScrollEventIgnored();
if (isNextEditorScrollEventIgnored()) return;
const cm = editorRef.current;
if (isCodeMirrorReady(cm)) {
if (scrollTopIsUncertain_.current) return;
const editorPercent = Math.max(0, Math.min(1, cm.getScrollPercent()));
if (!isNaN(editorPercent)) {
// when switching to another note, the percent can sometimes be NaN
// this is coming from `gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.ts`
// when CodeMirror returns scroll info with heigth == clientHeigth
// https://github.com/laurent22/joplin/issues/4797
if (!ignored) {
// calculates GUI-independent line-based percent
const percent = translateScrollPercentE2L(cm, editorPercent);
scrollPercent_.current = percent;
setViewerPercentScroll(percent);
}
// calculates GUI-independent line-based percent
const percent = translateScrollPercentE2L(cm, editorPercent);
scrollPercent_.current = percent;
setViewerPercentScroll(percent);
}
} else {
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
}, [setViewerPercentScroll]);
@@ -132,60 +113,17 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
scrollPercent_.current = 0;
if (editorRef.current) {
editorRef.current.setScrollPercent(0);
scrollTopIsUncertain_.current = false;
}
}, []);
const editor_resize = useCallback((cm) => {
if (isCodeMirrorReady(cm)) {
// This handler is called when resized and refreshed.
// Only when resized, the scroll position is restored.
const info = cm.getScrollInfo();
const height = info.height - info.clientHeight;
if (height !== lastResizeHeight_.current) {
// When resized, restoring is performed immediately.
restoreEditorPercentScroll();
lastResizeHeight_.current = height;
}
} else {
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
}, []);
// When heights of lines are updated in CodeMirror, 'update' events are raised.
// If such an update event is raised, scroll position should be restored.
// See https://github.com/laurent22/joplin/issues/5981
const editor_update = useCallback((cm) => {
if (isCodeMirrorReady(cm)) {
const linesHeight = cm.heightAtLine(cm.lineCount()) - cm.heightAtLine(0);
if (lastLinesHeight_.current !== linesHeight) {
// To avoid cancelling intentional scroll position changes,
// restoring is performed in a timeout handler.
if (!restoreEditorPercentScrollTimeoutId_.current) {
restoreEditorPercentScrollTimeoutId_.current = shim.setTimeout(restoreEditorPercentScroll, 10);
}
}
} else {
scrollTopIsUncertain_.current = true;
lastResizeHeight_.current = NaN;
lastLinesHeight_.current = NaN;
}
}, []);
const getLineScrollPercent = useCallback(() => {
const cm = editorRef.current;
if (isCodeMirrorReady(cm)) {
const ePercent = cm.getScrollPercent();
return translateScrollPercentE2L(cm, ePercent);
} else {
return scrollPercent_.current;
if (cm) {
restoreEditorPercentScroll();
}
}, []);
return {
resetScroll, setEditorPercentScroll, setViewerPercentScroll, editor_scroll, editor_resize, editor_update, getLineScrollPercent,
resetScroll, setEditorPercentScroll, setViewerPercentScroll, editor_scroll, editor_resize,
};
}

View File

@@ -3,8 +3,7 @@ import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
import { useEffect } from 'react';
import bridge from '../../../../../services/bridge';
import { ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenuUtils';
import { menuItems } from '../../../utils/contextMenu';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenu';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
import CommandService from '@joplin/lib/services/CommandService';
import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordinates';
@@ -68,8 +67,6 @@ export default function(editor: any, plugins: PluginStates, dispatch: Function)
contextMenuActionOptions.current = {
itemType,
resourceId,
filename: null,
mime: null,
linkToCopy,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',

View File

@@ -93,11 +93,11 @@ const declarations: CommandDeclaration[] = [
},
{
name: 'editor.undo',
label: () => _('Editor: %s', _('Undo')),
label: () => _('Undo'),
},
{
name: 'editor.redo',
label: () => _('Editor: %s', _('Redo')),
label: () => _('Redo'),
},
{
name: 'editor.indentLess',

View File

@@ -1,41 +0,0 @@
/** @jest-environment ./loadResources.testEnv */
// eslint-disable-next-line strict, lines-around-directive
'use strict';
// use strict is necessary here so that typescript doesn't place "use strict" above the jest docblock
// https://github.com/microsoft/TypeScript/issues/15819#issuecomment-782235619
import { textToDataUri, svgUriToPng } from './contextMenuUtils';
jest.mock('@joplin/lib/models/Resource');
describe('contextMenu', () => {
it('should provide proper copy path', async () => {
const testCase = [
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">test</svg>',
'image/svg+xml',
];
const expectedText = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIj50ZXN0PC9zdmc+';
expect(textToDataUri(testCase[0], testCase[1])).toBe(expectedText);
});
it('should convert to png binary', async () => {
const testCase = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEwMCAxMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiIGhlaWdodD0iMTAwcHgiIHdpZHRoPSIxMDBweCI+CjxnPgoJPHBhdGggZD0iTTI4LjEsMzYuNmM0LjYsMS45LDEyLjIsMS42LDIwLjksMS4xYzguOS0wLjQsMTktMC45LDI4LjksMC45YzYuMywxLjIsMTEuOSwzLjEsMTYuOCw2Yy0xLjUtMTIuMi03LjktMjMuNy0xOC42LTMxLjMgICBjLTQuOS0wLjItOS45LDAuMy0xNC44LDEuNEM0Ny44LDE3LjksMzYuMiwyNS42LDI4LjEsMzYuNnoiLz4KCTxwYXRoIGQ9Ik03MC4zLDkuOEM1Ny41LDMuNCw0Mi44LDMuNiwzMC41LDkuNWMtMyw2LTguNCwxOS42LTUuMywyNC45YzguNi0xMS43LDIwLjktMTkuOCwzNS4yLTIzLjFDNjMuNywxMC41LDY3LDEwLDcwLjMsOS44eiIvPgoJPHBhdGggZD0iTTE2LjUsNTEuM2MwLjYtMS43LDEuMi0zLjQsMi01LjFjLTMuOC0zLjQtNy41LTctMTEtMTAuOGMtMi4xLDYuMS0yLjgsMTIuNS0yLjMsMTguN0M5LjYsNTEuMSwxMy40LDUwLjIsMTYuNSw1MS4zeiIvPgoJPHBhdGggZD0iTTksMzEuNmMzLjUsMy45LDcuMiw3LjYsMTEuMSwxMS4xYzAuOC0xLjYsMS43LTMuMSwyLjYtNC42YzAuMS0wLjIsMC4zLTAuNCwwLjQtMC42Yy0yLjktMy4zLTMuMS05LjItMC42LTE3LjYgICBjMC44LTIuNywxLjgtNS4zLDIuNy03LjRjLTUuMiwzLjQtOS44LDgtMTMuMywxMy43QzEwLjgsMjcuOSw5LjgsMjkuNyw5LDMxLjZ6Ii8+Cgk8cGF0aCBkPSJNMTUuNCw1NC43Yy0yLjYtMS02LjEsMC43LTkuNywzLjRjMS4yLDYuNiwzLjksMTMsOCwxOC41QzEzLDY5LjMsMTMuNSw2MS44LDE1LjQsNTQuN3oiLz4KCTxwYXRoIGQ9Ik0zOS44LDU3LjZDNTQuMyw2Ni43LDcwLDczLDg2LjUsNzYuNGMwLjYtMC44LDEuMS0xLjYsMS43LTIuNWM0LjgtNy43LDctMTYuMyw2LjgtMjQuOGMtMTMuOC05LjMtMzEuMy04LjQtNDUuOC03LjcgICBjLTkuNSwwLjUtMTcuOCwwLjktMjMuMi0xLjdjLTAuMSwwLjEtMC4yLDAuMy0wLjMsMC40Yy0xLDEuNy0yLDMuNC0yLjksNS4xQzI4LjIsNDkuNywzMy44LDUzLjksMzkuOCw1Ny42eiIvPgoJPHBhdGggZD0iTTI2LjIsODguMmMzLjMsMiw2LjcsMy42LDEwLjIsNC43Yy0zLjUtNi4yLTYuMy0xMi42LTguOC0xOC41Yy0zLjEtNy4yLTUuOC0xMy41LTktMTcuMmMtMS45LDgtMiwxNi40LTAuMywyNC43ICAgQzIwLjYsODQuMiwyMy4yLDg2LjMsMjYuMiw4OC4yeiIvPgoJPHBhdGggZD0iTTMwLjksNzNjMi45LDYuOCw2LjEsMTQuNCwxMC41LDIxLjJjMTUuNiwzLDMyLTIuMyw0Mi42LTE0LjZDNjcuNyw3Niw1Mi4yLDY5LjYsMzcuOSw2MC43QzMyLDU3LDI2LjUsNTMsMjEuMyw0OC42ICAgYy0wLjYsMS41LTEuMiwzLTEuNyw0LjZDMjQuMSw1Ny4xLDI3LjMsNjQuNSwzMC45LDczeiIvPgo8L2c+Cjwvc3ZnPg==';
const png = await svgUriToPng(document, testCase);
expect(png).toBeInstanceOf(Uint8Array);
});
it('should throw error on invalid svg uri', async () => {
// We are mocking console.error since jsdom throws errors to console when we try to load an invalid img
// https://github.com/facebook/jest/pull/5267#issuecomment-356605468
const consoleError = console.error;
console.error = jest.fn();
const testCases: Array<string> = [
'data:image/svg+xml;base64,error',
'invalid',
];
for (const testCase of testCases) {
await expect(svgUriToPng(document, testCase)).rejects.toBeInstanceOf(Error);
}
console.error = consoleError;
});
});

View File

@@ -2,7 +2,6 @@ import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'
import { _ } from '@joplin/lib/locale';
import { copyHtmlToClipboard } from './clipboardUtils';
import bridge from '../../../services/bridge';
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
import Resource from '@joplin/lib/models/Resource';
@@ -11,10 +10,43 @@ import BaseModel from '@joplin/lib/BaseModel';
import { processPastedHtml } from './resourceHandling';
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
const fs = require('fs-extra');
const { writeFile } = require('fs-extra');
const { clipboard } = require('electron');
const { toSystemSlashes } = require('@joplin/lib/path-utils');
export enum ContextMenuItemType {
None = '',
Image = 'image',
Resource = 'resource',
Text = 'text',
Link = 'link',
}
export interface ContextMenuOptions {
itemType: ContextMenuItemType;
resourceId: string;
linkToCopy: string;
textToCopy: string;
htmlToCopy: string;
insertContent: Function;
isReadOnly?: boolean;
}
interface ContextMenuItem {
label: string;
onAction: Function;
isActive: Function;
}
interface ContextMenuItems {
[key: string]: ContextMenuItem;
}
async function resourceInfo(options: ContextMenuOptions): Promise<any> {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const resourcePath = resource ? Resource.fullPath(resource) : '';
return { resource, resourcePath };
}
function handleCopyToClipboard(options: ContextMenuOptions) {
if (options.textToCopy) {
clipboard.writeText(options.textToCopy);
@@ -23,12 +55,6 @@ function handleCopyToClipboard(options: ContextMenuOptions) {
}
}
async function saveFileData(data: any, filename: string) {
const newFilePath = await bridge().showSaveDialog({ defaultPath: filename });
if (!newFilePath) return;
await writeFile(newFilePath, data);
}
export async function openItemById(itemId: string, dispatch: Function, hash: string = '') {
const item = await BaseItem.loadItemById(itemId);
@@ -74,7 +100,7 @@ export function menuItems(dispatch: Function): ContextMenuItems {
onAction: async (options: ContextMenuOptions) => {
await openItemById(options.resourceId, dispatch);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
},
saveAs: {
label: _('Save as...'),
@@ -86,32 +112,7 @@ export function menuItems(dispatch: Function): ContextMenuItems {
if (!filePath) return;
await fs.copy(resourcePath, filePath);
},
// We handle images received as text seperately as it can be saved in multiple formats
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && (itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource),
},
saveAsSvg: {
label: _('Save as SVG'),
onAction: async (options: ContextMenuOptions) => {
await saveFileData(options.textToCopy, options.filename);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
},
saveAsPng: {
label: _('Save as PNG'),
onAction: async (options: ContextMenuOptions) => {
// First convert it to png then save
if (options.mime != 'image/svg+xml') {
throw new Error(`Unsupported image type: ${options.mime}`);
}
if (!options.filename) {
throw new Error('Filename is needed to save as png');
}
const dataUri = textToDataUri(options.textToCopy, options.mime);
const png = await svgUriToPng(document, dataUri);
const filename = options.filename.replace('.svg', '.png');
await saveFileData(png, filename);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy && itemType === ContextMenuItemType.Image && options.mime?.startsWith('image/svg'),
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
},
revealInFolder: {
label: _('Reveal file in folder'),
@@ -119,20 +120,13 @@ export function menuItems(dispatch: Function): ContextMenuItems {
const { resourcePath } = await resourceInfo(options);
bridge().showItemInFolder(resourcePath);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
},
copyPathToClipboard: {
label: _('Copy path to clipboard'),
onAction: async (options: ContextMenuOptions) => {
let path = '';
if (options.textToCopy && options.mime) {
path = textToDataUri(options.textToCopy, options.mime);
} else {
const { resourcePath } = await resourceInfo(options);
if (resourcePath) path = toSystemSlashes(resourcePath);
}
if (!path) return;
clipboard.writeText(path);
const { resourcePath } = await resourceInfo(options);
clipboard.writeText(toSystemSlashes(resourcePath));
},
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource,
},
@@ -143,7 +137,7 @@ export function menuItems(dispatch: Function): ContextMenuItems {
const image = bridge().createImageFromPath(resourcePath);
clipboard.writeImage(image);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image,
isActive: (itemType: ContextMenuItemType) => itemType === ContextMenuItemType.Image,
},
cut: {
label: _('Cut'),
@@ -151,14 +145,14 @@ export function menuItems(dispatch: Function): ContextMenuItems {
handleCopyToClipboard(options);
options.insertContent('');
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType != ContextMenuItemType.Image && (!options.isReadOnly && (!!options.textToCopy || !!options.htmlToCopy)),
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!options.textToCopy || !!options.htmlToCopy),
},
copy: {
label: _('Copy'),
onAction: async (options: ContextMenuOptions) => {
handleCopyToClipboard(options);
},
isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => itemType != ContextMenuItemType.Image && (!!options.textToCopy || !!options.htmlToCopy),
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !!options.textToCopy || !!options.htmlToCopy,
},
paste: {
label: _('Paste'),
@@ -190,6 +184,7 @@ export default async function contextMenu(options: ContextMenuOptions, dispatch:
const items = menuItems(dispatch);
if (!('readyOnly' in options)) options.isReadOnly = true;
for (const itemKey in items) {
const item = items[itemKey];

View File

@@ -1,92 +0,0 @@
import Resource from '@joplin/lib/models/Resource';
export enum ContextMenuItemType {
None = '',
Image = 'image',
Resource = 'resource',
Text = 'text',
Link = 'link',
}
export interface ContextMenuOptions {
itemType: ContextMenuItemType;
resourceId: string;
mime: string;
filename: string;
linkToCopy: string;
textToCopy: string;
htmlToCopy: string;
insertContent: Function;
isReadOnly?: boolean;
}
export interface ContextMenuItem {
label: string;
onAction: Function;
isActive: Function;
}
export interface ContextMenuItems {
[key: string]: ContextMenuItem;
}
export async function resourceInfo(options: ContextMenuOptions): Promise<any> {
const resource = options.resourceId ? await Resource.load(options.resourceId) : null;
const filePath = resource ? Resource.fullPath(resource) : null;
const filename = resource ? (resource.filename ? resource.filename : resource.title) : options.filename ? options.filename : '';
return { resource, filePath, filename };
}
export function textToDataUri(text: string, mime: string): string {
return `data:${mime};base64,${Buffer.from(text).toString('base64')}`;
}
export const svgUriToPng = (document: Document, svg: string) => {
return new Promise<Uint8Array>((resolve, reject) => {
let canvas: HTMLCanvasElement;
let img: HTMLImageElement;
const cleanUpAndReject = (e: Error) => {
if (canvas) canvas.remove();
if (img) img.remove();
return reject(e);
};
try {
img = document.createElement('img');
if (!img) throw new Error('Failed to create img element');
} catch (e) {
return cleanUpAndReject(e);
}
img.onload = function() {
try {
canvas = document.createElement('canvas');
if (!canvas) throw new Error('Failed to create canvas element');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get context');
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
const pngUri = canvas.toDataURL('image/png');
if (!pngUri) throw new Error('Failed to generate png uri');
const pngBase64 = pngUri.split(',')[1];
const byteString = atob(pngBase64);
// write the bytes of the string to a typed array
const buff = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
buff[i] = byteString.charCodeAt(i);
}
canvas.remove();
img.remove();
resolve(buff);
} catch (err) {
cleanUpAndReject(err);
}
};
img.onerror = function(e) {
cleanUpAndReject(new Error(e.toString()));
};
img.src = svg;
});
};

View File

@@ -35,8 +35,6 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
const menu = await contextMenu({
itemType: arg0 && arg0.type,
resourceId: arg0.resourceId,
filename: arg0.filename,
mime: arg0.mime,
textToCopy: arg0.textToCopy,
linkToCopy: arg0.linkToCopy || null,
htmlToCopy: '',

View File

@@ -92,7 +92,6 @@ export default function useNoteSearchBar() {
selectedIndex: localSearch.selectedIndex,
separateWordSearch: false,
searchTimestamp: localSearch.timestamp,
withSelection: true,
},
keywords: [
{

View File

@@ -4,7 +4,6 @@ interface SearchMarkersOptions {
searchTimestamp: number;
selectedIndex: number;
separateWordSearch: boolean;
withSelection?: boolean;
}
export interface SearchMarkers {

View File

@@ -167,8 +167,8 @@ class NoteSearchBarComponent extends React.Component {
type="text"
style={{ width: 200, marginRight: 5, backgroundColor: this.backgroundColor, color: theme.color }}
/>
{allowScrolling ? previousButton : null}
{allowScrolling ? nextButton : null}
{allowScrolling ? previousButton : null}
{allowScrolling ? matchesFoundString : null}
{!allowScrolling ? viewerWarning : null}
</div>

View File

@@ -56,7 +56,7 @@ class PromptDialog extends React.Component {
top: 0,
left: 0,
width: width,
height: height,
height: height - paddingTop,
backgroundColor: 'rgba(0,0,0,0.6)',
display: visible ? 'flex' : 'none',
alignItems: 'flex-start',

View File

@@ -15,7 +15,6 @@ export const Root = styled.div`
position: relative;
display: flex;
width: 100%;
min-width: 30px;
`;
interface Props {
@@ -139,21 +138,6 @@ function SearchBar(props: Props) {
}
}, [props.notesParentType, onExitSearch]);
// When the searchbar is remounted, exit the search if it was previously open
// or else other buttons stay hidden (e.g. when opening Layout Editor and closing it)
// https://github.com/laurent22/joplin/issues/5953
useEffect(() => {
if (props.notesParentType === 'Search' || props.isFocused) {
if (props.isFocused) {
props.dispatch({
type: 'FOCUS_CLEAR',
field: 'globalSearch',
});
}
void onExitSearch(true);
}
}, []);
return (
<Root className="search-bar">
<SearchInput

View File

@@ -17,12 +17,11 @@ import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note';
import Tag from '@joplin/lib/models/Tag';
import Logger from '@joplin/lib/Logger';
import { FolderEntity, FolderIcon } from '@joplin/lib/services/database/types';
import { FolderEntity } from '@joplin/lib/services/database/types';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import { store } from '@joplin/lib/reducer';
import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService';
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import FolderIconBox from '../FolderIconBox';
const { connect } = require('react-redux');
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
const { themeStyle } = require('@joplin/lib/theme');
@@ -78,12 +77,6 @@ function ExpandLink(props: any) {
);
}
const renderFolderIcon = (folderIcon: FolderIcon) => {
if (!folderIcon) return null;
return <div style={{ marginRight: 5, display: 'flex' }}><FolderIconBox folderIcon={folderIcon}/></div>;
};
function FolderItem(props: any) {
const { hasChildren, isExpanded, parentId, depth, selected, folderId, folderTitle, folderIcon, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_, shareId } = props;
@@ -91,6 +84,8 @@ function FolderItem(props: any) {
const shareIcon = shareId && !parentId ? <StyledShareIcon className="fas fa-share-alt"></StyledShareIcon> : null;
const icon = folderIcon ? <span style={{ fontSize: 20, marginRight: 5 }}>{folderIcon.emoji}</span> : null;
return (
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth} ${selected ? 'selected' : ''}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
@@ -110,7 +105,7 @@ function FolderItem(props: any) {
}}
onDoubleClick={onFolderToggleClick_}
>
{renderFolderIcon(folderIcon)}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
{icon}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
{shareIcon} {noteCountComp}
</StyledListItemAnchor>
</StyledListItem>

View File

@@ -193,36 +193,34 @@ export default function(props: Props) {
const onJoplinCloudLoginClick = useCallback(async () => {
setJoplinCloudLoginInProgress(true);
let result = null;
try {
result = await SyncTargetJoplinCloud.checkConfig({
const result = await SyncTargetJoplinCloud.checkConfig({
password: () => joplinCloudPassword,
path: () => Setting.value('sync.10.path'),
userContentPath: () => Setting.value('sync.10.userContentPath'),
username: () => joplinCloudEmail,
});
if (result.ok) {
Setting.setValue('sync.target', 10);
Setting.setValue('sync.10.username', joplinCloudEmail);
Setting.setValue('sync.10.password', joplinCloudPassword);
await Setting.saveAll();
alert(_('Thank you! Your Joplin Cloud account is now setup and ready to use.'));
closeDialog(props.dispatch);
props.dispatch({
type: 'NAV_GO',
routeName: 'Main',
});
} else {
alert(_('There was an error setting up your Joplin Cloud account. Please verify your email and password and try again. Error was:\n\n%s', result.errorMessage));
}
} finally {
setJoplinCloudLoginInProgress(false);
}
if (result.ok) {
Setting.setValue('sync.target', 10);
Setting.setValue('sync.10.username', joplinCloudEmail);
Setting.setValue('sync.10.password', joplinCloudPassword);
await Setting.saveAll();
alert(_('Thank you! Your Joplin Cloud account is now setup and ready to use.'));
closeDialog(props.dispatch);
props.dispatch({
type: 'NAV_GO',
routeName: 'Main',
});
} else {
alert(_('There was an error setting up your Joplin Cloud account. Please verify your email and password and try again. Error was:\n\n%s', result.errorMessage));
}
}, [joplinCloudEmail, joplinCloudPassword, props.dispatch]);
const onJoplinCloudCreateAccountClick = useCallback(() => {
@@ -232,10 +230,10 @@ export default function(props: Props) {
function renderJoplinCloudLoginForm() {
return (
<JoplinCloudLoginForm>
<div style={{ fontSize: '16px' }}>{_('Login below.')} <CreateAccountLink href="#" onClick={onJoplinCloudCreateAccountClick}>{_('Or create an account.')}</CreateAccountLink></div>
<FormLabel>{_('Email')}</FormLabel>
<div>{_('Login below.')} <CreateAccountLink href="#" onClick={onJoplinCloudCreateAccountClick}>{_('Or create an account.')}</CreateAccountLink></div>
<FormLabel>Email</FormLabel>
<StyledInput type="email" onChange={onJoplinCloudEmailChange}/>
<FormLabel>{_('Password')}</FormLabel>
<FormLabel>Password</FormLabel>
<StyledInput type="password" onChange={onJoplinCloudPasswordChange}/>
<SelectButton mt="1.3em" disabled={joplinCloudLoginInProgress} level={ButtonLevel.Primary} title={_('Login')} onClick={onJoplinCloudLoginClick}/>
</JoplinCloudLoginForm>

View File

@@ -1,15 +1,14 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('@joplin/lib/theme');
const CommandService = require('@joplin/lib/services/CommandService').default;
class TagItemComponent extends React.Component {
render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.tagStyle);
const { title, id } = this.props;
const title = this.props.title;
return <button style={style} onClick={() => CommandService.instance().execute('openTag', id)}>{title}</button>;
return <span style={style}>{title}</span>;
}
}

View File

@@ -42,7 +42,6 @@ function TagList(props: Props) {
for (let i = 0; i < tags.length; i++) {
const props = {
title: tags[i].title,
id: tags[i].id,
key: tags[i].id,
};
output.push(<TagItem {...props} />);

View File

@@ -40,13 +40,8 @@ export default function() {
'toggleVisiblePanes',
'editor.deleteLine',
'editor.duplicateLine',
// We cannot put the undo/redo commands in the menu because they are
// editor-specific commands. If we put them there it will break the
// undo/redo in regular text fields.
// https://github.com/laurent22/joplin/issues/6214
// 'editor.undo',
// 'editor.redo',
'editor.undo',
'editor.redo',
'editor.indentLess',
'editor.indentMore',
'editor.toggleComment',

View File

@@ -277,9 +277,6 @@
let restoreAndRefreshTimeoutID_ = null;
let restoreAndRefreshTimeout_ = Date.now();
// If 'noteRenderComplete' message is ongoing, resizing should not trigger a 'percentScroll' messsage.
let noteRenderCompleteMessageIsOngoing_ = false;
// A callback anonymous function invoked when the scroll height changes.
const onRendering = observeRendering((cause, height, heightChanged) => {
if (!alreadyAllImagesLoaded && !scrollmap.isPresent()) {
@@ -288,7 +285,6 @@
alreadyAllImagesLoaded = true;
scrollmap.refresh();
restorePercentScroll();
noteRenderCompleteMessageIsOngoing_ = true;
ipcProxySendToHost('noteRenderComplete');
return;
}
@@ -297,8 +293,6 @@
const restoreAndRefresh = () => {
scrollmap.refresh();
restorePercentScroll();
// To ensures Editor's scroll position is synced with Viewer's
if (!noteRenderCompleteMessageIsOngoing_) ipcProxySendToHost('percentScroll', percentScroll_);
};
const now = Date.now();
if (now < restoreAndRefreshTimeout_) {
@@ -349,13 +343,11 @@
if (scrollmap.isPresent()) {
// Now, ready to receive scrollToHash/setPercentScroll from Editor.
noteRenderCompleteMessageIsOngoing_ = true;
ipcProxySendToHost('noteRenderComplete');
}
}
ipc.setPercentScroll = (event) => {
noteRenderCompleteMessageIsOngoing_ = false;
setPercentScroll(event.percent);
}
@@ -586,24 +578,9 @@
}));
document.addEventListener('contextmenu', webviewLib.logEnabledEventHandler(event => {
// To handle right clicks on resource icons
let element = event.target;
// Mermaid svgs are wrapped inside a <pre> with class "mermaid"
let mermaidElement = element.closest(".mermaid")?.children[0];
if (mermaidElement) {
const svgString = new XMLSerializer().serializeToString(mermaidElement);
if (!!svgString) {
ipcProxySendToHost('contextMenu', {
type: 'image',
textToCopy: svgString,
mime: 'image/svg+xml',
filename: mermaidElement.id + '.svg',
});
}
return;
}
// To handle right clicks on resource icons
if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;
if (element && element.getAttribute('data-resource-id')) {

View File

@@ -1,20 +0,0 @@
/**
* A Jest custom test Environment to load the resources for the tests.
* Use this test envirenment when you work with resources like images, files.
* See gui/NoteEditor/utils/contextMenu.test.ts for an example.
*/
const JSDOMEnvironment = require('jest-environment-jsdom');
import type { EnvironmentContext } from '@jest/environment';
import type { Config } from '@jest/types';
export default class CustomEnvironment extends JSDOMEnvironment {
constructor(config: Config.ProjectConfig, context?: EnvironmentContext) {
// Resources is set to 'usable' to enable fetching of resources like images and fonts while testing
// Which does not happen by default in jest
// https://stackoverflow.com/a/49482563
config.testEnvironmentOptions.resources = 'usable';
super(config, context);
}
}

View File

@@ -151,7 +151,7 @@ General classes
body, button {
color: var(--joplin-color);
font-size: 13px;
font-size: 16px;
}
div, span, a {
@@ -159,7 +159,7 @@ div, span, a {
}
h2 {
font-size: 20px;
font-size: 24px;
&.-no-top-margin {
margin-top: 0;
@@ -193,7 +193,7 @@ div.form,
p {
&.-small {
font-size: 11px;
font-size: 13px;
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.7.14",
"version": "2.7.0",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -67,7 +67,7 @@
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": false,
"allowToChangeInstallationDirectory": true,
"differentialPackage": false
},
"portable": {
@@ -116,7 +116,6 @@
"app-builder-bin": "^1.9.11",
"babel-cli": "^6.26.0",
"babel-preset-react": "^6.24.1",
"canvas": "^2.9.0",
"electron": "14.1.0",
"electron-builder": "^22.11.7",
"electron-notarize": "^1.0.0",

View File

@@ -19,7 +19,6 @@ const { mergeOverlappingIntervals } = require('@joplin/lib/ArrayUtils.js');
import markupLanguageUtils from '../utils/markupLanguageUtils';
import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
import Logger from '@joplin/lib/Logger';
import { MarkupToHtml } from '@joplin/renderer';
const logger = Logger.create('GotoAnything');
@@ -82,7 +81,7 @@ class Dialog extends React.PureComponent<Props, State> {
private inputRef: any;
private itemListRef: any;
private listUpdateIID_: any;
private markupToHtml_: MarkupToHtml;
private markupToHtml_: any;
private userCallback_: any = null;
constructor(props: Props) {

View File

@@ -10,12 +10,6 @@
# ./runForTesting.sh 1 createUsers,createData,reset,e2ee,sync && ./runForTesting.sh 2 reset,e2ee,sync && ./runForTesting.sh 1
# ----------------------------------------------------------------------------------
# First user has E2EE, but second one doesn't:
# ----------------------------------------------------------------------------------
# ./runForTesting.sh 1 createUsers,createData,reset,e2ee,sync && ./runForTesting.sh 2 reset,sync && ./runForTesting.sh 1
# ----------------------------------------------------------------------------------
# Without E2EE:
# ----------------------------------------------------------------------------------

View File

@@ -1,4 +1,5 @@
import Logger from '@joplin/lib/Logger';
import time from '@joplin/lib/time';
const logger = Logger.create('BackOffHandler');
@@ -51,21 +52,13 @@ export default class BackOffHandler {
this.waitCount_++;
// For now don't actually apply a backoff and don't abort.
logger.warn(`Plugin ${this.pluginId_}: Applying a backoff of ${interval} seconds due to frequent plugin API calls. Consider reducing the number of calls, caching the data, or requesting more data per call. API call was: `, path, args, `[Wait count: ${this.waitCount_}]`);
if (this.waitCount_ > this.maxWaitCount_) logger.error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
await time.sleep(interval);
this.waitCount_--;
// if (this.waitCount_ > this.maxWaitCount_) throw new Error(`Plugin ${this.pluginId_}: More than ${this.maxWaitCount_} API alls are waiting - aborting. Please consider queuing the API calls in your plugins to reduce the load on the application.`);
// await time.sleep(interval);
// this.waitCount_--;
}
}

View File

@@ -40,7 +40,6 @@ export default class PerFolderSortOrderService {
this.loadSharedSortOrder();
eventManager.appStateOn('notesParentType', this.onFolderSelectionMayChange.bind(this, 'notesParentType'));
eventManager.appStateOn('selectedFolderId', this.onFolderSelectionMayChange.bind(this, 'selectedFolderId'));
this.previousFolderId = Setting.value('activeFolderId');
}
public static isSet(folderId: string): boolean {

View File

@@ -30,10 +30,7 @@ function convertJsx(path) {
if (fileIsNewerThan(jsxPath, jsPath)) {
console.info(`Compiling ${jsxPath}...`);
// { shell: true } is needed to get it working on Windows:
// https://discourse.joplinapp.org/t/attempting-to-build-on-windows/22559/12
const result = spawnSync('yarn', ['run', 'babel', '--presets', 'react', '--out-file', jsPath, jsxPath], { shell: true });
const result = spawnSync('yarn', ['run', 'babel', '--presets', 'react', '--out-file', jsPath, jsxPath]);
if (result.status !== 0) {
const msg = [];
if (result.stdout) msg.push(result.stdout.toString());

View File

@@ -146,8 +146,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097667
versionName "2.7.2"
versionCode 2097665
versionName "2.7.0"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -210,7 +210,7 @@ class CameraView extends Component {
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-end' }}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginBottom: 20 }}>
{ reverseCameraButton }
<TouchableOpacity onPress={this.photo_onPress} disabled={this.state.snapping}>
<TouchableOpacity onPress={this.photo_onPress}>
<View style={{ flexDirection: 'row', borderRadius: 90, width: 90, height: 90, backgroundColor: '#ffffffaa', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Icon
name={photoIcon}

View File

@@ -103,7 +103,7 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
return (
<View style={{ flex: 0, margin: 20, alignItems: 'center' }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{ this.state.date && <Text style={{ ...theme.normalText,color: theme.color, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
{ this.state.date && <Text style={{ ...theme.normalText, marginRight: 10 }}>{time.formatDateToLocal(this.state.date)}</Text> }
<Button title="Set date" onPress={this.onSetDate} />
</View>
<DateTimePickerModal

View File

@@ -93,6 +93,9 @@ class NoteScreenComponent extends BaseScreenComponent {
this.doFocusUpdate_ = false;
// iOS doesn't support multiline text fields properly so disable it
this.enableMultilineTitle_ = Platform.OS !== 'ios';
this.saveButtonHasBeenShown_ = false;
this.styles_ = {};
@@ -228,6 +231,7 @@ class NoteScreenComponent extends BaseScreenComponent {
this.onAlarmDialogAccept = this.onAlarmDialogAccept.bind(this);
this.onAlarmDialogReject = this.onAlarmDialogReject.bind(this);
this.todoCheckbox_change = this.todoCheckbox_change.bind(this);
this.titleTextInput_contentSizeChange = this.titleTextInput_contentSizeChange.bind(this);
this.title_changeText = this.title_changeText.bind(this);
this.undoRedoService_stackChange = this.undoRedoService_stackChange.bind(this);
this.screenHeader_undoButtonPress = this.screenHeader_undoButtonPress.bind(this);
@@ -385,6 +389,7 @@ class NoteScreenComponent extends BaseScreenComponent {
paddingBottom: 10, // Added for iOS (Not needed for Android??)
};
if (this.enableMultilineTitle_) styles.titleTextInput.height = this.state.titleTextInputHeight;
if (this.state.HACK_webviewLoadingState === 1) styles.titleTextInput.marginTop = 1;
this.styles_[cacheKey] = StyleSheet.create(styles);
@@ -966,6 +971,13 @@ class NoteScreenComponent extends BaseScreenComponent {
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
}
titleTextInput_contentSizeChange(event: any) {
if (!this.enableMultilineTitle_) return;
const height = event.nativeEvent.contentSize.height;
this.setState({ titleTextInputHeight: height });
}
scheduleFocusUpdate() {
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
@@ -1166,6 +1178,8 @@ class NoteScreenComponent extends BaseScreenComponent {
<View style={titleContainerStyle}>
{isTodo && <Checkbox style={this.styles().checkbox} checked={!!Number(note.todo_completed)} onChange={this.todoCheckbox_change} />}
<TextInput
onContentSizeChange={this.titleTextInput_contentSizeChange}
multiline={this.enableMultilineTitle_}
ref="titleTextField"
underlineColorAndroid="#ffffff00"
autoCapitalize="sentences"

View File

@@ -21,7 +21,6 @@ class NoteTagsDialogComponent extends React.Component {
tagListData: [],
newTags: '',
savingTags: false,
tagFilter: '',
};
const noteHasTag = tagId => {
@@ -89,10 +88,6 @@ class NoteTagsDialogComponent extends React.Component {
this.cancelButton_press = () => {
if (this.props.onCloseRequested) this.props.onCloseRequested();
};
this.filterTags = (allTags) => {
return allTags.filter((tag) => tag.title.includes(this.state.tagFilter.toLowerCase()), allTags);
};
}
UNSAFE_componentWillMount() {
@@ -145,16 +140,16 @@ class NoteTagsDialogComponent extends React.Component {
fontSize: 20,
color: theme.color,
},
tagBox: {
newTagBox: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 10,
paddingRight: 10,
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
borderBottomWidth: 1,
borderBottomColor: theme.dividerColor,
},
newTagBoxLabel: Object.assign({}, theme.normalText, { marginRight: 8 }),
tagBoxInput: Object.assign({}, theme.lineInput, { flex: 1 }),
newTagBoxInput: Object.assign({}, theme.lineInput, { flex: 1 }),
};
this.styles_[themeId] = StyleSheet.create(styles);
@@ -166,7 +161,7 @@ class NoteTagsDialogComponent extends React.Component {
const dialogContent = (
<View style={{ flex: 1 }}>
<View style={this.styles().tagBox}>
<View style={this.styles().newTagBox}>
<Text style={this.styles().newTagBoxLabel}>{_('New tags:')}</Text>
<TextInput
selectionColor={theme.textSelectionColor}
@@ -175,23 +170,10 @@ class NoteTagsDialogComponent extends React.Component {
onChangeText={value => {
this.setState({ newTags: value });
}}
style={this.styles().tagBoxInput}
placeholder={_('tag1,tag2,...')}
style={this.styles().newTagBoxInput}
/>
</View>
<View style={this.styles().tagBox}>
<TextInput
selectionColor={theme.textSelectionColor}
keyboardAppearance={theme.keyboardAppearance}
value={this.state.tagFilter}
onChangeText={value => {
this.setState({ tagFilter: value });
}}
placeholder={_('Filter tags')}
style={this.styles().tagBoxInput}
/>
</View>
<FlatList data={this.filterTags(this.state.tagListData)} renderItem={this.renderTag} keyExtractor={this.tagKeyExtractor} />
<FlatList data={this.state.tagListData} renderItem={this.renderTag} keyExtractor={this.tagKeyExtractor} />
</View>
);

View File

@@ -1,6 +1,6 @@
const React = require('react');
const Component = React.Component;
const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Alert, Image } = require('react-native');
const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Alert } = require('react-native');
const { connect } = require('react-redux');
const Icon = require('react-native-vector-icons/Ionicons').default;
const Folder = require('@joplin/lib/models/Folder').default;
@@ -74,7 +74,7 @@ class SideMenuContentComponent extends Component {
styles.folderButton = Object.assign({}, styles.button);
styles.folderButton.paddingLeft = 0;
styles.folderButtonText = Object.assign({}, styles.buttonText, { paddingLeft: 0 });
styles.folderButtonText = Object.assign({}, styles.buttonText);
styles.folderButtonSelected = Object.assign({}, styles.folderButton);
styles.folderButtonSelected.backgroundColor = theme.selectedColor;
styles.folderIcon = Object.assign({}, theme.icon);
@@ -219,18 +219,6 @@ class SideMenuContentComponent extends Component {
if (actionDone === 'auth') this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
}
renderFolderIcon(theme, folderIcon) {
if (!folderIcon) return null;
if (folderIcon.type === 1) { // FolderIconType.Emoji
return <Text style={{ fontSize: theme.fontSize, marginRight: 4 }}>{folderIcon.emoji}</Text>;
} else if (folderIcon.type === 2) { // FolderIconType.DataUrl
return <Image style={{ width: 20, height: 20, marginRight: 4, resizeMode: 'contain' }} source={{ uri: folderIcon.dataUrl }}/>;
} else {
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
}
}
renderFolderItem(folder, selected, hasChildren, depth) {
const theme = themeStyle(this.props.themeId);
@@ -240,7 +228,6 @@ class SideMenuContentComponent extends Component {
height: 36,
alignItems: 'center',
paddingRight: theme.marginRight,
paddingLeft: 10,
};
if (selected) folderButtonStyle.backgroundColor = theme.selectedColor;
folderButtonStyle.paddingLeft = depth * 10 + theme.marginLeft;
@@ -266,6 +253,7 @@ class SideMenuContentComponent extends Component {
);
const folderIcon = Folder.unserializeIcon(folder.icon);
const icon = folderIcon ? `${folderIcon.emoji} ` : '';
return (
<View key={folder.id} style={{ flex: 1, flexDirection: 'row' }}>
@@ -279,9 +267,8 @@ class SideMenuContentComponent extends Component {
}}
>
<View style={folderButtonStyle}>
{this.renderFolderIcon(theme, folderIcon)}
<Text numberOfLines={1} style={this.styles().folderButtonText}>
{Folder.displayTitle(folder)}
{icon + Folder.displayTitle(folder)}
</Text>
</View>
</TouchableOpacity>

View File

@@ -492,13 +492,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 79;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.7.1;
MARKETING_VERSION = 12.7.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -521,12 +521,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 79;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.7.1;
MARKETING_VERSION = 12.7.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -667,14 +667,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 79;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.7.1;
MARKETING_VERSION = 12.7.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -698,14 +698,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 80;
CURRENT_PROJECT_VERSION = 79;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.7.1;
MARKETING_VERSION = 12.7.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -43,7 +43,6 @@ module.exports = {
'@joplin/renderer': path.resolve(__dirname, '../renderer/'),
'@joplin/tools': path.resolve(__dirname, '../tools/'),
'@joplin/fork-htmlparser2': path.resolve(__dirname, '../fork-htmlparser2/'),
'@joplin/fork-uslug': path.resolve(__dirname, '../fork-uslug/'),
},
{
get: (target, name) => {
@@ -61,6 +60,5 @@ module.exports = {
path.resolve(__dirname, '../renderer'),
path.resolve(__dirname, '../tools'),
path.resolve(__dirname, '../fork-htmlparser2'),
path.resolve(__dirname, '../fork-uslug'),
],
};

View File

@@ -1 +1 @@
module.exports = `LyoKCkF0b20gT25lIERhcmsgV2l0aCBzdXBwb3J0IGZvciBSZWFzb25NTCBieSBHaWRpIE1vcnJpcywgYmFzZWQgb2ZmIHdvcmsgYnkgRGFuaWVsIEdhbWFnZQoKT3JpZ2luYWwgT25lIERhcmsgU3ludGF4IHRoZW1lIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2F0b20vb25lLWRhcmstc3ludGF4CgoqLwouaGxqcyB7CiAgY29sb3I6ICNhYmIyYmY7CiAgYmFja2dyb3VuZDogIzI4MmMzNDsKfQouaGxqcy1rZXl3b3JkLCAuaGxqcy1vcGVyYXRvciB7CiAgY29sb3I6ICNGOTI2NzI7Cn0KLmhsanMtcGF0dGVybi1tYXRjaCB7CiAgY29sb3I6ICNGOTI2NzI7Cn0KLmhsanMtcGF0dGVybi1tYXRjaCAuaGxqcy1jb25zdHJ1Y3RvciB7CiAgY29sb3I6ICM2MWFlZWU7Cn0KLmhsanMtZnVuY3Rpb24gewogIGNvbG9yOiAjNjFhZWVlOwp9Ci5obGpzLWZ1bmN0aW9uIC5obGpzLXBhcmFtcyB7CiAgY29sb3I6ICNBNkUyMkU7Cn0KLmhsanMtZnVuY3Rpb24gLmhsanMtcGFyYW1zIC5obGpzLXR5cGluZyB7CiAgY29sb3I6ICNGRDk3MUY7Cn0KLmhsanMtbW9kdWxlLWFjY2VzcyAuaGxqcy1tb2R1bGUgewogIGNvbG9yOiAjN2U1N2MyOwp9Ci5obGpzLWNvbnN0cnVjdG9yIHsKICBjb2xvcjogI2UyYjkzZDsKfQouaGxqcy1jb25zdHJ1Y3RvciAuaGxqcy1zdHJpbmcgewogIGNvbG9yOiAjOUNDQzY1Owp9Ci5obGpzLWNvbW1lbnQsIC5obGpzLXF1b3RlIHsKICBjb2xvcjogI2IxOGViMTsKICBmb250LXN0eWxlOiBpdGFsaWM7Cn0KLmhsanMtZG9jdGFnLCAuaGxqcy1mb3JtdWxhIHsKICBjb2xvcjogI2M2NzhkZDsKfQouaGxqcy1zZWN0aW9uLCAuaGxqcy1uYW1lLCAuaGxqcy1zZWxlY3Rvci10YWcsIC5obGpzLWRlbGV0aW9uLCAuaGxqcy1zdWJzdCB7CiAgY29sb3I6ICNlMDZjNzU7Cn0KLmhsanMtbGl0ZXJhbCB7CiAgY29sb3I6ICM1NmI2YzI7Cn0KLmhsanMtc3RyaW5nLCAuaGxqcy1yZWdleHAsIC5obGpzLWFkZGl0aW9uLCAuaGxqcy1hdHRyaWJ1dGUsIC5obGpzLW1ldGEgLmhsanMtc3RyaW5nIHsKICBjb2xvcjogIzk4YzM3OTsKfQouaGxqcy1idWlsdF9pbiwKLmhsanMtdGl0bGUuY2xhc3NfLAouaGxqcy1jbGFzcyAuaGxqcy10aXRsZSB7CiAgY29sb3I6ICNlNmMwN2I7Cn0KLmhsanMtYXR0ciwgLmhsanMtdmFyaWFibGUsIC5obGpzLXRlbXBsYXRlLXZhcmlhYmxlLCAuaGxqcy10eXBlLCAuaGxqcy1zZWxlY3Rvci1jbGFzcywgLmhsanMtc2VsZWN0b3ItYXR0ciwgLmhsanMtc2VsZWN0b3ItcHNldWRvLCAuaGxqcy1udW1iZXIgewogIGNvbG9yOiAjZDE5YTY2Owp9Ci5obGpzLXN5bWJvbCwgLmhsanMtYnVsbGV0LCAuaGxqcy1saW5rLCAuaGxqcy1tZXRhLCAuaGxqcy1zZWxlY3Rvci1pZCwgLmhsanMtdGl0bGUgewogIGNvbG9yOiAjNjFhZWVlOwp9Ci5obGpzLWVtcGhhc2lzIHsKICBmb250LXN0eWxlOiBpdGFsaWM7Cn0KLmhsanMtc3Ryb25nIHsKICBmb250LXdlaWdodDogYm9sZDsKfQouaGxqcy1saW5rIHsKICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKfQo=`;
module.exports = `cHJlIGNvZGUuaGxqc3tkaXNwbGF5OmJsb2NrO292ZXJmbG93LXg6YXV0bztwYWRkaW5nOjFlbX1jb2RlLmhsanN7cGFkZGluZzozcHggNXB4fS5obGpze2NvbG9yOiNhYmIyYmY7YmFja2dyb3VuZDojMjgyYzM0fS5obGpzLWtleXdvcmQsLmhsanMtb3BlcmF0b3IsLmhsanMtcGF0dGVybi1tYXRjaHtjb2xvcjojZjkyNjcyfS5obGpzLWZ1bmN0aW9uLC5obGpzLXBhdHRlcm4tbWF0Y2ggLmhsanMtY29uc3RydWN0b3J7Y29sb3I6IzYxYWVlZX0uaGxqcy1mdW5jdGlvbiAuaGxqcy1wYXJhbXN7Y29sb3I6I2E2ZTIyZX0uaGxqcy1mdW5jdGlvbiAuaGxqcy1wYXJhbXMgLmhsanMtdHlwaW5ne2NvbG9yOiNmZDk3MWZ9LmhsanMtbW9kdWxlLWFjY2VzcyAuaGxqcy1tb2R1bGV7Y29sb3I6IzdlNTdjMn0uaGxqcy1jb25zdHJ1Y3Rvcntjb2xvcjojZTJiOTNkfS5obGpzLWNvbnN0cnVjdG9yIC5obGpzLXN0cmluZ3tjb2xvcjojOWNjYzY1fS5obGpzLWNvbW1lbnQsLmhsanMtcXVvdGV7Y29sb3I6I2IxOGViMTtmb250LXN0eWxlOml0YWxpY30uaGxqcy1kb2N0YWcsLmhsanMtZm9ybXVsYXtjb2xvcjojYzY3OGRkfS5obGpzLWRlbGV0aW9uLC5obGpzLW5hbWUsLmhsanMtc2VjdGlvbiwuaGxqcy1zZWxlY3Rvci10YWcsLmhsanMtc3Vic3R7Y29sb3I6I2UwNmM3NX0uaGxqcy1saXRlcmFse2NvbG9yOiM1NmI2YzJ9LmhsanMtYWRkaXRpb24sLmhsanMtYXR0cmlidXRlLC5obGpzLW1ldGEgLmhsanMtc3RyaW5nLC5obGpzLXJlZ2V4cCwuaGxqcy1zdHJpbmd7Y29sb3I6Izk4YzM3OX0uaGxqcy1idWlsdF9pbiwuaGxqcy1jbGFzcyAuaGxqcy10aXRsZSwuaGxqcy10aXRsZS5jbGFzc197Y29sb3I6I2U2YzA3Yn0uaGxqcy1hdHRyLC5obGpzLW51bWJlciwuaGxqcy1zZWxlY3Rvci1hdHRyLC5obGpzLXNlbGVjdG9yLWNsYXNzLC5obGpzLXNlbGVjdG9yLXBzZXVkbywuaGxqcy10ZW1wbGF0ZS12YXJpYWJsZSwuaGxqcy10eXBlLC5obGpzLXZhcmlhYmxle2NvbG9yOiNkMTlhNjZ9LmhsanMtYnVsbGV0LC5obGpzLWxpbmssLmhsanMtbWV0YSwuaGxqcy1zZWxlY3Rvci1pZCwuaGxqcy1zeW1ib2wsLmhsanMtdGl0bGV7Y29sb3I6IzYxYWVlZX0uaGxqcy1lbXBoYXNpc3tmb250LXN0eWxlOml0YWxpY30uaGxqcy1zdHJvbmd7Zm9udC13ZWlnaHQ6NzAwfS5obGpzLWxpbmt7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZX0=`;

View File

@@ -1 +1 @@
module.exports = `LyoKCkF0b20gT25lIExpZ2h0IGJ5IERhbmllbCBHYW1hZ2UKT3JpZ2luYWwgT25lIExpZ2h0IFN5bnRheCB0aGVtZSBmcm9tIGh0dHBzOi8vZ2l0aHViLmNvbS9hdG9tL29uZS1saWdodC1zeW50YXgKCmJhc2U6ICAgICNmYWZhZmEKbW9uby0xOiAgIzM4M2E0Mgptb25vLTI6ICAjNjg2Yjc3Cm1vbm8tMzogICNhMGExYTcKaHVlLTE6ICAgIzAxODRiYgpodWUtMjogICAjNDA3OGYyCmh1ZS0zOiAgICNhNjI2YTQKaHVlLTQ6ICAgIzUwYTE0ZgpodWUtNTogICAjZTQ1NjQ5Cmh1ZS01LTI6ICNjOTEyNDMKaHVlLTY6ICAgIzk4NjgwMQpodWUtNi0yOiAjYzE4NDAxCgoqLwoKLmhsanMgewogIGNvbG9yOiAjMzgzYTQyOwogIGJhY2tncm91bmQ6ICNmYWZhZmE7Cn0KCi5obGpzLWNvbW1lbnQsCi5obGpzLXF1b3RlIHsKICBjb2xvcjogI2EwYTFhNzsKICBmb250LXN0eWxlOiBpdGFsaWM7Cn0KCi5obGpzLWRvY3RhZywKLmhsanMta2V5d29yZCwKLmhsanMtZm9ybXVsYSB7CiAgY29sb3I6ICNhNjI2YTQ7Cn0KCi5obGpzLXNlY3Rpb24sCi5obGpzLW5hbWUsCi5obGpzLXNlbGVjdG9yLXRhZywKLmhsanMtZGVsZXRpb24sCi5obGpzLXN1YnN0IHsKICBjb2xvcjogI2U0NTY0OTsKfQoKLmhsanMtbGl0ZXJhbCB7CiAgY29sb3I6ICMwMTg0YmI7Cn0KCi5obGpzLXN0cmluZywKLmhsanMtcmVnZXhwLAouaGxqcy1hZGRpdGlvbiwKLmhsanMtYXR0cmlidXRlLAouaGxqcy1tZXRhIC5obGpzLXN0cmluZyB7CiAgY29sb3I6ICM1MGExNGY7Cn0KCi5obGpzLWF0dHIsCi5obGpzLXZhcmlhYmxlLAouaGxqcy10ZW1wbGF0ZS12YXJpYWJsZSwKLmhsanMtdHlwZSwKLmhsanMtc2VsZWN0b3ItY2xhc3MsCi5obGpzLXNlbGVjdG9yLWF0dHIsCi5obGpzLXNlbGVjdG9yLXBzZXVkbywKLmhsanMtbnVtYmVyIHsKICBjb2xvcjogIzk4NjgwMTsKfQoKLmhsanMtc3ltYm9sLAouaGxqcy1idWxsZXQsCi5obGpzLWxpbmssCi5obGpzLW1ldGEsCi5obGpzLXNlbGVjdG9yLWlkLAouaGxqcy10aXRsZSB7CiAgY29sb3I6ICM0MDc4ZjI7Cn0KCi5obGpzLWJ1aWx0X2luLAouaGxqcy10aXRsZS5jbGFzc18sCi5obGpzLWNsYXNzIC5obGpzLXRpdGxlIHsKICBjb2xvcjogI2MxODQwMTsKfQoKLmhsanMtZW1waGFzaXMgewogIGZvbnQtc3R5bGU6IGl0YWxpYzsKfQoKLmhsanMtc3Ryb25nIHsKICBmb250LXdlaWdodDogYm9sZDsKfQoKLmhsanMtbGluayB7CiAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7Cn0K`;
module.exports = `cHJlIGNvZGUuaGxqc3tkaXNwbGF5OmJsb2NrO292ZXJmbG93LXg6YXV0bztwYWRkaW5nOjFlbX1jb2RlLmhsanN7cGFkZGluZzozcHggNXB4fS5obGpze2NvbG9yOiMzODNhNDI7YmFja2dyb3VuZDojZmFmYWZhfS5obGpzLWNvbW1lbnQsLmhsanMtcXVvdGV7Y29sb3I6I2EwYTFhNztmb250LXN0eWxlOml0YWxpY30uaGxqcy1kb2N0YWcsLmhsanMtZm9ybXVsYSwuaGxqcy1rZXl3b3Jke2NvbG9yOiNhNjI2YTR9LmhsanMtZGVsZXRpb24sLmhsanMtbmFtZSwuaGxqcy1zZWN0aW9uLC5obGpzLXNlbGVjdG9yLXRhZywuaGxqcy1zdWJzdHtjb2xvcjojZTQ1NjQ5fS5obGpzLWxpdGVyYWx7Y29sb3I6IzAxODRiYn0uaGxqcy1hZGRpdGlvbiwuaGxqcy1hdHRyaWJ1dGUsLmhsanMtbWV0YSAuaGxqcy1zdHJpbmcsLmhsanMtcmVnZXhwLC5obGpzLXN0cmluZ3tjb2xvcjojNTBhMTRmfS5obGpzLWF0dHIsLmhsanMtbnVtYmVyLC5obGpzLXNlbGVjdG9yLWF0dHIsLmhsanMtc2VsZWN0b3ItY2xhc3MsLmhsanMtc2VsZWN0b3ItcHNldWRvLC5obGpzLXRlbXBsYXRlLXZhcmlhYmxlLC5obGpzLXR5cGUsLmhsanMtdmFyaWFibGV7Y29sb3I6Izk4NjgwMX0uaGxqcy1idWxsZXQsLmhsanMtbGluaywuaGxqcy1tZXRhLC5obGpzLXNlbGVjdG9yLWlkLC5obGpzLXN5bWJvbCwuaGxqcy10aXRsZXtjb2xvcjojNDA3OGYyfS5obGpzLWJ1aWx0X2luLC5obGpzLWNsYXNzIC5obGpzLXRpdGxlLC5obGpzLXRpdGxlLmNsYXNzX3tjb2xvcjojYzE4NDAxfS5obGpzLWVtcGhhc2lze2ZvbnQtc3R5bGU6aXRhbGljfS5obGpzLXN0cm9uZ3tmb250LXdlaWdodDo3MDB9LmhsanMtbGlua3t0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lfQ==`;

View File

@@ -1,5 +1,5 @@
module.exports = {
hash:"ea13a22d0df59339b671f6b5700e2914", files: {
hash:"dd2315568bb7795f97cee26a47e9b82b", files: {
'highlight.js/atom-one-dark-reasonable.css': { data: require('./highlight.js/atom-one-dark-reasonable.css.base64.js'), mime: 'text/css', encoding: 'base64' },
'highlight.js/atom-one-light.css': { data: require('./highlight.js/atom-one-light.css.base64.js'), mime: 'text/css', encoding: 'base64' },
'katex/fonts/KaTeX_AMS-Regular.woff2': { data: require('./katex/fonts/KaTeX_AMS-Regular.woff2.base64.js'), mime: 'application/octet-stream', encoding: 'base64' },

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More