mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
149 Commits
3.3.15-bet
...
mobile/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21f931d3ea | ||
|
|
58253f11b7 | ||
|
|
0535de9ae9 | ||
|
|
74921ae1b3 | ||
|
|
40f2c88ead | ||
|
|
7fe410ae89 | ||
|
|
4cbdd085d6 | ||
|
|
24556dab83 | ||
|
|
dd8116eba8 | ||
|
|
ae82a8c0a0 | ||
|
|
af4dc5e9ea | ||
|
|
71b03a90dd | ||
|
|
728e50cd13 | ||
|
|
c8e8935b34 | ||
|
|
21f4b906d7 | ||
|
|
d49d42b736 | ||
|
|
267fe4e453 | ||
|
|
413ca61ef5 | ||
|
|
02b96470bc | ||
|
|
7234128f78 | ||
|
|
27e070e555 | ||
|
|
539950bdc5 | ||
|
|
83280e7cee | ||
|
|
cee0275565 | ||
|
|
87bddf0037 | ||
|
|
e736e3d810 | ||
|
|
092f15406c | ||
|
|
befcd0f112 | ||
|
|
8b92936d05 | ||
|
|
aec135de83 | ||
|
|
f9738168e5 | ||
|
|
e873b88dbd | ||
|
|
214f5b8a98 | ||
|
|
70cf17fdd5 | ||
|
|
7d557810cb | ||
|
|
ed24282703 | ||
|
|
e2cf3e81af | ||
|
|
28fa133b6d | ||
|
|
37893ced80 | ||
|
|
900c48292c | ||
|
|
302ae5ddb4 | ||
|
|
24fca08fdc | ||
|
|
91077dc132 | ||
|
|
273bbea938 | ||
|
|
36c44ad201 | ||
|
|
46f5a2d7a7 | ||
|
|
9b359a91cc | ||
|
|
0af831d50b | ||
|
|
3efaeaeeb9 | ||
|
|
00a20dda48 | ||
|
|
ec45160b27 | ||
|
|
eda284ca25 | ||
|
|
1ed331b4bb | ||
|
|
ee74bb4fbf | ||
|
|
df104669a3 | ||
|
|
9621b43bf2 | ||
|
|
503c1a5c8d | ||
|
|
a2fa1d4d78 | ||
|
|
6c6ad9a766 | ||
|
|
fc8807f74d | ||
|
|
edec48f9d4 | ||
|
|
ce98551057 | ||
|
|
74a2cc5b19 | ||
|
|
9628a940e9 | ||
|
|
d48643cbb9 | ||
|
|
558beaf216 | ||
|
|
6cc5d2452b | ||
|
|
e934ef7ffd | ||
|
|
e2d8032ef4 | ||
|
|
81e6101fb5 | ||
|
|
070354920b | ||
|
|
404168fb86 | ||
|
|
d5fa9fd02b | ||
|
|
6cc66d91be | ||
|
|
20a3b3b95e | ||
|
|
9145ea4234 | ||
|
|
0fa79433bd | ||
|
|
5185273b38 | ||
|
|
eadd23de4d | ||
|
|
71d9738980 | ||
|
|
80328af888 | ||
|
|
5e83da5575 | ||
|
|
521afe90ca | ||
|
|
ee3b7b5d7d | ||
|
|
95a9775c26 | ||
|
|
93d1319b25 | ||
|
|
b2bac3808b | ||
|
|
ebf58e68cf | ||
|
|
dc3e909dfe | ||
|
|
bf09e9615a | ||
|
|
b2d30d47d6 | ||
|
|
b1296fd24f | ||
|
|
62d8c89300 | ||
|
|
80c44eee89 | ||
|
|
153eb126fc | ||
|
|
f1fc166713 | ||
|
|
dda36bb036 | ||
|
|
843ad08813 | ||
|
|
a0e3c923e4 | ||
|
|
3095b03b09 | ||
|
|
ea5a0e0a7c | ||
|
|
2b4ec5b03b | ||
|
|
59f7add065 | ||
|
|
9c7bfc2347 | ||
|
|
ef90f40be4 | ||
|
|
bca6a4f22c | ||
|
|
1d3e2b56b2 | ||
|
|
a93a20ed5b | ||
|
|
971a80c183 | ||
|
|
125b60facc | ||
|
|
ef99b47178 | ||
|
|
d65f870213 | ||
|
|
5b3f1c9de2 | ||
|
|
4afb090cb1 | ||
|
|
8cf6e65ed8 | ||
|
|
c85c51ec08 | ||
|
|
dbeabea6c5 | ||
|
|
dfbe2d0f0f | ||
|
|
ea2277ea4e | ||
|
|
ad78105a6e | ||
|
|
36584175e2 | ||
|
|
084b1fdfd2 | ||
|
|
bed091e20f | ||
|
|
4420663687 | ||
|
|
677ae82ffa | ||
|
|
48a3fac9e0 | ||
|
|
e797d37b01 | ||
|
|
ffafd2e787 | ||
|
|
8500c971eb | ||
|
|
46a67a56fb | ||
|
|
5dc62e6df2 | ||
|
|
4479d6cf56 | ||
|
|
1faaf1f6b1 | ||
|
|
1f33ef191a | ||
|
|
d5bf8a6e3c | ||
|
|
5c252c7843 | ||
|
|
d2e10ccaf6 | ||
|
|
89d2b6870f | ||
|
|
26a9086d96 | ||
|
|
9fc8e6b1e5 | ||
|
|
6718650619 | ||
|
|
a622949f23 | ||
|
|
5a2df5b2c1 | ||
|
|
ae751cdd65 | ||
|
|
0d00bb2866 | ||
|
|
4299965064 | ||
|
|
6f3fc8db3d | ||
|
|
db731856f8 | ||
|
|
217f1dd6d4 |
30
.github/pull_request_template.md
vendored
Normal file
30
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
## Description
|
||||
<!-- Add a detailed summary of what this feature/bugfix does -->
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] Feature
|
||||
|
||||
## Visuals
|
||||
- [ ] Attached relevant screenshots / screen recording / GIF
|
||||
- [ ] N/A (not a feature or no UI changes)
|
||||
|
||||
## Testing
|
||||
- [ ] Ran all E2E tests
|
||||
- [ ] Ran all integration tests
|
||||
- [ ] Added/updated tests for this change (if needed)
|
||||
- [ ] N/A (tests not needed — explanation provided below)
|
||||
|
||||
### If tests were not added, explain why
|
||||
<!-- explanation -->
|
||||
|
||||
## Platform
|
||||
<!-- Describe which platforms this PR is related to -->
|
||||
|
||||
- [ ] Web
|
||||
- [ ] Mobile
|
||||
- [ ] Desktop
|
||||
|
||||
## Sign-off
|
||||
- [ ] QA passed
|
||||
- [ ] UI/UX passed
|
||||
135
.github/workflows/android.preview.firebase.yml
vendored
Normal file
135
.github/workflows/android.preview.firebase.yml
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
name: Notesnook Android Preview
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
branches: [master, beta]
|
||||
paths:
|
||||
- "apps/mobile/**"
|
||||
- "packages/**"
|
||||
- ".github/workflows/android.preview.firebase.yml"
|
||||
|
||||
jobs:
|
||||
authorize:
|
||||
environment: ${{ github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
'external' || 'internal' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo true
|
||||
|
||||
build:
|
||||
needs: authorize
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
STAGING_BUILD: true
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Use specific Java version for the builds
|
||||
uses: joschi/setup-jdk@v2
|
||||
with:
|
||||
java-version: "17"
|
||||
architecture: "x64"
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=mobile
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd apps/mobile/android && chmod +x ./gradlew
|
||||
|
||||
- name: Get build number
|
||||
id: build-number
|
||||
run: echo "timestamp=$(($(date +%s) - 1774851180 ))" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check for typescript errors
|
||||
run: |
|
||||
npm run tx mobile:build
|
||||
cd apps/mobile
|
||||
npx tsc --noEmit
|
||||
|
||||
- name: Build arm64-v8a apk
|
||||
run: |
|
||||
cd apps/mobile/android
|
||||
./gradlew assembleRelease -PreactNativeArchitectures=arm64-v8a -PstagingReleaseBuild=true -PprBuildNumber=${{ steps.build-number.outputs.timestamp }}
|
||||
|
||||
- name: Get app version
|
||||
id: package-version
|
||||
uses: saionaro/extract-package-version@master
|
||||
with:
|
||||
path: apps/mobile
|
||||
|
||||
- name: Publish to firebase CLI
|
||||
id: firebase-output
|
||||
uses: wzieba/Firebase-Distribution-Github-Action@v1
|
||||
with:
|
||||
appId: ${{secrets.FIREBASE_APP_ID}}
|
||||
serviceCredentialsFileContent: ${{ secrets.QA_SERVICE_ACCOUNT }}
|
||||
groups: testers
|
||||
file: apps/mobile/android/app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
||||
releaseNotes: Preview for https://github.com/streetwriters/notesnook/pull/${{github.event.number}}
|
||||
|
||||
- name: Post or update PR comment
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
preview_url: ${{ steps.firebase-output.outputs.TESTING_URI }}
|
||||
with:
|
||||
script: |
|
||||
const marker = '<!-- android-preview-comment -->';
|
||||
const prNumber = context.issue.number;
|
||||
const previewUrl = process.env.preview_url || '';
|
||||
const body = `${marker}\n**Android App Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
const existing = comments.find(c => c.body && c.body.includes(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
- name: Upload sourcemaps
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sourcemaps
|
||||
path: |
|
||||
apps/mobile/android/app/build/generated/sourcemaps/**/*.map
|
||||
257
.github/workflows/desktop.preview.yml
vendored
Normal file
257
.github/workflows/desktop.preview.yml
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
name: Preview @notesnook/desktop
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
branches: [master, beta]
|
||||
paths:
|
||||
- "apps/desktop/**"
|
||||
- "apps/web/**"
|
||||
- "packages/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/desktop.preview.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Install sqlite-better-trigram for all arch
|
||||
run: |
|
||||
npm i --cpu arm64 sqlite-better-trigram
|
||||
npm i --cpu x64 sqlite-better-trigram
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Generate desktop build (stable)
|
||||
run: npm run tx @notesnook/web:build:desktop
|
||||
|
||||
- name: Build desktop bundle
|
||||
working-directory: ./apps/desktop
|
||||
run: npm run bundle
|
||||
|
||||
- name: Archive build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: apps/web/build/**/*
|
||||
|
||||
build-macos:
|
||||
name: Build for macOS
|
||||
needs: build
|
||||
runs-on: macos-14
|
||||
outputs:
|
||||
macos-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install setuptools
|
||||
run: brew install python-setuptools
|
||||
|
||||
- name: Setup notarization
|
||||
run: |
|
||||
mkdir -p ~/private_keys/
|
||||
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
|
||||
|
||||
- name: Collect app metadata
|
||||
id: app_metadata
|
||||
working-directory: ./apps/desktop
|
||||
run: |
|
||||
echo ::set-output name=apple_app_id::$(cat package.json | jq -r .appAppleId)
|
||||
echo ::set-output name=app_bundle_id::$(cat package.json | jq -r .build.appId)
|
||||
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
|
||||
echo ::set-output name=bundle_version::$(cat package.json | jq -r .build.mac.bundleVersion)
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Install sqlite-better-trigram for all arch
|
||||
run: |
|
||||
npm i --cpu arm64 sqlite-better-trigram
|
||||
npm i --cpu x64 sqlite-better-trigram
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Install provisioning profile
|
||||
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build dmg
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.mac_certs }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
||||
APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
|
||||
APPLE_API_KEY_ID: ${{ secrets.api_key_id }}
|
||||
APPLE_API_ISSUER: ${{ secrets.api_key_issuer_id }}
|
||||
run: |
|
||||
npm run tx @notesnook/desktop:release
|
||||
cd apps/desktop
|
||||
yarn electron-builder --config=electron-builder.config.js --mac dmg --arm64 -p never
|
||||
|
||||
- name: Upload dmg artifact
|
||||
id: artifact-upload-step
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-build
|
||||
path: apps/desktop/output/*.dmg
|
||||
|
||||
build-linux-x64:
|
||||
name: Build for Linux x64
|
||||
needs: build
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
linux-x64-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Install sqlite-better-trigram for all arch
|
||||
run: |
|
||||
npm i --cpu x64 sqlite-better-trigram
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build Electron wrapper
|
||||
run: npm run tx @notesnook/desktop:release
|
||||
|
||||
- name: Build AppImage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
id: artifact-upload-step
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-x64-build
|
||||
path: apps/desktop/output/*.AppImage
|
||||
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
windows-artifact-url: ${{ steps.artifact-upload-step.outputs.artifact-url }}
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Install sqlite-better-trigram for all arch
|
||||
run: |
|
||||
npm i --cpu arm64 sqlite-better-trigram
|
||||
npm i --cpu x64 sqlite-better-trigram
|
||||
|
||||
npm i --cpu arm64 sqlite3-fts5-html
|
||||
npm i --cpu x64 sqlite3-fts5-html
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build
|
||||
run: node scripts/execute.mjs @notesnook/desktop:release
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
NOTESNOOK_STAGING: true
|
||||
run: yarn electron-builder --config=electron-builder.config.js --win nsis:x64 --publish never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload exe artifact
|
||||
id: artifact-upload-step
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build
|
||||
path: apps/desktop/output/*.exe
|
||||
|
||||
post-pr-comment:
|
||||
name: Post PR comment with preview URLs
|
||||
needs: [build, build-macos, build-linux-x64, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Post or update PR comment
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
macos_artifact_url: ${{ needs.build-macos.outputs.macos-artifact-url }}
|
||||
linux_x64_artifact_url: ${{ needs.build-linux-x64.outputs.linux-x64-artifact-url }}
|
||||
windows_artifact_url: ${{ needs.build-windows.outputs.windows-artifact-url }}
|
||||
with:
|
||||
script: |
|
||||
const marker = '<!-- desktop-preview-comment -->';
|
||||
const prNumber = context.issue.number;
|
||||
const previewUrl = [
|
||||
{ platform: 'macOS', url: process.env.macos_artifact_url },
|
||||
{ platform: 'Linux x64', url: process.env.linux_x64_artifact_url },
|
||||
{ platform: 'Windows x64', url: process.env.windows_artifact_url },
|
||||
].filter(u => u.url).map(u => `- [${u.platform}](${u.url})`).join('\n');
|
||||
const body = `${marker}\n**Desktop Previews**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
const existing = comments.find(c => c.body && c.body.includes(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
168
.github/workflows/ios.preview.firebase.yml
vendored
Normal file
168
.github/workflows/ios.preview.firebase.yml
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
name: Notesnook iOS Preview
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize]
|
||||
branches: [master, beta]
|
||||
paths:
|
||||
- "apps/mobile/**"
|
||||
- "packages/**"
|
||||
- ".github/workflows/ios.preview.firebase.yml"
|
||||
|
||||
jobs:
|
||||
authorize:
|
||||
environment: ${{ github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
'external' || 'internal' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo true
|
||||
|
||||
build:
|
||||
needs: authorize
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: "26.1.1"
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=mobile
|
||||
|
||||
- name: Build packages
|
||||
run: npm run tx @notesnook/mobile:build
|
||||
|
||||
- name: Cache Pods
|
||||
uses: actions/cache@v3
|
||||
id: pods-cache
|
||||
with:
|
||||
path: apps/mobile/ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/ios/Podfile.lock') }}
|
||||
|
||||
- name: Install Pods
|
||||
run: |
|
||||
cd apps/mobile/ios
|
||||
pod install
|
||||
|
||||
- name: Check for typescript errors
|
||||
run: |
|
||||
npm run tx mobile:build
|
||||
cd apps/mobile
|
||||
npx tsc --noEmit
|
||||
|
||||
- name: Get marketing version
|
||||
id: marketing-version
|
||||
run: echo "version=$(grep "IOS_MARKETING_VERSION" apps/mobile/ios/build-configs/ios-build.staging.xcconfig | awk -F'=' '{print $2}' | xargs)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get build number
|
||||
id: build-number
|
||||
run: echo "timestamp=$(($(date +%s) - 1774851180 ))" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Make staging xcconfig active
|
||||
run: |
|
||||
cd apps/mobile/ios/build-configs
|
||||
./use-ios-build-config.sh staging --marketing-version ${{steps.marketing-version.outputs.version}} --build-number ${{steps.build-number.outputs.timestamp}}
|
||||
|
||||
- name: Build iOS App
|
||||
uses: ammarahm-ed/ios-build-action@master
|
||||
with:
|
||||
bundle-identifier: org.streetwriters.notesnook
|
||||
scheme: Notesnook
|
||||
configuration: "Release"
|
||||
export-options: apps/mobile/ios/ExportOptionsStaging.plist
|
||||
export-method: "ad-hoc"
|
||||
project-path: apps/mobile/ios/Notesnook.xcodeproj
|
||||
workspace-path: apps/mobile/ios/Notesnook.xcworkspace
|
||||
update-targets: |
|
||||
Notesnook
|
||||
Make Note
|
||||
NotesWidgetExtension
|
||||
disable-targets: Notesnook-tvOS,Notesnook-tvOSTests,NotesnookTests
|
||||
code-signing-identity: Apple Distribution
|
||||
team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
p12-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
||||
certificate-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
|
||||
app-store-connect-api-key-issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||
app-store-connect-api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-connect-api-key-base64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }}
|
||||
output-path: Notesnook.ipa
|
||||
mobileprovision-base64: |
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_APP }}
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_SHARE }}
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_ADHOC_WIDGET }}
|
||||
|
||||
- name: Upload IPA artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios-preview-ipa
|
||||
path: Notesnook.ipa
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Download IPA artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ios-preview-ipa
|
||||
path: .
|
||||
|
||||
- name: Publish to firebase CLI
|
||||
id: firebase-output
|
||||
uses: wzieba/Firebase-Distribution-Github-Action@v1
|
||||
with:
|
||||
appId: ${{secrets.FIREBASE_IOS_APP_ID}}
|
||||
serviceCredentialsFileContent: ${{ secrets.QA_SERVICE_ACCOUNT }}
|
||||
groups: testers
|
||||
file: Notesnook.ipa
|
||||
releaseNotes: Preview for https://github.com/streetwriters/notesnook/pull/${{github.event.number}}
|
||||
|
||||
- name: Post or update PR comment
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
preview_url: ${{ steps.firebase-output.outputs.TESTING_URI }}
|
||||
with:
|
||||
script: |
|
||||
const marker = '<!-- ios-preview-comment -->';
|
||||
const prNumber = context.issue.number;
|
||||
const previewUrl = process.env.preview_url || '';
|
||||
const body = `${marker}\n**iOS App Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
const existing = comments.find(c => c.body && c.body.includes(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
40
.github/workflows/web.benchmarks.yml
vendored
40
.github/workflows/web.benchmarks.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Benchmark @notesnook/web
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/web.benchmarks.yml"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
bench:
|
||||
name: Bench
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Build
|
||||
run: npm run build:web
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Run benchmarks
|
||||
id: benchmark
|
||||
run: node __bench__/app.bench.mjs
|
||||
working-directory: apps/web
|
||||
80
.github/workflows/web.preview.yml
vendored
Normal file
80
.github/workflows/web.preview.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Notesnook Web PR Preview
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
branches: [master, beta]
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- "packages/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/web.preview.yml"
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build web
|
||||
run: npm run build:web
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
id: deploy
|
||||
working-directory: ./apps/web
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BRANCH=pr-${{ github.event.number }}-$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
echo "Deploying branch: $BRANCH"
|
||||
DEPLOY_OUT=$(npx --yes wrangler pages deploy --project-name=notesnook-app --branch="$BRANCH" ./build 2>&1) || { echo "$DEPLOY_OUT"; exit 1; }
|
||||
echo "$DEPLOY_OUT"
|
||||
PREVIEW_URL=$(printf "%s" "$DEPLOY_OUT" | grep -Eo 'https?://[^ ]+' | head -1 || true)
|
||||
if [ -z "$PREVIEW_URL" ]; then
|
||||
echo "WARNING: could not parse preview URL from wrangler output"
|
||||
fi
|
||||
echo "preview_url=$PREVIEW_URL" >> $GITHUB_ENV
|
||||
|
||||
- name: Post or update PR comment
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
preview_url: ${{ env.preview_url }}
|
||||
with:
|
||||
script: |
|
||||
const marker = '<!-- pages-preview-comment -->';
|
||||
const prNumber = context.issue.number;
|
||||
const previewUrl = process.env.preview_url || '';
|
||||
const body = `${marker}\n**Cloudflare Pages Preview**\n\n${previewUrl || 'Preview URL unavailable — check workflow logs.'}\n\nCommit: ${process.env.GITHUB_SHA}\n`;
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
const existing = comments.find(c => c.body && c.body.includes(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.3.9-beta.2",
|
||||
"version": "3.3.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.3.9-beta.2",
|
||||
"version": "3.3.13",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.3.9-beta.2",
|
||||
"version": "3.3.13",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
||||
@@ -252,6 +252,7 @@ export const osIntegrationRouter = t.router({
|
||||
if (menuItem) menu.append(menuItem);
|
||||
}
|
||||
if (menu.items.length > 0) menu.popup();
|
||||
menu.on("menu-will-close", () => emit.next([]));
|
||||
return () => {
|
||||
menu.removeAllListeners();
|
||||
menu.closePopup();
|
||||
|
||||
@@ -34,6 +34,17 @@ export const windowRouter = t.router({
|
||||
}),
|
||||
maximized: t.procedure.query(() => globalThis.window?.isMaximized()),
|
||||
fullscreen: t.procedure.query(() => globalThis.window?.isFullScreen()),
|
||||
onClose: t.procedure.subscription(() => {
|
||||
return observable<void>((emit) => {
|
||||
function listener() {
|
||||
emit.next();
|
||||
}
|
||||
globalThis.window?.addListener("close", listener);
|
||||
return () => {
|
||||
globalThis.window?.removeListener("close", listener);
|
||||
};
|
||||
});
|
||||
}),
|
||||
onWindowStateChanged: t.procedure.subscription(() => {
|
||||
return observable<{ maximized: boolean; fullscreen: boolean }>((emit) => {
|
||||
function listener() {
|
||||
|
||||
@@ -52,6 +52,9 @@ locale.then(({ default: locale }) => {
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
const appHostnames = isDevelopment()
|
||||
? ["localhost", "127.0.0.1"]
|
||||
: ["app.notesnook.com"];
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
console.log("Another instance is already running!");
|
||||
@@ -173,6 +176,19 @@ async function createWindow() {
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
mainWindow.webContents.on("will-navigate", (event, url) => {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
if (!appHostnames.includes(parsedUrl.hostname)) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("will-navigate: failed to parse URL", url, e);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
nativeTheme.on("updated", () => {
|
||||
setupTray();
|
||||
setupJumplist();
|
||||
|
||||
@@ -62,7 +62,17 @@ function registerProtocol() {
|
||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
});
|
||||
} else {
|
||||
return net.fetch(request, { bypassCustomProtocolHandlers: true });
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
if (!requestHeaders.has("referer"))
|
||||
requestHeaders.set("referer", PROTOCOL_URL);
|
||||
return await net.fetch(
|
||||
new Request(request, {
|
||||
headers: requestHeaders
|
||||
}),
|
||||
{
|
||||
bypassCustomProtocolHandlers: true
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
console.info(`${SCHEME} protocol inteception "successful"`);
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
</p>
|
||||
|
||||
<h1 align="center">Notesnook Mobile</h1>
|
||||
<h3 align="center">The mobile app is built using React Native, Typescript & Javascript for both iOS & Android.</h3>
|
||||
<p align="center">
|
||||
<a href="#developer-guide">Developer guide</a> | <a href="#build-instructions">How to build?</a>
|
||||
</p>
|
||||
<h3 align="center">The mobile app is built with React Native for both iOS and Android.</h3>
|
||||
<p align="center"><a href="#build-instructions">Build instructions</a> | <a href="#developer-guide">Developer guide</a> | <a href="#running-e2e-tests-detox">E2E tests</a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=com.streetwriters.notesnook">
|
||||
@@ -25,21 +23,21 @@
|
||||
|
||||
Requirements:
|
||||
|
||||
1. [Node.js](https://nodejs.org/en/download/)
|
||||
1. [Node.js](https://nodejs.org/en/download/) 20+ (the repo is pinned to Node `22.20.0` via Volta)
|
||||
2. [git](https://git-scm.com/downloads)
|
||||
3. NPM (not yarn or pnpm)
|
||||
4. [React Native](https://reactnative.dev/docs/environment-setup)
|
||||
3. `npm`
|
||||
4. [React Native environment setup](https://reactnative.dev/docs/set-up-your-environment)
|
||||
|
||||
To run the app locally, you will need to setup React Native on your system:
|
||||
To run the app locally, first complete React Native native tooling setup:
|
||||
|
||||
1. Open the official [environment setup guide here](https://reactnative.dev/docs/environment-setup)
|
||||
1. Open [React Native environment setup](https://reactnative.dev/docs/set-up-your-environment)
|
||||
2. Select `React Native CLI Quickstart`
|
||||
3. Select your OS & the platform to run the app on (iOS or Android)
|
||||
3. Select your OS and target platform(s): iOS and/or Android
|
||||
4. Follow the steps listed.
|
||||
|
||||
> Please keep in mind that **Expo is not supported**.
|
||||
> Expo is not used in this project.
|
||||
|
||||
Once you have completed the setup, the first step is to `clone` the monorepo:
|
||||
Clone the monorepo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/streetwriters/notesnook.git
|
||||
@@ -48,7 +46,7 @@ git clone https://github.com/streetwriters/notesnook.git
|
||||
cd notesnook
|
||||
```
|
||||
|
||||
Once you are inside the `./notesnook` directory, run the preparation step:
|
||||
Install dependencies and bootstrap the mobile workspace:
|
||||
|
||||
```bash
|
||||
# this might take a while to complete
|
||||
@@ -58,17 +56,17 @@ npm run bootstrap -- --scope=mobile
|
||||
|
||||
### Running the app on Android
|
||||
|
||||
[Setup an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) if you haven't already, and then run the following command to start the app in the Emulator:
|
||||
[Set up an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) (or connect a physical device), then run:
|
||||
|
||||
```bash
|
||||
npm run start:android
|
||||
```
|
||||
|
||||
If you want to run the app on your phone, make sure to [enable USB debugging](https://developer.android.com/studio/debug/dev-options).
|
||||
If you are using a physical device, enable [USB debugging](https://developer.android.com/studio/debug/dev-options).
|
||||
|
||||
### Running the app on iOS
|
||||
|
||||
To run the app on iOS:
|
||||
Install CocoaPods dependencies first, then run the iOS app:
|
||||
|
||||
```bash
|
||||
# this might take a while to complete
|
||||
@@ -77,90 +75,87 @@ npm run prepare:ios
|
||||
npm run start:ios
|
||||
```
|
||||
|
||||
### Useful development commands
|
||||
|
||||
```bash
|
||||
# start Metro only
|
||||
npm run start:metro
|
||||
|
||||
# start Re.Pack bundler
|
||||
npm run start:repack
|
||||
```
|
||||
|
||||
## Developer guide
|
||||
|
||||
> This project is in a transition state between Javascript & Typescript. We are gradually porting everything over to Typescript, so if you can help with that, it'd be great!
|
||||
> The mobile app is a mixed TypeScript/JavaScript codebase.
|
||||
|
||||
### The tech stack
|
||||
|
||||
We try to keep the stack as lean as possible:
|
||||
|
||||
1. React Native
|
||||
2. Typescript/Javascript
|
||||
3. Zustand: State management
|
||||
4. Detox: Runs all our e2e tests
|
||||
5. React Native MMKV: Database & persistence
|
||||
6. libsodium: Encryption
|
||||
1. React Native `0.82`
|
||||
2. React `19`
|
||||
3. TypeScript + JavaScript
|
||||
4. Zustand (state management)
|
||||
5. Detox (end-to-end testing)
|
||||
6. libsodium (encryption)
|
||||
|
||||
### Project structure
|
||||
|
||||
The app codebase is distributed over two primary directories. `native/` and `app/`.
|
||||
Top-level directories in `apps/mobile/`:
|
||||
|
||||
- `android`: Android native code
|
||||
- `ios`: iOS native code
|
||||
- `app/`: Includes all the app code other than the native part. All JS-only dependencies are installed here.
|
||||
- `components/`: Each component serves a specific purpose in the app UI. For example, the `Paragraph` component is used to render paragraphs in the app, and a `Header` component is used to render a `header` on all screens.
|
||||
- `common/`: Features that are integral to the app's functionality. For example, the notesnook core is initialized here.
|
||||
- `hooks/`: Hooks for different app logic
|
||||
- `navigation/`: Includes app navigation-specific code. Here the app navigation, editor & side menu are rendered side by side in fluid tabs.
|
||||
- `screens`: Navigator screens.
|
||||
- `share`: Code for the iOS Share Extension and Android widget.
|
||||
- `services`: Parts of code that do a specific function. For example, the `sync` service runs Sync from anywhere in the app.
|
||||
- `stores`: We use `zustand` for global state management in the app. Multiple stores provide the state for different parts of the app.
|
||||
- `utils`: General purpose stuff such as constant values, utility functions, etc.
|
||||
- `app/`: Main React Native app source (`components`, `common`, `hooks`, `navigation`, `screens`, `services`, `stores`, `utils`, etc.)
|
||||
- `android/`: Android native project
|
||||
- `ios/`: iOS native project
|
||||
- `e2e/`: Detox test suite and config
|
||||
- `patches/`: `patch-package` patches
|
||||
- `scripts/`: Mobile-specific scripts
|
||||
|
||||
There are several other folders at the root:
|
||||
## Running E2E tests (Detox)
|
||||
|
||||
- `e2e/`: Detox End to end tests
|
||||
- `patches/`: Patches for various react native dependencies.
|
||||
Detox device defaults in this repo:
|
||||
|
||||
### Running the tests
|
||||
|
||||
When you are done making the required changes, you must run the tests to ensure you didn't break anything. We use Detox as the testing framework & the tests can be started as follows:
|
||||
- Android emulator: `Pixel_5_API_36`
|
||||
- iOS simulator: `iPhone 17 Pro Max`
|
||||
|
||||
### Android
|
||||
|
||||
To run the tests on Android, you will need to create an emulator device on your system:
|
||||
Build and run Android Detox tests:
|
||||
|
||||
```
|
||||
$ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_5_API_31 -d pixel --package "system-images;android-31;default;x86_64"
|
||||
```
|
||||
|
||||
If you face problems, follow the detailed guide in [Detox documentation](https://wix.github.io/Detox/docs/introduction/android-dev-env). Keep the emulator name set to `Pixel_5_API_31`.
|
||||
|
||||
Once you have created an emulator device, build the Android apks:
|
||||
|
||||
```
|
||||
```bash
|
||||
npm run build:android
|
||||
```
|
||||
|
||||
Finally, run the tests:
|
||||
|
||||
```
|
||||
npm run test:android
|
||||
```
|
||||
|
||||
For debug configuration:
|
||||
|
||||
```bash
|
||||
npm run build:android:debug
|
||||
npm run start:metro
|
||||
npm run test:android:debug
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
To run e2e tests on the iOS simulator, you must be on a Mac with XCode installed.
|
||||
|
||||
First, install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils):
|
||||
Build and run iOS Detox tests:
|
||||
|
||||
```bash
|
||||
npm run build:ios
|
||||
npm run test:ios
|
||||
```
|
||||
|
||||
If simulator tooling is missing, install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils):
|
||||
|
||||
```bash
|
||||
brew tap wix/brew
|
||||
brew install applesimutils
|
||||
```
|
||||
|
||||
Now build the iOS app for testing:
|
||||
## Release commands
|
||||
|
||||
```
|
||||
npm run build:ios
|
||||
```
|
||||
|
||||
Finally, run the tests:
|
||||
Android release helpers:
|
||||
|
||||
```bash
|
||||
npm run release:android
|
||||
npm run release:android:bundle
|
||||
```
|
||||
npm run test:ios
|
||||
```
|
||||
|
||||
All tests on iOS are configured to run on `iPhone 8` simulator.
|
||||
|
||||
@@ -5,6 +5,7 @@ apply plugin: 'kotlin-parcelize'
|
||||
|
||||
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
|
||||
|
||||
|
||||
import com.android.build.OutputFile
|
||||
import groovy.json.JsonSlurper
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
@@ -94,6 +95,18 @@ def fdroidBuild() {
|
||||
return project.hasProperty("fdroidBuild") && project.fdroidBuild == "true"
|
||||
}
|
||||
|
||||
def prBuildNumber() {
|
||||
def value = project.getProperties().get("prBuildNumber")
|
||||
return value ? value : "1000"
|
||||
}
|
||||
|
||||
def stagingReleaseBuild() {
|
||||
// Enable with STAGING_BUILDtrue or -PstagingReleaseBuild=true
|
||||
def fromEnv = System.getenv("STAGING_BUILD") == "true"
|
||||
def fromProp = project.hasProperty("stagingReleaseBuild") && project.stagingReleaseBuild == "true"
|
||||
return fromEnv || fromProp
|
||||
}
|
||||
|
||||
def getNpmVersion() {
|
||||
def inputFile = file("$rootDir/../package.json")
|
||||
def jsonPackage = new JsonSlurper().parseText(inputFile.text)
|
||||
@@ -118,14 +131,17 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
namespace "com.streetwriters.notesnook"
|
||||
defaultConfig {
|
||||
applicationId "com.streetwriters.notesnook"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3094
|
||||
if (project.hasProperty("prBuildNumber")) {
|
||||
versionCode Integer.parseInt(prBuildNumber())
|
||||
} else {
|
||||
versionCode 3099
|
||||
}
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -192,6 +208,8 @@ android {
|
||||
output.versionCodeOverride =
|
||||
defaultConfig.versionCode * 5 + versionCodes.get(abi)
|
||||
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
|
||||
} else if (stagingReleaseBuild()) {
|
||||
output.versionCodeOverride = defaultConfig.versionCode + versionCodes.get(abi)
|
||||
} else {
|
||||
if (isBuildingAAB) {
|
||||
output.versionCodeOverride = 4 * 1048576 + defaultConfig.versionCode
|
||||
@@ -233,7 +251,6 @@ dependencies {
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -199,11 +199,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".OnClearFromRecentService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:stopWithTask="false" />
|
||||
|
||||
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
|
||||
|
||||
<service
|
||||
|
||||
@@ -39,28 +39,32 @@ public class BootTaskService extends HeadlessJsTaskService {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
|
||||
"Default",
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
try {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
|
||||
"Default",
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
|
||||
notification = new NotificationCompat.Builder(this, channel.getId())
|
||||
.setContentTitle("Sync on boot")
|
||||
.setContentText("")
|
||||
.setSmallIcon(R.drawable.ic_stat_name)
|
||||
.build();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 34) {
|
||||
this.startForeground(
|
||||
1,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||
} else {
|
||||
this.startForeground(
|
||||
1,
|
||||
notification);
|
||||
}
|
||||
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
|
||||
notification = new NotificationCompat.Builder(this, channel.getId())
|
||||
.setContentTitle("Sync on boot")
|
||||
.setContentText("")
|
||||
.setSmallIcon(R.drawable.ic_stat_name)
|
||||
.build();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 34) {
|
||||
this.startForeground(
|
||||
1,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||
} else {
|
||||
this.startForeground(
|
||||
1,
|
||||
notification);
|
||||
}
|
||||
|
||||
} catch (Exception ignored) {
|
||||
stopSelf(startId);
|
||||
}
|
||||
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
|
||||
@@ -28,10 +28,6 @@ public class MainActivity extends ReactActivity {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
|
||||
try {
|
||||
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.streetwriters.notesnook;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class OnClearFromRecentService extends Service {
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(Intent rootIntent) {
|
||||
try {
|
||||
SharedPreferences appStateDetails = getApplicationContext().getSharedPreferences("appStateDetails", MODE_PRIVATE);
|
||||
SharedPreferences.Editor edit = appStateDetails.edit();
|
||||
edit.remove("appState");
|
||||
edit.apply();
|
||||
stopSelf();
|
||||
} catch (UnsatisfiedLinkError | Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,21 +93,6 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setAppState(final String appState) {
|
||||
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor edit = appStateDetails.edit();
|
||||
edit.putString("appState", appState);
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public String getAppState() {
|
||||
SharedPreferences appStateDetails = getReactApplicationContext().getSharedPreferences("appStateDetails", Context.MODE_PRIVATE);
|
||||
String appStateValue = appStateDetails.getString("appState", "");
|
||||
return appStateValue.isEmpty() ? null : appStateValue;
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public int getWidgetId() {
|
||||
return NotePreviewConfigureActivity.appWidgetId;
|
||||
|
||||
@@ -4,4 +4,10 @@
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
<base-config>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -36,6 +36,8 @@ allprojects {
|
||||
url "$rootDir/../node_modules/detox/Detox-android"
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
- Bug fixes and minor improvements
|
||||
- Fix clearing note title adds generated title instead of staying empty
|
||||
- Improved table cell selection, table scrolling, and table cell editing on mobile
|
||||
- Fixed app on launch randomly focuses editor instead of notes list
|
||||
- Fixed file size check shows error but continues adding the file to editor
|
||||
- Note title headline format should get content from first paragraph
|
||||
- Minor bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "Notesnook",
|
||||
"displayName": "Notesnook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +127,12 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
|
||||
.then((theme) => {
|
||||
if (theme) {
|
||||
theme.colorScheme === "dark"
|
||||
? useThemeStore.getState().setDarkTheme(theme)
|
||||
: useThemeStore.getState().setLightTheme(theme);
|
||||
? useThemeStore.setState({
|
||||
darkTheme: theme
|
||||
})
|
||||
: useThemeStore.setState({
|
||||
lightTheme: theme
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -138,9 +142,9 @@ export const withTheme = (Element: (props: any) => JSX.Element) => {
|
||||
|
||||
const listener = Appearance.addChangeListener(({ colorScheme }) => {
|
||||
if (colorScheme && SettingsService.getProperty("useSystemTheme")) {
|
||||
useThemeStore
|
||||
.getState()
|
||||
.setColorScheme(colorScheme as "light" | "dark");
|
||||
useThemeStore.setState({
|
||||
colorScheme: colorScheme as "light" | "dark"
|
||||
});
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
|
||||
@@ -205,8 +205,9 @@ export async function getDatabaseKey(appLockPassword?: string) {
|
||||
}
|
||||
|
||||
if (await Keychain.hasInternetCredentials("notesnook")) {
|
||||
const userKeyCredentials =
|
||||
await Keychain.getInternetCredentials("notesnook");
|
||||
const userKeyCredentials = await Keychain.getInternetCredentials(
|
||||
"notesnook"
|
||||
);
|
||||
|
||||
if (userKeyCredentials) {
|
||||
const userKeyCipher: Cipher = (await encrypt(
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function setupDatabase(password?: string) {
|
||||
({
|
||||
compress: Gzip.deflate,
|
||||
decompress: Gzip.inflate
|
||||
}) as ICompressor,
|
||||
} as ICompressor),
|
||||
batchSize: 50,
|
||||
sqliteOptions: {
|
||||
dialect: (name) => ({
|
||||
|
||||
@@ -33,6 +33,10 @@ import {
|
||||
} from "./utils";
|
||||
import Upload from "@ammarahmed/react-native-upload";
|
||||
import { CloudUploader } from "react-native-nitro-cloud-uploader";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
// Upload constants
|
||||
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
@@ -197,6 +201,20 @@ export async function uploadFile(
|
||||
const remoteFileSize = await getUploadedFileSize(filename);
|
||||
if (remoteFileSize === FileSizeResult.Error) return false;
|
||||
|
||||
const featureResult = await isFeatureAvailable(
|
||||
"fileSize",
|
||||
fileInfo.size || 0
|
||||
);
|
||||
|
||||
if (!featureResult.isAllowed) {
|
||||
ToastManager.show({
|
||||
heading: strings.fileTooLarge(),
|
||||
message: featureResult.error,
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
remoteFileSize > FileSizeResult.Empty &&
|
||||
remoteFileSize === fileInfo.size
|
||||
@@ -212,6 +230,9 @@ export async function uploadFile(
|
||||
);
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
const status = await PermissionsAndroid.request(
|
||||
"android.permission.POST_NOTIFICATIONS"
|
||||
);
|
||||
@@ -221,6 +242,10 @@ export async function uploadFile(
|
||||
type: "info"
|
||||
});
|
||||
}
|
||||
await sleep(500);
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
}
|
||||
|
||||
let uploaded = false;
|
||||
|
||||
@@ -161,10 +161,10 @@ export async function checkUpload(
|
||||
size === 0
|
||||
? `File size is 0.`
|
||||
: size === -1
|
||||
? `File verification check failed.`
|
||||
: expectedSize !== decryptedLength
|
||||
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
|
||||
: undefined;
|
||||
? `File verification check failed.`
|
||||
: expectedSize !== decryptedLength
|
||||
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
|
||||
: undefined;
|
||||
if (error) throw new Error(error);
|
||||
}
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ const AppLocked = () => {
|
||||
deviceMode !== "mobile"
|
||||
? "50%"
|
||||
: Platform.OS == "ios"
|
||||
? "95%"
|
||||
: "100%",
|
||||
? "95%"
|
||||
: "100%",
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 30,
|
||||
marginTop: 15,
|
||||
|
||||
@@ -59,6 +59,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import Navigation from "../../services/navigation";
|
||||
|
||||
const Actions = ({
|
||||
attachment,
|
||||
@@ -304,9 +305,9 @@ const Actions = ({
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
eSendEvent(eCloseSheet, contextId);
|
||||
await sleep(150);
|
||||
close?.();
|
||||
eSendEvent(eCloseAttachmentDialog);
|
||||
await sleep(300);
|
||||
Navigation.navigate("FluidPanelsView");
|
||||
openNote(item, (item as any).type === "trash");
|
||||
}}
|
||||
style={{
|
||||
|
||||
@@ -195,8 +195,8 @@ export const Login = ({
|
||||
? "50%"
|
||||
: "49.99%"
|
||||
: focused
|
||||
? "100%"
|
||||
: "99.9%",
|
||||
? "100%"
|
||||
: "99.9%",
|
||||
backgroundColor: colors.primary.background,
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: DDS.isTab ? 0 : DefaultAppStyles.GAP,
|
||||
|
||||
@@ -161,10 +161,10 @@ const FloatingButton = ({
|
||||
icon
|
||||
? icon
|
||||
: route.name === "Notebooks"
|
||||
? "notebook-plus"
|
||||
: route.name === "Trash"
|
||||
? "delete"
|
||||
: "plus"
|
||||
? "notebook-plus"
|
||||
: route.name === "Trash"
|
||||
? "delete"
|
||||
: "plus"
|
||||
}
|
||||
color={color || colors.primary.accent}
|
||||
size={size === "small" ? AppFontSize.xl : AppFontSize.xxxl}
|
||||
|
||||
@@ -136,8 +136,8 @@ const BaseDialog = ({
|
||||
backgroundColor: background
|
||||
? background
|
||||
: transparent
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
}}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
@@ -155,8 +155,8 @@ const BaseDialog = ({
|
||||
justifyContent: centered
|
||||
? "center"
|
||||
: bottom
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -201,14 +201,12 @@ export const AppLockPassword = () => {
|
||||
accountPass
|
||||
? strings.enterAccountPassword()
|
||||
: mode === "change"
|
||||
? keyboardType === "pin"
|
||||
? strings.newPin()
|
||||
: strings.newPassword()
|
||||
: `${
|
||||
keyboardType === "pin"
|
||||
? strings.pin()
|
||||
: strings.password()
|
||||
}`
|
||||
? keyboardType === "pin"
|
||||
? strings.newPin()
|
||||
: strings.newPassword()
|
||||
: `${
|
||||
keyboardType === "pin" ? strings.pin() : strings.password()
|
||||
}`
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -385,8 +383,8 @@ export const AppLockPassword = () => {
|
||||
mode === "remove"
|
||||
? strings.remove()
|
||||
: mode === "change"
|
||||
? strings.change()
|
||||
: strings.save()
|
||||
? strings.change()
|
||||
: strings.save()
|
||||
}
|
||||
negativeTitle={strings.cancel()}
|
||||
positiveType="transparent"
|
||||
|
||||
@@ -56,7 +56,12 @@ import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { Note, NoteContent, VAULT_ERRORS } from "@notesnook/core";
|
||||
import {
|
||||
isEncryptedContent,
|
||||
Note,
|
||||
NoteContent,
|
||||
VAULT_ERRORS
|
||||
} from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const VaultDialog: React.FC = () => {
|
||||
@@ -109,9 +114,17 @@ export const VaultDialog: React.FC = () => {
|
||||
const biometry = await BiometricService.isBiometryAvailable();
|
||||
const available = !!biometry;
|
||||
const fingerprint = await BiometricService.hasInternetCredentials();
|
||||
const noteLocked = data.item
|
||||
? await db.vaults.itemExists(data.item)
|
||||
: false;
|
||||
|
||||
if (data.item) {
|
||||
const locked = await db.vaults.itemExists(data.item);
|
||||
noteLockedRef.current = locked;
|
||||
if (!locked) {
|
||||
const content = await db.content.findByNoteId(data.item!.id);
|
||||
if (content && isEncryptedContent(content)) {
|
||||
noteLockedRef.current = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set refs
|
||||
noteRef.current = data.item;
|
||||
@@ -122,7 +135,6 @@ export const VaultDialog: React.FC = () => {
|
||||
positiveButtonTypeRef.current = data.positiveButtonType || "transparent";
|
||||
customActionTitleRef.current = data.customActionTitle || null;
|
||||
customActionParagraphRef.current = data.customActionParagraph || null;
|
||||
noteLockedRef.current = noteLocked;
|
||||
onUnlockRef.current = data.onUnlock;
|
||||
requestTypeRef.current = data.requestType;
|
||||
|
||||
@@ -417,9 +429,10 @@ export const VaultDialog: React.FC = () => {
|
||||
onUnlockRef.current
|
||||
) {
|
||||
const password = passwordRef.current;
|
||||
const unlock = onUnlockRef.current;
|
||||
close();
|
||||
await sleep(300);
|
||||
onUnlockRef.current(note, password);
|
||||
await sleep(500);
|
||||
unlock(note, password);
|
||||
}
|
||||
} catch (e) {
|
||||
takeErrorAction();
|
||||
|
||||
@@ -40,7 +40,6 @@ import Animated, {
|
||||
withTiming
|
||||
} from "react-native-reanimated";
|
||||
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||
import { getAppState } from "../../screens/editor/tiptap/utils";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { eClearEditor } from "../../utils/events";
|
||||
@@ -84,18 +83,9 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
}: TabProps,
|
||||
ref
|
||||
) {
|
||||
const appState = useMemo(() => getAppState(), []);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const fullscreen = useSettingStore((state) => state.fullscreen);
|
||||
const translateX = useSharedValue(
|
||||
widths
|
||||
? appState &&
|
||||
appState?.movedAway === false &&
|
||||
useTabStore.getState().getCurrentNoteId()
|
||||
? widths.sidebar + widths.list
|
||||
: widths.sidebar
|
||||
: 0
|
||||
);
|
||||
const translateX = useSharedValue(widths ? widths.sidebar : 0);
|
||||
const startX = useSharedValue(0);
|
||||
const currentTab = useSharedValue(1);
|
||||
const previousTab = useSharedValue(1);
|
||||
@@ -125,10 +115,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
translateX.value = 0;
|
||||
} else {
|
||||
if (prevWidths.current?.sidebar !== widths.sidebar) {
|
||||
translateX.value =
|
||||
appState && appState?.movedAway === false
|
||||
? editorPosition
|
||||
: widths.sidebar;
|
||||
translateX.value = widths.sidebar;
|
||||
if (translateX.value === editorPosition) {
|
||||
onChangeTab?.({ i: 2, from: 1 });
|
||||
}
|
||||
@@ -136,15 +123,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
}
|
||||
isLoaded.current = true;
|
||||
prevWidths.current = widths;
|
||||
}, [
|
||||
deviceMode,
|
||||
widths,
|
||||
fullscreen,
|
||||
translateX,
|
||||
editorPosition,
|
||||
appState,
|
||||
onChangeTab
|
||||
]);
|
||||
}, [deviceMode, widths, fullscreen, translateX, editorPosition, onChangeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
const sub = BackHandler.addEventListener("hardwareBackPress", () => {
|
||||
|
||||
@@ -32,9 +32,11 @@ import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||
import { fluidTabsRef } from "../../../utils/global-refs";
|
||||
|
||||
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
||||
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import { RouteParams } from "../../../stores/use-navigation-store";
|
||||
import NotePreview from "../../note-history/preview";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
import { RouteParams } from "../../../stores/use-navigation-store";
|
||||
|
||||
export const openNote = async (
|
||||
item: Note,
|
||||
@@ -63,11 +65,19 @@ export const openNote = async (
|
||||
component: <NotePreview note={item} content={content} />
|
||||
});
|
||||
} else {
|
||||
if (!useTabStore.getState().hasTabForNote(note.id!)) {
|
||||
editorController.current.commands.setLoading(
|
||||
true,
|
||||
useTabStore.getState().currentTab
|
||||
);
|
||||
}
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
setTimeout(() => {
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
}, 32);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,10 +77,10 @@ export default function List(props: ListProps) {
|
||||
props.renderedInRoute === "Notes"
|
||||
? "home"
|
||||
: props.renderedInRoute === "Favorites"
|
||||
? "favorites"
|
||||
: props.renderedInRoute === "Trash" || props.dataType === "trash"
|
||||
? "trash"
|
||||
: `${props.dataType}s`;
|
||||
? "favorites"
|
||||
: props.renderedInRoute === "Trash" || props.dataType === "trash"
|
||||
? "trash"
|
||||
: `${props.dataType}s`;
|
||||
|
||||
const groupOptions = useGroupOptions(groupType);
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ import { IconButton } from "../ui/icon-button";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
import { Cipher } from "@notesnook/crypto";
|
||||
|
||||
const MergeConflicts = () => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -84,12 +85,21 @@ const MergeConflicts = () => {
|
||||
dateEdited: contentToSave.dateEdited
|
||||
});
|
||||
|
||||
const noteLocked = await db.vaults.itemExists(note);
|
||||
const noteContent = await db.content.findByNoteId(note.id);
|
||||
|
||||
if (noteLocked) {
|
||||
await db.vault.save({
|
||||
...contentToSave,
|
||||
sessionId: `${Date.now()}`
|
||||
if (noteContent?.locked) {
|
||||
const selectedContent = contentToSave.conflicted
|
||||
? noteContent
|
||||
: noteContent.conflicted;
|
||||
|
||||
await db.content.add({
|
||||
id: note.contentId,
|
||||
dateResolved: noteContent?.conflicted?.dateModified || Date.now(),
|
||||
sessionId: `${Date.now()}`,
|
||||
data: selectedContent?.data as Cipher<"base64">,
|
||||
type: selectedContent?.type,
|
||||
locked: true,
|
||||
conflicted: undefined
|
||||
});
|
||||
} else {
|
||||
await db.content.add({
|
||||
@@ -138,7 +148,6 @@ const MergeConflicts = () => {
|
||||
onUnlock: async (item, password) => {
|
||||
if (!item || !password) return;
|
||||
const currentContent = await db.content.get(item.contentId!);
|
||||
|
||||
try {
|
||||
noteContent = {
|
||||
...(await db.content.get(item.contentId!)),
|
||||
|
||||
@@ -33,7 +33,6 @@ import AppIcon from "../ui/AppIcon";
|
||||
import { Button } from "../ui/button";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Dialog } from "../dialog";
|
||||
|
||||
const TOP_BAR_ITEMS: ActionId[] = [
|
||||
"pin",
|
||||
@@ -174,15 +173,15 @@ export const Items = ({
|
||||
DDS.isTab
|
||||
? AppFontSize.xxl
|
||||
: shouldShrink
|
||||
? AppFontSize.xxl
|
||||
: AppFontSize.lg
|
||||
? AppFontSize.xxl
|
||||
: AppFontSize.lg
|
||||
}
|
||||
color={
|
||||
item.checked
|
||||
? item.activeColor || colors.primary.accent
|
||||
: item.id.match(/(delete|trash)/g)
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
@@ -213,8 +212,8 @@ export const Items = ({
|
||||
text: item.checked
|
||||
? item.activeColor || colors.primary.accent
|
||||
: item.id === "delete" || item.id === "trash"
|
||||
? colors.error.paragraph
|
||||
: colors.primary.paragraph
|
||||
? colors.error.paragraph
|
||||
: colors.primary.paragraph
|
||||
}}
|
||||
testID={"icon-" + item.id}
|
||||
onPress={item.onPress}
|
||||
@@ -278,8 +277,8 @@ export const Items = ({
|
||||
item.checked
|
||||
? item.activeColor || colors.primary.accent
|
||||
: item.id === "delete" || item.id === "trash"
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -327,8 +326,9 @@ export const Items = ({
|
||||
|
||||
const getTopBarItemChunksOfFour = () => {
|
||||
const chunks = [];
|
||||
for (let i = 0; i < topBarItems.length; i += 5) {
|
||||
chunks.push(topBarItems.slice(i, i + 5));
|
||||
const itemCount = shouldShrink ? 4 : 5;
|
||||
for (let i = 0; i < topBarItems.length; i += itemCount) {
|
||||
chunks.push(topBarItems.slice(i, i + itemCount));
|
||||
}
|
||||
return chunks;
|
||||
};
|
||||
@@ -375,7 +375,8 @@ export const Items = ({
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
gap: 5
|
||||
gap: 5,
|
||||
width: width
|
||||
}}
|
||||
>
|
||||
{item.map(renderTopBarItem)}
|
||||
|
||||
@@ -356,7 +356,7 @@ const ProductItem = (props: {
|
||||
(product as RNIap.Subscription)?.productId
|
||||
) ||
|
||||
props.pricingPlans.user?.subscription?.productId ===
|
||||
(product as Plan).id);
|
||||
(product as Plan)?.id);
|
||||
|
||||
useEffect(() => {
|
||||
props.pricingPlans
|
||||
|
||||
@@ -102,9 +102,17 @@ const TabItemComponent = (props: {
|
||||
{props.tab.session?.noteLocked ? (
|
||||
<>
|
||||
{props.tab.session?.locked ? (
|
||||
<Icon size={AppFontSize.md} name="lock" />
|
||||
<Icon
|
||||
size={AppFontSize.md}
|
||||
name="lock"
|
||||
color={colors.primary.icon}
|
||||
/>
|
||||
) : (
|
||||
<Icon size={AppFontSize.md} name="lock-open-outline" />
|
||||
<Icon
|
||||
size={AppFontSize.md}
|
||||
name="lock-open-outline"
|
||||
color={colors.primary.icon}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -118,8 +118,8 @@ const ListBlockItem = ({
|
||||
{item?.content.length > 200
|
||||
? item?.content.slice(0, 200) + "..."
|
||||
: !item.content || item.content.trim() === ""
|
||||
? strings.linkNoteEmptyBlock()
|
||||
: item.content}
|
||||
? strings.linkNoteEmptyBlock()
|
||||
: item.content}
|
||||
</Paragraph>
|
||||
|
||||
<View
|
||||
|
||||
@@ -53,15 +53,9 @@ async function fetchMonographData(noteId: string) {
|
||||
const monograph = monographId
|
||||
? await db.monographs.get(monographId)
|
||||
: undefined;
|
||||
const analyticsFeature = await isFeatureAvailable("monographAnalytics");
|
||||
const analytics =
|
||||
monographId && analyticsFeature
|
||||
? await db.monographs.analytics(monographId)
|
||||
: undefined;
|
||||
return {
|
||||
monograph,
|
||||
monographId,
|
||||
analytics
|
||||
};
|
||||
}
|
||||
|
||||
@@ -381,9 +375,6 @@ const PublishNoteSheet = ({
|
||||
}}
|
||||
>
|
||||
<Paragraph size={AppFontSize.sm}>{strings.views()}</Paragraph>
|
||||
<Paragraph>
|
||||
{monographData?.result?.analytics?.totalViews || 0}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -104,8 +104,8 @@ const ListBlockItem = ({
|
||||
{item?.content.length > 200
|
||||
? item?.content.slice(0, 200) + "..."
|
||||
: !item.content || item.content.trim() === ""
|
||||
? strings.linkNoteEmptyBlock()
|
||||
: item.content}
|
||||
? strings.linkNoteEmptyBlock()
|
||||
: item.content}
|
||||
</Paragraph>
|
||||
|
||||
<View
|
||||
|
||||
@@ -114,8 +114,8 @@ export const UserSheet = () => {
|
||||
progress ? `(${progress.current})` : ""
|
||||
}`
|
||||
: lastSyncStatus === SyncStatus.Failed
|
||||
? strings.syncFailed()
|
||||
: strings.synced()}{" "}
|
||||
? strings.syncFailed()
|
||||
: strings.synced()}{" "}
|
||||
{!syncing ? (
|
||||
<TimeSince
|
||||
style={{
|
||||
@@ -139,8 +139,8 @@ export const UserSheet = () => {
|
||||
!user || lastSyncStatus === SyncStatus.Failed
|
||||
? colors.error.icon
|
||||
: isOffline
|
||||
? colors.static.orange
|
||||
: colors.success.icon
|
||||
? colors.static.orange
|
||||
: colors.success.icon
|
||||
}
|
||||
/>
|
||||
</Paragraph>
|
||||
|
||||
@@ -136,8 +136,8 @@ const SimpleTabView = ({
|
||||
{loadedKeysRef.current.has(route.key)
|
||||
? getSceneForRoute(route)
|
||||
: navigationState.index === routeIndex
|
||||
? getSceneForRoute(route)
|
||||
: null}
|
||||
? getSceneForRoute(route)
|
||||
: null}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -435,8 +435,9 @@ const TabBar = (props: SimpleTabBarProps) => {
|
||||
color={colors.primary.icon}
|
||||
onPress={async () => {
|
||||
if (props.navigationState.index === 1) {
|
||||
const notebooksFeature =
|
||||
await isFeatureAvailable("notebooks");
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
PaywallSheet.present(notebooksFeature);
|
||||
return;
|
||||
|
||||
@@ -151,8 +151,8 @@ export function MenuItem({
|
||||
item.icon === "crown"
|
||||
? colors.static.yellow
|
||||
: isFocused
|
||||
? colors.selected.icon
|
||||
: colors.secondary.icon
|
||||
? colors.selected.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
size={AppFontSize.md}
|
||||
/>
|
||||
|
||||
@@ -98,8 +98,8 @@ export const NotebookItem = ({
|
||||
item.depth === 0
|
||||
? undefined
|
||||
: item.depth < 6
|
||||
? 15 * item.depth
|
||||
: 15 * 5,
|
||||
? 15 * item.depth
|
||||
: 15 * 5,
|
||||
width: "100%",
|
||||
marginTop: 2
|
||||
}}
|
||||
@@ -187,8 +187,8 @@ export const NotebookItem = ({
|
||||
!item.hasChildren || disableExpand
|
||||
? "book-outline"
|
||||
: expanded
|
||||
? "chevron-down"
|
||||
: "chevron-right"
|
||||
? "chevron-down"
|
||||
: "chevron-right"
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -241,6 +241,7 @@ export const NotebookItem = ({
|
||||
name="plus"
|
||||
size={AppFontSize.md}
|
||||
testID={`add-notebook-${index}`}
|
||||
color={colors.primary.icon}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
|
||||
@@ -303,7 +303,7 @@ export const SideMenuTags = () => {
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
}, 500);
|
||||
}, 100);
|
||||
}}
|
||||
placeholderTextColor={colors.primary.placeholder}
|
||||
/>
|
||||
|
||||
@@ -158,22 +158,22 @@ export const Toast = ({ context = "global" }) => {
|
||||
toastOptions.icon
|
||||
? toastOptions.icon
|
||||
: toastOptions.type === "success"
|
||||
? "check"
|
||||
: toastOptions.type === "info"
|
||||
? "information"
|
||||
: "close"
|
||||
? "check"
|
||||
: toastOptions.type === "info"
|
||||
? "information"
|
||||
: "close"
|
||||
}
|
||||
size={isFullToastMessage ? AppFontSize.xxxl : AppFontSize.xl}
|
||||
color={
|
||||
toastOptions?.icon
|
||||
? toastOptions?.icon
|
||||
: toastOptions.type === "error"
|
||||
? colors.error.icon
|
||||
: toastOptions.type === "info"
|
||||
? isDark
|
||||
? colors.static.white
|
||||
: colors.static.black
|
||||
: colors.success.icon
|
||||
? colors.error.icon
|
||||
: toastOptions.type === "info"
|
||||
? isDark
|
||||
? colors.static.white
|
||||
: colors.static.black
|
||||
: colors.success.icon
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { ColorValue, TextProps } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import EvilIcon from "react-native-vector-icons/EvilIcons";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
|
||||
export interface IconProps extends TextProps {
|
||||
@@ -43,11 +44,22 @@ export interface IconProps extends TextProps {
|
||||
*
|
||||
*/
|
||||
color?: ColorValue | number | undefined;
|
||||
|
||||
iconFamily?: "evilicons" | "material";
|
||||
}
|
||||
|
||||
export default function AppIcon(props: IconProps) {
|
||||
export default function AppIcon({
|
||||
iconFamily = "material",
|
||||
...props
|
||||
}: IconProps) {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
return iconFamily === "evilicons" ? (
|
||||
<EvilIcon
|
||||
size={AppFontSize.md}
|
||||
color={colors.primary.icon}
|
||||
{...(props as any)}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
size={AppFontSize.md}
|
||||
color={colors.primary.icon}
|
||||
|
||||
@@ -107,7 +107,7 @@ export const IconButton = ({
|
||||
color={
|
||||
restProps.disabled
|
||||
? RGB_Linear_Shade(-0.05, hexToRGBA(colors.secondary.background))
|
||||
: colors.static[color as never] || color
|
||||
: colors.static[color as never] || color || colors.primary.icon
|
||||
}
|
||||
size={size}
|
||||
/>
|
||||
|
||||
@@ -121,8 +121,8 @@ const Input = ({
|
||||
const color = error
|
||||
? colors.error.border
|
||||
: focus
|
||||
? customColor || colors.selected.border
|
||||
: colors.primary.border;
|
||||
? customColor || colors.selected.border
|
||||
: colors.primary.border;
|
||||
|
||||
const validate = async (value: string) => {
|
||||
if (!validationType) return;
|
||||
|
||||
@@ -268,8 +268,8 @@ export const Pressable = ({
|
||||
const opacity = customOpacity
|
||||
? customOpacity
|
||||
: type === "accent"
|
||||
? 1
|
||||
: colorOpacity;
|
||||
? 1
|
||||
: colorOpacity;
|
||||
const alpha = customAlpha ? customAlpha : isDark ? 0.03 : -0.03;
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const growFactor = 1 + (fontScale - 1) / 8;
|
||||
|
||||
@@ -159,7 +159,7 @@ const SheetWrapper = ({
|
||||
{bottomPadding ? (
|
||||
<View
|
||||
style={{
|
||||
height: bottomInsets
|
||||
height: 10
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import {
|
||||
Color,
|
||||
createInternalLink,
|
||||
isEncryptedContent,
|
||||
Item,
|
||||
ItemReference,
|
||||
Note,
|
||||
@@ -221,7 +222,15 @@ export const useActions = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (item.type === "note") {
|
||||
db.vaults.itemExists(item).then((locked) => setLocked(locked));
|
||||
db.vaults.itemExists(item).then(async (locked) => {
|
||||
setLocked(locked);
|
||||
if (!locked) {
|
||||
const content = await db.content.findByNoteId(item.id);
|
||||
if (content && isEncryptedContent(content)) {
|
||||
setLocked(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [item]);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { strings } from "@notesnook/intl";
|
||||
import notifee from "@notifee/react-native";
|
||||
import NetInfo, { NetInfoSubscription } from "@react-native-community/netinfo";
|
||||
import dayjs from "dayjs";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
AppState,
|
||||
@@ -48,6 +49,7 @@ import * as RNIap from "react-native-iap";
|
||||
import { DatabaseLogger, db, setupDatabase } from "../common/database";
|
||||
import { initializeLogger } from "../common/database/logger";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { deleteDCacheFiles } from "../common/filesystem/io";
|
||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
import Migrate from "../components/sheets/migrate";
|
||||
import NewFeature from "../components/sheets/new-feature";
|
||||
@@ -58,11 +60,7 @@ import {
|
||||
resetTabStore,
|
||||
useTabStore
|
||||
} from "../screens/editor/tiptap/use-tab-store";
|
||||
import {
|
||||
clearAppState,
|
||||
editorController,
|
||||
editorState
|
||||
} from "../screens/editor/tiptap/utils";
|
||||
import { editorController, editorState } from "../screens/editor/tiptap/utils";
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import BackupService from "../services/backup";
|
||||
import BiometricService from "../services/biometrics";
|
||||
@@ -89,6 +87,7 @@ import { clearAllStores, initAfterSync } from "../stores";
|
||||
import { refreshAllStores } from "../stores/create-db-collection-store";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { useMessageStore } from "../stores/use-message-store";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { SyncStatus, useUserStore } from "../stores/use-user-store";
|
||||
import { updateStatusBarColor } from "../utils/colors";
|
||||
@@ -105,12 +104,8 @@ import {
|
||||
} from "../utils/events";
|
||||
import { getGithubVersion } from "../utils/github-version";
|
||||
import { fluidTabsRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import useFeatureManager from "./use-feature-manager";
|
||||
import { deleteDCacheFiles } from "../common/filesystem/io";
|
||||
import dayjs from "dayjs";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -173,7 +168,6 @@ const onAppOpenedFromURL = async (event: {
|
||||
if (url.startsWith("https://app.notesnook.com/account/verified")) {
|
||||
await onUserEmailVerified();
|
||||
} else if (url.startsWith("ShareMedia://QuickNoteWidget")) {
|
||||
clearAppState();
|
||||
editorState().movedAway = false;
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
fluidTabsRef.current?.goToPage("editor", false);
|
||||
@@ -361,23 +355,6 @@ async function checkForShareExtensionLaunchedInBackground() {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function saveEditorState() {
|
||||
if (!editorState().movedAway) {
|
||||
const id = useTabStore.getState().getCurrentNoteId();
|
||||
const note = id ? await db.notes.note(id) : undefined;
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
if (locked) return;
|
||||
const state = JSON.stringify({
|
||||
editing: editorState().currentlyEditing,
|
||||
movedAway: editorState().movedAway,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
NotesnookModule.setAppState(state);
|
||||
} else {
|
||||
NotesnookModule.setAppState("");
|
||||
}
|
||||
}
|
||||
|
||||
const onSuccessfulSubscription = async (
|
||||
subscription: RNIap.ProductPurchase | RNIap.SubscriptionPurchase
|
||||
) => {
|
||||
@@ -537,7 +514,6 @@ const initializeDatabase = async (password?: string) => {
|
||||
Notifications.restorePinnedNotes();
|
||||
expiringNotesTimer();
|
||||
deleteDCacheFiles();
|
||||
db.attachments.removeOrphaned();
|
||||
}
|
||||
Walkthrough.init();
|
||||
};
|
||||
@@ -593,13 +569,13 @@ export const useAppEvents = () => {
|
||||
}, [isAppLoading, onSyncComplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialUrl) {
|
||||
if (initialUrl && !isAppLoading) {
|
||||
onAppOpenedFromURL({
|
||||
url: initialUrl!,
|
||||
isInitialUrl: true
|
||||
});
|
||||
}
|
||||
}, [initialUrl]);
|
||||
}, [initialUrl, isAppLoading]);
|
||||
|
||||
const subscribeToPurchaseListeners = useCallback(async () => {
|
||||
if (Platform.OS === "android") {
|
||||
@@ -839,7 +815,6 @@ export const useAppEvents = () => {
|
||||
Sync.run("global", false, "full");
|
||||
reconnectSSE();
|
||||
await checkForShareExtensionLaunchedInBackground();
|
||||
NotesnookModule.setAppState("");
|
||||
let user = await db.user.getUser();
|
||||
if (user && !user?.isEmailConfirmed) {
|
||||
try {
|
||||
@@ -862,7 +837,6 @@ export const useAppEvents = () => {
|
||||
eSendEvent(eEditorReset);
|
||||
}
|
||||
} else {
|
||||
await saveEditorState();
|
||||
if (
|
||||
SettingsService.canLockAppInBackground() &&
|
||||
!useSettingStore.getState().requestBiometrics &&
|
||||
|
||||
@@ -60,23 +60,17 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
): [ItemTypeKey[T] | undefined, () => void] => {
|
||||
const [item, setItem] = useState<ItemTypeKey[T]>();
|
||||
const itemIdRef = useRef<string>(undefined);
|
||||
const prevIdOrIndexRef = useRef<string | number>(undefined);
|
||||
|
||||
if (prevIdOrIndexRef.current !== idOrIndex) {
|
||||
itemIdRef.current = undefined;
|
||||
prevIdOrIndexRef.current = idOrIndex;
|
||||
}
|
||||
const itemsRef = useRef(item);
|
||||
itemsRef.current = item;
|
||||
|
||||
useEffect(() => {
|
||||
const onUpdateItem = async (itemId?: string) => {
|
||||
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
|
||||
if (!isValidIdOrIndex(idOrIndex)) return;
|
||||
|
||||
let item: ItemTypeKey[T] | undefined = undefined;
|
||||
if (items && typeof idOrIndex === "number") {
|
||||
const item = (await items.item(idOrIndex))?.item;
|
||||
setItem(item);
|
||||
itemIdRef.current = item?.id;
|
||||
onItemUpdated?.(item);
|
||||
item = (await items.item(idOrIndex))?.item;
|
||||
} else {
|
||||
if (!(db as any)[type + "s"][type]) {
|
||||
console.warn(
|
||||
@@ -84,24 +78,30 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
`db.${type}s.${type}(id: string)`
|
||||
);
|
||||
} else {
|
||||
const item = await (db as any)[type + "s"]?.[type]?.(
|
||||
idOrIndex as string
|
||||
);
|
||||
setItem(item);
|
||||
itemIdRef.current = item.id;
|
||||
onItemUpdated?.(item);
|
||||
item = await (db as any)[type + "s"]?.[type]?.(idOrIndex as string);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
itemsRef.current === item ||
|
||||
itemsRef.current?.dateModified === item?.dateModified
|
||||
)
|
||||
return;
|
||||
setItem(item);
|
||||
itemIdRef.current = item?.id;
|
||||
onItemUpdated?.(item);
|
||||
};
|
||||
let unsub: (() => void) | undefined;
|
||||
if (
|
||||
useSettingStore.getState().isAppLoading &&
|
||||
//@ts-ignore
|
||||
!globalThis["IS_SHARE_EXTENSION"]
|
||||
) {
|
||||
useSettingStore.subscribe((state) => {
|
||||
unsub = useSettingStore.subscribe((state) => {
|
||||
if (!state.isAppLoading) {
|
||||
onUpdateItem();
|
||||
unsub?.();
|
||||
unsub = undefined;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -109,9 +109,10 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
|
||||
}
|
||||
eSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||
return () => {
|
||||
unsub?.();
|
||||
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
|
||||
};
|
||||
}, [idOrIndex, type, items, onItemUpdated]);
|
||||
}, [idOrIndex, type, items]);
|
||||
|
||||
return [
|
||||
isValidIdOrIndex(idOrIndex) ? (item as ItemTypeKey[T]) : undefined,
|
||||
@@ -134,8 +135,9 @@ export const useNoteLocked = (noteId: string | undefined) => {
|
||||
//@ts-ignore
|
||||
!globalThis["IS_SHARE_EXTENSION"]
|
||||
) {
|
||||
useSettingStore.subscribe((state) => {
|
||||
let unsub = useSettingStore.subscribe((state) => {
|
||||
if (!state.isAppLoading) {
|
||||
unsub();
|
||||
db.vaults
|
||||
.itemExists({
|
||||
type: "note",
|
||||
@@ -181,25 +183,27 @@ export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
|
||||
const [totalNotesById, setTotalNotesById] = useState<{
|
||||
[id: string]: number;
|
||||
}>({});
|
||||
const totalNotesRef = useRef(0);
|
||||
|
||||
const getTotalNotes = React.useCallback(
|
||||
(ids: string[]) => {
|
||||
if (!ids || !ids.length || !type) return;
|
||||
db.relations
|
||||
.from({ type: type, ids: ids as string[] }, ["note"])
|
||||
.get()
|
||||
.then((relations) => {
|
||||
const totalNotesById: any = {};
|
||||
for (const id of ids) {
|
||||
totalNotesById[id] = relations.filter(
|
||||
(relation) => relation.fromId === id && relation.toType === "note"
|
||||
)?.length;
|
||||
}
|
||||
setTotalNotesById(totalNotesById);
|
||||
});
|
||||
},
|
||||
[type]
|
||||
);
|
||||
const getTotalNotes = React.useCallback((ids: string[]) => {
|
||||
if (!ids || !ids.length || !type) return;
|
||||
db.relations
|
||||
.from({ type: type, ids: ids as string[] }, ["note"])
|
||||
.get()
|
||||
.then((relations) => {
|
||||
const totalNotesById: any = {};
|
||||
|
||||
for (const id of ids) {
|
||||
totalNotesById[id] = relations.filter(
|
||||
(relation) => relation.fromId === id && relation.toType === "note"
|
||||
)?.length;
|
||||
}
|
||||
|
||||
if (totalNotesById === totalNotesRef.current) return;
|
||||
totalNotesRef.current = totalNotesById;
|
||||
setTotalNotesById(totalNotesById);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
totalNotes: (id: string) => {
|
||||
|
||||
@@ -276,8 +276,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
(product as Plan).period === "yearly"
|
||||
? (product as Plan).price.gross
|
||||
: (product as Plan).period === "5-year"
|
||||
? (product as Plan).price.gross
|
||||
: (product as Plan).price.gross
|
||||
? (product as Plan).price.gross
|
||||
: (product as Plan).price.gross
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -432,8 +432,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
return period.endsWith("W")
|
||||
? "week"
|
||||
: period.endsWith("M")
|
||||
? "month"
|
||||
: "year";
|
||||
? "month"
|
||||
: "year";
|
||||
} else {
|
||||
const unit = (product as RNIap.SubscriptionIOS)
|
||||
?.subscriptionPeriodUnitIOS;
|
||||
@@ -475,8 +475,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
type: phase.billingPeriod.endsWith("W")
|
||||
? "week"
|
||||
: phase.billingPeriod.endsWith("M")
|
||||
? "month"
|
||||
: "year"
|
||||
? "month"
|
||||
: "year"
|
||||
};
|
||||
} else {
|
||||
const productIos = product as RNIap.SubscriptionIOS;
|
||||
@@ -607,8 +607,8 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
(product as Plan).period === "yearly"
|
||||
? ((product as Plan).price.gross / 12).toFixed(2)
|
||||
: (product as Plan).period === "5-year"
|
||||
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
|
||||
: (product as Plan).price.gross
|
||||
? ((product as Plan).price.gross / (12 * 5)).toFixed(2)
|
||||
: (product as Plan).price.gross
|
||||
}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import { useEffect } from "react";
|
||||
import { NativeEventEmitter, NativeModule } from "react-native";
|
||||
import { useRef } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { Linking } from "react-native";
|
||||
import deviceInfoModule from "react-native-device-info";
|
||||
import { strings } from "@notesnook/intl";
|
||||
const ShortcutsEmitter = new NativeEventEmitter(
|
||||
@@ -54,14 +53,9 @@ export const useShortcutManager = ({
|
||||
}, [shortcuts]);
|
||||
|
||||
useEffect(() => {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url?.startsWith("ShareMedia://QuickNoteWidget")) {
|
||||
onShortcutPressed(defaultShortcuts[0]);
|
||||
}
|
||||
});
|
||||
if (!isSupported()) return;
|
||||
Shortcuts.getInitialShortcut().then((shortcut) => {
|
||||
if (initialShortcutRecieved.current) return;
|
||||
if (initialShortcutRecieved.current || !shortcut) return;
|
||||
onShortcutPressed(shortcut);
|
||||
initialShortcutRecieved.current = true;
|
||||
});
|
||||
|
||||
@@ -47,12 +47,7 @@ import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
|
||||
import { useShortcutManager } from "../hooks/use-shortcut-manager";
|
||||
import { hideAllTooltips } from "../hooks/use-tooltip";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import {
|
||||
clearAppState,
|
||||
editorController,
|
||||
editorState,
|
||||
getAppState
|
||||
} from "../screens/editor/tiptap/utils";
|
||||
import { editorController, editorState } from "../screens/editor/tiptap/utils";
|
||||
import { DDS } from "../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -118,13 +113,9 @@ export const FluidPanelsView = React.memo(
|
||||
|
||||
useShortcutManager({
|
||||
onShortcutPressed: async (item) => {
|
||||
if (!item && getAppState()) {
|
||||
editorState().movedAway = false;
|
||||
fluidTabsRef.current?.goToPage("editor", false);
|
||||
return;
|
||||
}
|
||||
if (!item) return;
|
||||
|
||||
if (item?.type === "notesnook.action.newnote") {
|
||||
clearAppState();
|
||||
if (!fluidTabsRef.current) {
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
@@ -205,7 +196,6 @@ export const FluidPanelsView = React.memo(
|
||||
eSendEvent(eCloseFullscreenEditor, current);
|
||||
}
|
||||
|
||||
const state = getAppState();
|
||||
setTimeout(() => {
|
||||
switch (current) {
|
||||
case "tablet":
|
||||
@@ -217,18 +207,10 @@ export const FluidPanelsView = React.memo(
|
||||
}
|
||||
break;
|
||||
case "mobile":
|
||||
if (
|
||||
state &&
|
||||
editorState().movedAway === false &&
|
||||
useTabStore.getState().getCurrentNoteId()
|
||||
) {
|
||||
fluidTabsRef.current?.goToPage("editor", false);
|
||||
} else {
|
||||
fluidTabsRef.current?.goToPage(
|
||||
fluidTabsRef.current?.page(),
|
||||
false
|
||||
);
|
||||
}
|
||||
fluidTabsRef.current?.goToPage(
|
||||
fluidTabsRef.current?.page(),
|
||||
false
|
||||
);
|
||||
break;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
@@ -400,45 +400,17 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
: recurringMode === RecurringModes.Week
|
||||
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
|
||||
(item) => (
|
||||
<Button
|
||||
key={strings.weekDayNamesShort[
|
||||
item as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
title={strings.weekDayNamesShort[
|
||||
item as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
type={
|
||||
selectedDays.indexOf(item) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(item) > -1) {
|
||||
days.splice(days.indexOf(item), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(item);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)
|
||||
: MonthDays.map((item, index) => (
|
||||
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
|
||||
(item) => (
|
||||
<Button
|
||||
key={index + "monthday"}
|
||||
title={index + 1 + ""}
|
||||
key={strings.weekDayNamesShort[
|
||||
item as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
title={strings.weekDayNamesShort[
|
||||
item as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
type={
|
||||
selectedDays.indexOf(index + 1) > -1
|
||||
selectedDays.indexOf(item) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
@@ -450,16 +422,44 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index + 1) > -1) {
|
||||
days.splice(days.indexOf(index + 1), 1);
|
||||
if (days.indexOf(item) > -1) {
|
||||
days.splice(days.indexOf(item), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index + 1);
|
||||
days.push(item);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
)
|
||||
)
|
||||
: MonthDays.map((item, index) => (
|
||||
<Button
|
||||
key={index + "monthday"}
|
||||
title={index + 1 + ""}
|
||||
type={
|
||||
selectedDays.indexOf(index + 1) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index + 1) > -1) {
|
||||
days.splice(days.indexOf(index + 1), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index + 1);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
@@ -542,24 +542,24 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: recurringMode === RecurringModes.Year
|
||||
? strings.reminderRepeatStrings.year(
|
||||
dayjs(date).format("dddd, MMMM D, h:mm A")
|
||||
)
|
||||
: selectedDays.length === 7 &&
|
||||
recurringMode === RecurringModes.Week
|
||||
? strings.reminderRepeatStrings.week.daily(
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: selectedDays.length === 0
|
||||
? strings.reminderRepeatStrings[
|
||||
recurringMode as "week" | "month"
|
||||
].selectDays()
|
||||
: strings.reminderRepeatStrings.repeats(
|
||||
repeatFrequency,
|
||||
recurringMode as string,
|
||||
getSelectedDaysText(selectedDays),
|
||||
dayjs(date).format("hh:mm A")
|
||||
)}
|
||||
? strings.reminderRepeatStrings.year(
|
||||
dayjs(date).format("dddd, MMMM D, h:mm A")
|
||||
)
|
||||
: selectedDays.length === 7 &&
|
||||
recurringMode === RecurringModes.Week
|
||||
? strings.reminderRepeatStrings.week.daily(
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: selectedDays.length === 0
|
||||
? strings.reminderRepeatStrings[
|
||||
recurringMode as "week" | "month"
|
||||
].selectDays()
|
||||
: strings.reminderRepeatStrings.repeats(
|
||||
repeatFrequency,
|
||||
recurringMode as string,
|
||||
getSelectedDaysText(selectedDays),
|
||||
dayjs(date).format("hh:mm A")
|
||||
)}
|
||||
</Paragraph>
|
||||
</>
|
||||
</View>
|
||||
@@ -587,8 +587,8 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
mode === "Silent"
|
||||
? "minus-circle"
|
||||
: mode === "Vibrate"
|
||||
? "vibrate"
|
||||
: "volume-high"
|
||||
? "vibrate"
|
||||
: "volume-high"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
height={35}
|
||||
|
||||
@@ -90,6 +90,7 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
message: featureResult.error,
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileCopyUri.status === "error") {
|
||||
@@ -272,6 +273,7 @@ const handleImageResponse = async (
|
||||
message: featureResult.error,
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const b64 = `data:${image.mime};base64, ` + image.data;
|
||||
|
||||
@@ -559,7 +559,7 @@ export const useEditorEvents = (
|
||||
.then((data: any) => {
|
||||
console.log(
|
||||
"Got attachment data:",
|
||||
data,
|
||||
!!data,
|
||||
editorMessage.resolverId
|
||||
);
|
||||
editor.postMessage(NativeEvents.resolve, {
|
||||
|
||||
@@ -69,14 +69,7 @@ import Commands from "./commands";
|
||||
import { SessionHistory } from "./session-history";
|
||||
import { EditorState, SavePayload } from "./types";
|
||||
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
|
||||
import {
|
||||
clearAppState,
|
||||
defaultState,
|
||||
getAppState,
|
||||
isContentInvalid,
|
||||
isEditorLoaded,
|
||||
post
|
||||
} from "./utils";
|
||||
import { defaultState, isContentInvalid, isEditorLoaded, post } from "./utils";
|
||||
import { Linking } from "react-native";
|
||||
|
||||
const loadNoteMutex = new Mutex();
|
||||
@@ -167,7 +160,7 @@ export const useEditor = (
|
||||
const tags = useTagStore((state) => state.items);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const isDefaultEditor = editorId === "";
|
||||
const saveCount = useRef(0);
|
||||
const saveCount = useRef<Record<string, number>>({});
|
||||
const lastContentChangeTime = useRef<Record<string, number>>({});
|
||||
const lock = useRef(false);
|
||||
const currentLoadingNoteId = useRef<string>(undefined);
|
||||
@@ -253,9 +246,9 @@ export const useEditor = (
|
||||
clearTimeout(timers.current["loading-images" + noteId]);
|
||||
}
|
||||
|
||||
saveCount.current = 0;
|
||||
currentLoadingNoteId.current = undefined;
|
||||
lock.current = false;
|
||||
saveCount.current[tabId] = 0;
|
||||
resetContent && postMessage(NativeEvents.title, "", tabId);
|
||||
resetContent && (await commands.clearContent(tabId));
|
||||
resetContent && (await commands.clearTags(tabId));
|
||||
@@ -388,7 +381,7 @@ export const useEditor = (
|
||||
|
||||
if (
|
||||
oldNote?.title !== currentNotes.current[id]?.title &&
|
||||
!noteData.title
|
||||
typeof noteData.title === "undefined"
|
||||
) {
|
||||
postMessage(
|
||||
NativeEvents.title,
|
||||
@@ -436,10 +429,9 @@ export const useEditor = (
|
||||
lastContentChangeTime.current[id] = note.dateEdited;
|
||||
|
||||
if (
|
||||
saveCount.current < 2 ||
|
||||
saveCount.current[tabId] < 2 ||
|
||||
currentNotes.current[id]?.title !== note.title ||
|
||||
currentNotes.current[id]?.headline?.slice(0, 200) !==
|
||||
note.headline?.slice(0, 200)
|
||||
currentNotes.current[id]?.headline !== note.headline
|
||||
) {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}
|
||||
@@ -455,7 +447,11 @@ export const useEditor = (
|
||||
currentNotes.current[id] = note;
|
||||
}
|
||||
|
||||
saveCount.current++;
|
||||
if (!saveCount.current[tabId]) {
|
||||
saveCount.current[tabId] = 1;
|
||||
} else {
|
||||
saveCount.current[tabId]++;
|
||||
}
|
||||
|
||||
clearTimeout(timers.current.onsave);
|
||||
timers.current.onsave = setTimeout(async () => {
|
||||
@@ -487,6 +483,17 @@ export const useEditor = (
|
||||
};
|
||||
} else if (note.contentId) {
|
||||
const rawContent = await db.content?.get(note.contentId);
|
||||
|
||||
if (rawContent && isEncryptedContent(rawContent)) {
|
||||
useTabStore.getState().updateTab(useTabStore.getState().currentTab, {
|
||||
session: {
|
||||
noteLocked: true,
|
||||
locked: true
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (rawContent && !isDeleted(rawContent) && !rawContent.locked) {
|
||||
currentContents.current[note.id] = {
|
||||
data: rawContent.data,
|
||||
@@ -553,6 +560,7 @@ export const useEditor = (
|
||||
useTabStore.getState().newTabSession(tabId, {});
|
||||
}
|
||||
}
|
||||
saveCount.current[tabId] = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
if (state.current?.ready && !state.current.movedAway)
|
||||
@@ -685,6 +693,7 @@ export const useEditor = (
|
||||
currentNotes.current[item.id] = item;
|
||||
|
||||
editorSessionHistory.newSession(item.id);
|
||||
saveCount.current[tabId] = 0;
|
||||
|
||||
await commands.setStatus(
|
||||
getFormattedDate(item.dateEdited, "date-time"),
|
||||
@@ -1014,22 +1023,11 @@ export const useEditor = (
|
||||
);
|
||||
|
||||
const restoreEditorState = useCallback(async () => {
|
||||
const appState = getAppState();
|
||||
if (!appState) return;
|
||||
state.current.isRestoringState = true;
|
||||
state.current.currentlyEditing = true;
|
||||
if (fluidTabsRef.current?.page() === "editor") {
|
||||
state.current.movedAway = false;
|
||||
}
|
||||
if (!state.current.editorStateRestored) {
|
||||
state.current.isRestoringState = true;
|
||||
if (!DDS.isTab) {
|
||||
fluidTabsRef.current?.goToPage("editor", false);
|
||||
}
|
||||
}
|
||||
|
||||
clearAppState();
|
||||
state.current.isRestoringState = false;
|
||||
}, []);
|
||||
|
||||
const onContentChanged = (noteId?: string) => {
|
||||
|
||||
@@ -141,34 +141,6 @@ export function isContentInvalid(content: string | undefined) {
|
||||
);
|
||||
}
|
||||
|
||||
const canRestoreAppState = (appState: AppState) => {
|
||||
return appState.editing && Date.now() < appState.timestamp + 3600000;
|
||||
};
|
||||
|
||||
let appState: AppState | undefined;
|
||||
export function setAppState(state: AppState) {
|
||||
appState = state;
|
||||
}
|
||||
export function getAppState() {
|
||||
if (appState && canRestoreAppState(appState)) return appState as AppState;
|
||||
const json = NotesnookModule.getAppState();
|
||||
if (json) {
|
||||
appState = JSON.parse(json) as AppState;
|
||||
if (canRestoreAppState(appState)) {
|
||||
return appState;
|
||||
} else {
|
||||
clearAppState();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function clearAppState() {
|
||||
appState = undefined;
|
||||
NotesnookModule.setAppState("");
|
||||
}
|
||||
|
||||
export async function openInternalLink(url: string) {
|
||||
const data = parseInternalLink(url);
|
||||
if (!data?.id) return false;
|
||||
|
||||
@@ -237,8 +237,9 @@ const LinkNotebooks = (props: NavigationProps<"LinkNotebooks">) => {
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: async () => {
|
||||
const notebooksFeature =
|
||||
await isFeatureAvailable("notebooks");
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
@@ -387,8 +388,8 @@ const NotebookItemWrapper = React.memo(
|
||||
!selected
|
||||
? "selected"
|
||||
: !state.initialState[item.notebook.id]
|
||||
? undefined
|
||||
: "deselected"
|
||||
? undefined
|
||||
: "deselected"
|
||||
);
|
||||
}}
|
||||
canDisableSelectionMode={false}
|
||||
|
||||
@@ -417,15 +417,15 @@ const TagItem = ({
|
||||
selection === "selected"
|
||||
? "check-circle-outline"
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
name={
|
||||
selection === "selected"
|
||||
? "check-circle-outline"
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -242,8 +242,9 @@ export const MoveNotebook = (props: NavigationProps<"MoveNotebook">) => {
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: async () => {
|
||||
const notebooksFeature =
|
||||
await isFeatureAvailable("notebooks");
|
||||
const notebooksFeature = await isFeatureAvailable(
|
||||
"notebooks"
|
||||
);
|
||||
if (!notebooksFeature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: notebooksFeature.error,
|
||||
|
||||
@@ -322,8 +322,8 @@ export const MFASetup = ({
|
||||
method?.id === "email"
|
||||
? user?.email
|
||||
: method?.id === "app"
|
||||
? authenticatorDetails?.sharedKey || ""
|
||||
: undefined
|
||||
? authenticatorDetails?.sharedKey || ""
|
||||
: undefined
|
||||
}
|
||||
multiline={method.id === "app"}
|
||||
onChangeText={(value) => {
|
||||
@@ -356,12 +356,12 @@ export const MFASetup = ({
|
||||
sending
|
||||
? null
|
||||
: method.id === "app"
|
||||
? strings.copy()
|
||||
: `${
|
||||
seconds
|
||||
? strings.resendCode(seconds as number)
|
||||
: strings.sendCode()
|
||||
}`
|
||||
? strings.copy()
|
||||
: `${
|
||||
seconds
|
||||
? strings.resendCode(seconds as number)
|
||||
: strings.sendCode()
|
||||
}`
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -75,15 +75,15 @@ export default function DebugLogs() {
|
||||
item.level === LogLevel.Error || item.level === LogLevel.Fatal
|
||||
? hexToRGBA(colors.error.paragraph, 0.2)
|
||||
: item.level === LogLevel.Warn
|
||||
? hexToRGBA(colors.static.orange, 0.2)
|
||||
: "transparent";
|
||||
? hexToRGBA(colors.static.orange, 0.2)
|
||||
: "transparent";
|
||||
|
||||
const color =
|
||||
item.level === LogLevel.Error || item.level === LogLevel.Fatal
|
||||
? colors.error.paragraph
|
||||
: item.level === LogLevel.Warn
|
||||
? colors.static.black
|
||||
: colors.primary.paragraph;
|
||||
? colors.static.black
|
||||
: colors.primary.paragraph;
|
||||
|
||||
return !item ? null : (
|
||||
<TouchableOpacity
|
||||
|
||||
@@ -24,7 +24,6 @@ import Animated, { FadeInDown, FadeOutDown } from "react-native-reanimated";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { Notice } from "../../../components/ui/notice";
|
||||
import Paragraph from "../../../components/ui/typography/paragraph";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { Group } from "./group";
|
||||
@@ -41,7 +40,7 @@ export const ConfigureToolbar = () => {
|
||||
|
||||
const renderGroups = () => {
|
||||
return data?.map((item, index) => (
|
||||
<Group key={`group-${index}`} item={item} index={index} />
|
||||
<Group key={`group-${index}-${item.length}`} item={item} index={index} />
|
||||
));
|
||||
};
|
||||
|
||||
@@ -131,6 +130,7 @@ export const ConfigureToolbar = () => {
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
key={preset}
|
||||
scrollEventThrottle={13}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
|
||||
@@ -31,7 +31,7 @@ import { renderTool } from "./common";
|
||||
import { DraggableItem, useDragState } from "./state";
|
||||
import ToolSheet from "./tool-sheet";
|
||||
|
||||
import { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
|
||||
import type { ToolId } from "@notesnook/editor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -64,78 +64,87 @@ export const Group = ({
|
||||
const { colors } = useThemeColors();
|
||||
const featureAvailable = useIsFeatureAvailable("customToolbarPreset");
|
||||
|
||||
const onDrop = (data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data.slice();
|
||||
|
||||
if (dragged.type === "group") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
_data.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
_data.splice(fromIndex, 1)[0]
|
||||
);
|
||||
}
|
||||
|
||||
// Always insert sub group at the end of the group.
|
||||
if (dragged.type === "subgroup") {
|
||||
const fromIndex = dragged.index;
|
||||
|
||||
const insertAt = _data[reciever.index] as string[];
|
||||
const insertFrom = _data[dragged.groupIndex] as string[];
|
||||
|
||||
if (typeof insertAt[insertAt.length - 1] !== "string") {
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
const onDrop = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error || strings.featureNotAvailable()
|
||||
});
|
||||
return;
|
||||
}
|
||||
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
|
||||
}
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data.slice();
|
||||
|
||||
if (dragged.type === "tool") {
|
||||
const insertFrom =
|
||||
typeof dragged.parentIndex === "number"
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
_data[groupIndex].push(insertFrom.splice(dragged.index, 1)[0] as ToolId);
|
||||
}
|
||||
if (dragged.type === "group") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
};
|
||||
_data.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
_data.splice(fromIndex, 1)[0]
|
||||
);
|
||||
}
|
||||
|
||||
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.dragged.payload.type !== "group")
|
||||
return setRecievePosition("below");
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
};
|
||||
// Always insert sub group at the end of the group.
|
||||
if (dragged.type === "subgroup") {
|
||||
const fromIndex = dragged.index;
|
||||
|
||||
const insertAt = _data[reciever.index] as string[];
|
||||
const insertFrom = _data[dragged.groupIndex] as string[];
|
||||
|
||||
if (typeof insertAt[insertAt.length - 1] !== "string") {
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
}
|
||||
insertAt.push(insertFrom.splice(fromIndex, 1)[0]);
|
||||
}
|
||||
|
||||
if (dragged.type === "tool") {
|
||||
const insertFrom =
|
||||
typeof dragged.parentIndex === "number"
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
_data[groupIndex].push(
|
||||
insertFrom.splice(dragged.index, 1)[0] as ToolId
|
||||
);
|
||||
}
|
||||
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
},
|
||||
[featureAvailable]
|
||||
);
|
||||
|
||||
const onRecieveData = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.dragged.payload.type !== "group")
|
||||
return setRecievePosition("below");
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
name: "minus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -157,11 +166,12 @@ export const Group = ({
|
||||
},
|
||||
{
|
||||
name: "plus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ export default function ToolSheet({
|
||||
fwdRef: RefObject<ActionSheetRef>;
|
||||
}) {
|
||||
const { colors } = useThemeColors();
|
||||
const data = useDragState((state) => state.data);
|
||||
const ungrouped = getUngroupedTools(data) as ToolId[];
|
||||
const [data] = useDragState((state) => [state.data]);
|
||||
const ungrouped = getUngroupedTools( data) as ToolId[];
|
||||
|
||||
const renderTool = React.useCallback(
|
||||
(item: ToolId) => {
|
||||
@@ -114,7 +114,7 @@ export default function ToolSheet({
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
maxHeight: "100%",
|
||||
maxHeight: 400,
|
||||
padding: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -33,7 +33,7 @@ import { DraggableItem, useDragState } from "./state";
|
||||
import ToolSheet from "./tool-sheet";
|
||||
import { findToolById, getToolIcon } from "./toolbar-definition";
|
||||
|
||||
import { useIsFeatureAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useIsFeatureAvailable } from "@notesnook/common";
|
||||
import type { ToolId } from "@notesnook/editor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -78,11 +78,12 @@ export const Tool = ({
|
||||
? [
|
||||
{
|
||||
name: "minus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -102,11 +103,12 @@ export const Tool = ({
|
||||
},
|
||||
{
|
||||
name: "plus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -122,11 +124,12 @@ export const Tool = ({
|
||||
: [
|
||||
{
|
||||
name: "minus",
|
||||
onPress: () => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customToolbarPreset");
|
||||
if (!feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
message: feature?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -151,7 +154,7 @@ export const Tool = ({
|
||||
if (parentIndex === undefined && !isSubgroup) {
|
||||
btns.unshift({
|
||||
name: "unfold-less-horizontal",
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
if (groupIndex === undefined) return;
|
||||
const _data = useDragState.getState().data.slice();
|
||||
const hasSubGroup = Array.isArray(
|
||||
@@ -267,6 +270,7 @@ export const Tool = ({
|
||||
style={{
|
||||
paddingLeft: 30
|
||||
}}
|
||||
key={`subgroup-${item.length}-${groupIndex}-${index}-${parentIndex}`}
|
||||
>
|
||||
{renderGroup({ index, item, parentIndex: groupIndex })}
|
||||
</View>
|
||||
@@ -290,63 +294,69 @@ export const Tool = ({
|
||||
]
|
||||
);
|
||||
|
||||
const onDrop = (data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data?.slice();
|
||||
if (!_data) return;
|
||||
const isFromSubgroup = typeof dragged?.parentIndex === "number";
|
||||
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
|
||||
|
||||
if (dragged.type === "tool") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
const insertAt = isDroppedAtSubgroup
|
||||
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
|
||||
: (_data[reciever.groupIndex] as string[]);
|
||||
const insertFrom = isFromSubgroup
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
insertAt.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
insertFrom.splice(fromIndex, 1)[0]
|
||||
);
|
||||
|
||||
// Remove the group or subgroup if it is empty.
|
||||
if (insertFrom.length === 0) {
|
||||
isFromSubgroup
|
||||
? _data[dragged.parentIndex].splice(
|
||||
_data[dragged.parentIndex].length - 1,
|
||||
1
|
||||
)
|
||||
: _data.splice(dragged.groupIndex, 1);
|
||||
const onDrop = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
if (!featureAvailable?.isAllowed) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: featureAvailable?.error || strings.featureNotAvailable()
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
};
|
||||
const isDroppedAbove = data.receiver.receiveOffsetRatio.y < 0.5;
|
||||
const dragged = data.dragged.payload;
|
||||
const reciever = data.receiver.payload;
|
||||
const _data = useDragState.getState().data?.slice();
|
||||
if (!_data) return;
|
||||
const isFromSubgroup = typeof dragged?.parentIndex === "number";
|
||||
const isDroppedAtSubgroup = typeof reciever?.parentIndex === "number";
|
||||
|
||||
const onRecieveData = (data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
};
|
||||
if (dragged.type === "tool") {
|
||||
const fromIndex = dragged.index;
|
||||
const toIndex = isDroppedAbove
|
||||
? Math.max(0, reciever.index)
|
||||
: reciever.index + 1;
|
||||
|
||||
const insertAt = isDroppedAtSubgroup
|
||||
? (_data[reciever.parentIndex][reciever.groupIndex] as string[])
|
||||
: (_data[reciever.groupIndex] as string[]);
|
||||
const insertFrom = isFromSubgroup
|
||||
? (_data[dragged.parentIndex][dragged.groupIndex] as string[])
|
||||
: (_data[dragged.groupIndex] as string[]);
|
||||
insertAt.splice(
|
||||
toIndex > fromIndex ? toIndex - 1 : toIndex,
|
||||
0,
|
||||
insertFrom.splice(fromIndex, 1)[0]
|
||||
);
|
||||
|
||||
// Remove the group or subgroup if it is empty.
|
||||
if (insertFrom.length === 0) {
|
||||
isFromSubgroup
|
||||
? _data[dragged.parentIndex].splice(
|
||||
_data[dragged.parentIndex].length - 1,
|
||||
1
|
||||
)
|
||||
: _data.splice(dragged.groupIndex, 1);
|
||||
}
|
||||
}
|
||||
setData(_data);
|
||||
setRecieving(false);
|
||||
return data.dragAbsolutePosition;
|
||||
},
|
||||
[featureAvailable]
|
||||
);
|
||||
|
||||
const onRecieveData = React.useCallback(
|
||||
(data: DraxDragWithReceiverEventData) => {
|
||||
setRecieving(true);
|
||||
if (data.receiver.receiveOffsetRatio.y < 0.5) {
|
||||
setRecievePosition("above");
|
||||
} else {
|
||||
setRecievePosition("below");
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Animated.View layout={Layout}>
|
||||
|
||||
@@ -25,18 +25,18 @@ import {
|
||||
} from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
||||
import { ToolId } from "@notesnook/editor";
|
||||
|
||||
export const tools = getAllTools() as any;
|
||||
export const tools = () => getAllTools();
|
||||
export const presets: { [name: string]: ToolbarGroupDefinition[] } = {
|
||||
default: getDefaultPresets().default as any,
|
||||
minimal: getDefaultPresets().minimal as any,
|
||||
custom: []
|
||||
};
|
||||
|
||||
export function findToolById(id: keyof typeof tools): {
|
||||
export function findToolById(id: keyof ReturnType<typeof tools> ): {
|
||||
title: string;
|
||||
icon: string;
|
||||
} {
|
||||
return tools[id];
|
||||
return tools()[id];
|
||||
}
|
||||
|
||||
export function getToolIcon(id: ToolId, color: string) {
|
||||
@@ -51,13 +51,14 @@ export function getToolIcon(id: ToolId, color: string) {
|
||||
|
||||
export function getUngroupedTools(
|
||||
toolDefinition: (string | string[])[][]
|
||||
): string[] {
|
||||
const keys = Object.keys(tools);
|
||||
): string[] {
|
||||
const allTools = tools();
|
||||
const keys = Object.keys(allTools);
|
||||
|
||||
const ungrouped = [];
|
||||
const toolString = JSON.stringify(toolDefinition);
|
||||
for (const key of keys) {
|
||||
if (tools[key as ToolId].conditional) continue;
|
||||
if ((allTools[key as ToolId] as any).conditional) continue;
|
||||
if (!toolString.includes(key)) ungrouped.push(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -123,8 +123,8 @@ export const TrashIntervalPicker = createSettingsPicker({
|
||||
return item === -1
|
||||
? strings.never()
|
||||
: item === 1
|
||||
? strings.reminderRecurringMode.day()
|
||||
: strings.days(item);
|
||||
? strings.reminderRecurringMode.day()
|
||||
: strings.days(item);
|
||||
},
|
||||
getItemKey: (item) => item.toString(),
|
||||
options: [-1, 1, 7, 30, 365],
|
||||
@@ -276,10 +276,10 @@ export const ApplockTimerPicker = createSettingsPicker({
|
||||
return item === -1
|
||||
? strings.never()
|
||||
: item === 0 || item === undefined
|
||||
? strings.immediately()
|
||||
: item === 1
|
||||
? strings.minutes(1)
|
||||
: strings.minutes(item);
|
||||
? strings.immediately()
|
||||
: item === 1
|
||||
? strings.minutes(1)
|
||||
: strings.minutes(item);
|
||||
},
|
||||
getItemKey: (item) => item.toString(),
|
||||
options: [-1, 0, 1, 5, 15, 30],
|
||||
|
||||
@@ -570,13 +570,17 @@ const BackupItem = ({
|
||||
paragraph: strings.restoreBackupConfirm(),
|
||||
positiveText: strings.restore(),
|
||||
negativeText: strings.cancel(),
|
||||
context: "global",
|
||||
positivePress: async () => {
|
||||
restoreBackup({
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? (item as ScopedStorage.FileType).uri
|
||||
: (item as ReactNativeBlobUtilStat).path
|
||||
});
|
||||
setTimeout(() => {
|
||||
restoreBackup({
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? (item as ScopedStorage.FileType).uri
|
||||
: (item as ReactNativeBlobUtilStat).path
|
||||
});
|
||||
}, 500);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from "@react-navigation/native";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, TextInput, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { FeatureResult, useIsFeatureAvailable } from "@notesnook/common";
|
||||
//@ts-ignore
|
||||
import ToggleSwitch from "toggle-switch-react-native";
|
||||
@@ -218,14 +217,15 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
}}
|
||||
>
|
||||
{!!item.icon && (
|
||||
<Icon
|
||||
<AppIcon
|
||||
color={
|
||||
item.type === "danger"
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
iconFamily={item.iconFamily}
|
||||
name={item.icon}
|
||||
size={30}
|
||||
size={item.iconSize || 30}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -92,6 +92,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "delete-data",
|
||||
name: strings.deleteData(),
|
||||
icon: "delete",
|
||||
description: strings.deleteAccountDesc(),
|
||||
modifer: () => {
|
||||
presentDialog({
|
||||
@@ -205,15 +206,15 @@ export const settingsGroups: SettingSection[] = [
|
||||
return status === SubscriptionStatus.TRIAL
|
||||
? strings.trialOnGoing(trialEndDate)
|
||||
: status === SubscriptionStatus.ACTIVE
|
||||
? strings.subRenewOn(expiryDate)
|
||||
: status === SubscriptionStatus.CANCELED ||
|
||||
status === SubscriptionStatus.PAUSED
|
||||
? strings.subEndsOn(expiryDate)
|
||||
: status === SubscriptionStatus.EXPIRED
|
||||
? subscriptionDaysLeft.time < -3
|
||||
? strings.subEnded()
|
||||
: strings.accountDowngradedIn(3)
|
||||
: strings.neverHesitate();
|
||||
? strings.subRenewOn(expiryDate)
|
||||
: status === SubscriptionStatus.CANCELED ||
|
||||
status === SubscriptionStatus.PAUSED
|
||||
? strings.subEndsOn(expiryDate)
|
||||
: status === SubscriptionStatus.EXPIRED
|
||||
? subscriptionDaysLeft.time < -3
|
||||
? strings.subEnded()
|
||||
: strings.accountDowngradedIn(3)
|
||||
: strings.neverHesitate();
|
||||
}
|
||||
|
||||
return strings.neverHesitate();
|
||||
@@ -1243,7 +1244,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "backup-now",
|
||||
id: "backup-now-with-attachments",
|
||||
name: strings.backupNowWithAttachments(),
|
||||
description: strings.backupNowWithAttachmentsDesc(),
|
||||
hidden: () => !useUserStore.getState().user,
|
||||
@@ -1545,6 +1546,9 @@ export const settingsGroups: SettingSection[] = [
|
||||
id: "join-telegram",
|
||||
name: strings.joinTelegram(),
|
||||
description: strings.joinTelegramDesc(),
|
||||
icon: "sc-telegram",
|
||||
iconFamily: "evilicons",
|
||||
iconSize: 35,
|
||||
modifer: () => {
|
||||
Linking.openURL("https://t.me/notesnook").catch(() => {
|
||||
/* empty */
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { TextInput } from "react-native";
|
||||
import { Settings } from "../../stores/use-setting-store";
|
||||
import { FeatureId } from "@notesnook/common";
|
||||
import { IconProps } from "../../components/ui/AppIcon";
|
||||
|
||||
export type SettingSection = {
|
||||
id: string;
|
||||
@@ -34,6 +35,8 @@ export type SettingSection = {
|
||||
name?: string | ((current?: unknown) => string);
|
||||
description?: string | ((current: unknown) => string);
|
||||
icon?: string;
|
||||
iconFamily?: IconProps["iconFamily"];
|
||||
iconSize?: number;
|
||||
property?: keyof Settings;
|
||||
sections?: SettingSection[];
|
||||
component?: string;
|
||||
|
||||
@@ -319,9 +319,8 @@ async function run(
|
||||
};
|
||||
}
|
||||
|
||||
await sleep(300);
|
||||
|
||||
if (canShowCompletionStatus) {
|
||||
await sleep(500);
|
||||
presentBackupCompleteSheet(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Platform } from "react-native";
|
||||
import { db } from "../common/database";
|
||||
import { setAppState } from "../screens/editor/tiptap/utils";
|
||||
import { eOnLoadNote } from "../utils/events";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { eSendEvent } from "./event-manager";
|
||||
@@ -37,14 +36,6 @@ export const IntentService = {
|
||||
onLaunch() {
|
||||
if (launched || Platform.OS === "ios") return;
|
||||
launched = true;
|
||||
if (launchIntent["com.streetwriters.notesnook.OpenNoteId"]) {
|
||||
setAppState({
|
||||
movedAway: false,
|
||||
editing: true,
|
||||
timestamp: Date.now(),
|
||||
noteId: launchIntent["com.streetwriters.notesnook.OpenNoteId"]
|
||||
});
|
||||
}
|
||||
},
|
||||
async onAppStateChanged() {
|
||||
if (Platform.OS === "ios") return;
|
||||
|
||||
@@ -146,7 +146,7 @@ const updateAvailableMessage = (
|
||||
icon: "update",
|
||||
type: "normal",
|
||||
id: "app-update"
|
||||
}) as Message;
|
||||
} as Message);
|
||||
|
||||
export function setUpdateAvailableMessage(
|
||||
version: GithubVersionInfo | CheckVersionResponse
|
||||
|
||||
@@ -138,7 +138,6 @@ const onEvent = async ({ type, detail }: Event) => {
|
||||
if (type === EventType.PRESS) {
|
||||
notifee.decrementBadgeCount();
|
||||
if (notification?.data?.type === "quickNote") return;
|
||||
NotesnookModule.setAppState("");
|
||||
if (notification?.data?.type === "reminder" && notification?.id) {
|
||||
const reminder = await db.reminders?.reminder(
|
||||
notification.id?.split("_")[0]
|
||||
@@ -453,13 +452,6 @@ async function loadNote(id: string, jump: boolean) {
|
||||
if (!DDS.isTab && jump) {
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
}
|
||||
NotesnookModule.setAppState(
|
||||
JSON.stringify({
|
||||
editing: true,
|
||||
movedAway: false,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
);
|
||||
|
||||
const tab = useTabStore.getState().getTabForNote(id);
|
||||
if (useTabStore.getState().currentTab !== tab) {
|
||||
|
||||
@@ -195,8 +195,8 @@ const NotebookItem = ({
|
||||
!nestedNotebooks?.placeholders.length
|
||||
? "book-outline"
|
||||
: isExpanded
|
||||
? "chevron-down"
|
||||
: "chevron-right"
|
||||
? "chevron-down"
|
||||
: "chevron-right"
|
||||
}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
@@ -538,10 +538,10 @@ export const Search = ({
|
||||
{searchKeyword
|
||||
? `No results found for "${searchKeyword}"`
|
||||
: mode === "appendNote"
|
||||
? "No notes"
|
||||
: mode === "selectTags"
|
||||
? "No tags"
|
||||
: "No notebooks"}
|
||||
? "No notes"
|
||||
: mode === "selectTags"
|
||||
? "No tags"
|
||||
: "No notebooks"}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ import { initDatabase, useShareStore } from "./store";
|
||||
import { isTablet } from "react-native-device-info";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { DefaultAppStyles } from "../utils/styles";
|
||||
import { encodeHTML5 } from "entities";
|
||||
|
||||
const getLinkPreview = (url: string) => {
|
||||
return getPreviewData(url, 5000);
|
||||
@@ -81,7 +82,7 @@ async function sanitizeHtml(site: string, title?: string) {
|
||||
return (
|
||||
html +
|
||||
"<hr/>" +
|
||||
`<p>Clipped from <a href="${site}">${title || site}</a></p>
|
||||
`<p>Clipped from <a href="${site}">${encodeHTML5(title || site)}</a></p>
|
||||
<p>Date clipped ${getFormattedDate(Date.now())}</p>`
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -96,7 +97,7 @@ function makeHtmlFromUrl(url: string) {
|
||||
function makeHtmlFromPlainText(text: string) {
|
||||
if (!text) return "";
|
||||
|
||||
return `<p>${text
|
||||
return `<p>${encodeHTML5(text)
|
||||
.replace(/[\n]+/g, "\n")
|
||||
.replace(/(?:\r\n|\r|\n)/g, "</p><p>")}</p>`;
|
||||
}
|
||||
@@ -419,8 +420,8 @@ const ShareView = () => {
|
||||
let html = !rawData.value
|
||||
? ""
|
||||
: isURL(rawData?.value)
|
||||
? makeHtmlFromUrl(rawData?.value)
|
||||
: makeHtmlFromPlainText(rawData?.value);
|
||||
? makeHtmlFromUrl(rawData?.value)
|
||||
: makeHtmlFromPlainText(rawData?.value);
|
||||
setNote((note) => {
|
||||
note.content.data = html;
|
||||
noteContent.current = html;
|
||||
|
||||
@@ -76,18 +76,14 @@ export const useThemeStore = create<ThemeStore>((set, get) => ({
|
||||
? (Appearance.getColorScheme() as "dark" | "light")
|
||||
: SettingsService.get().colorScheme,
|
||||
setDarkTheme: (darkTheme) => {
|
||||
switchThemeWithAnimation(() => {
|
||||
set({ darkTheme });
|
||||
changeSystemBarColors();
|
||||
SettingsService.setProperty("darkTheme", darkTheme);
|
||||
});
|
||||
set({ darkTheme });
|
||||
changeSystemBarColors();
|
||||
SettingsService.setProperty("darkTheme", darkTheme);
|
||||
},
|
||||
setLightTheme: (lightTheme) => {
|
||||
switchThemeWithAnimation(() => {
|
||||
set({ lightTheme });
|
||||
changeSystemBarColors();
|
||||
SettingsService.setProperty("lighTheme", lightTheme);
|
||||
});
|
||||
set({ lightTheme });
|
||||
changeSystemBarColors();
|
||||
SettingsService.setProperty("lighTheme", lightTheme);
|
||||
},
|
||||
setColorScheme: (colorScheme) => {
|
||||
switchThemeWithAnimation(() => {
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface UserStore {
|
||||
appLocked: boolean;
|
||||
lockApp: (verified: boolean) => void;
|
||||
disableAppLockRequests: boolean;
|
||||
setDisableAppLockRequests: (shouldBlockVerifyUser: boolean) => void;
|
||||
setDisableAppLockRequests: (disableAppLockRequests: boolean) => void;
|
||||
profile?: Partial<Profile>;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@ interface NotesnookModuleInterface {
|
||||
getActivityName: () => Promise<string>;
|
||||
setBackgroundColor: (color: string) => void;
|
||||
setSecureMode: (enabled: boolean) => void;
|
||||
setAppState: (appState: string) => void;
|
||||
getAppState: () => string;
|
||||
saveAndFinish: () => void;
|
||||
setString: (storeName: string, key: string, value: string) => void;
|
||||
getString: (storeName: string, key: string) => Promise<string>;
|
||||
@@ -72,8 +70,6 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({
|
||||
getActivityName: () => {},
|
||||
setBackgroundColor: () => {},
|
||||
setSecureMode: () => {},
|
||||
setAppState: () => {},
|
||||
getAppState: () => {},
|
||||
saveAndFinish: () => {},
|
||||
getString: () => {},
|
||||
setString: () => {},
|
||||
|
||||
@@ -159,7 +159,7 @@ function show(event: any, text: string, position = 2) {
|
||||
duration: 2000,
|
||||
autoHide: true,
|
||||
clickToHide: true,
|
||||
shadow: true
|
||||
shadow: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { notesnook } from "../test.ids";
|
||||
import { TestBuilder } from "./utils";
|
||||
|
||||
describe("Sort & filter", () => {
|
||||
it.only("Sort by date-edited/date-created", async () => {
|
||||
it("Sort by date-edited/date-created", async () => {
|
||||
await TestBuilder.create()
|
||||
.prepare()
|
||||
.createNote("Note 1", "Note 1")
|
||||
@@ -34,7 +34,7 @@ describe("Sort & filter", () => {
|
||||
.element(by.web.className("ProseMirror"))
|
||||
.typeText("Edited ", true);
|
||||
})
|
||||
.pressBack(1)
|
||||
.pressBack(2)
|
||||
.waitAndTapById("icon-sort")
|
||||
.wait(500)
|
||||
.waitAndTapByText("Date created")
|
||||
|
||||
@@ -46,9 +46,10 @@ describe("Tags", () => {
|
||||
.waitAndTapByText('Add "#testtag"')
|
||||
.isVisibleByText("#testtag")
|
||||
.pressBack(2)
|
||||
.wait(1000)
|
||||
.openSideMenu()
|
||||
.waitAndTapById("tab-tags")
|
||||
.waitAndTapByText("#testtag")
|
||||
.waitAndTapByText("testtag")
|
||||
.processResult(async (note) => {
|
||||
await TestBuilder.create().isVisibleByText(note.body).run();
|
||||
})
|
||||
|
||||
@@ -32,10 +32,7 @@ const testvars = {
|
||||
|
||||
class Element {
|
||||
element: Detox.NativeElement;
|
||||
constructor(
|
||||
public type: "id" | "text",
|
||||
public value: string
|
||||
) {
|
||||
constructor(public type: "id" | "text", public value: string) {
|
||||
if (type == "id") {
|
||||
this.element = element(by.id(value)).atIndex(0);
|
||||
} else {
|
||||
|
||||
BIN
apps/mobile/fonts/EvilIcons.ttf
Normal file
BIN
apps/mobile/fonts/EvilIcons.ttf
Normal file
Binary file not shown.
@@ -17,9 +17,10 @@ Notifications.init();
|
||||
|
||||
enableFreeze(true);
|
||||
NetInfo.configure({
|
||||
reachabilityUrl: "https://notesnook.com",
|
||||
reachabilityUrl: "https://api.notesnook.com/health",
|
||||
reachabilityTest: (response) => {
|
||||
if (!response) return false;
|
||||
console.log("reachabilty test", response.status);
|
||||
return response?.status >= 200 && response?.status < 300;
|
||||
}
|
||||
});
|
||||
|
||||
15
apps/mobile/ios/ExportOptionsStaging.plist
Normal file
15
apps/mobile/ios/ExportOptionsStaging.plist
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>org.streetwriters.notesnook</key>
|
||||
<string>Notesnook Adhoc 2026</string>
|
||||
<key>org.streetwriters.notesnook.notewidget</key>
|
||||
<string>Notesnook Note Widget Adhoc 2026</string>
|
||||
<key>org.streetwriters.notesnook.share</key>
|
||||
<string>Notesnook Share Adhoc 2026</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -34,6 +34,8 @@
|
||||
65AA857E25E6DDEC00772A01 /* NotesWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AA857D25E6DDEC00772A01 /* NotesWidget.swift */; };
|
||||
65AA858025E6DDEE00772A01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65AA857F25E6DDEE00772A01 /* Assets.xcassets */; };
|
||||
65AA858425E6DDEE00772A01 /* NotesWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
65AD1F4A2F6289110033176F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65AD1F492F6289110033176F /* EvilIcons.ttf */; };
|
||||
65AD1F4B2F6289110033176F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65AD1F492F6289110033176F /* EvilIcons.ttf */; };
|
||||
65B5014425A672B200E2D264 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 65B5014325A672B200E2D264 /* ShareViewController.m */; };
|
||||
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65B5014525A672B200E2D264 /* MainInterface.storyboard */; };
|
||||
65B5014B25A672B200E2D264 /* Make Note.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65B5014025A672B200E2D264 /* Make Note.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
@@ -129,6 +131,7 @@
|
||||
65AA857D25E6DDEC00772A01 /* NotesWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesWidget.swift; sourceTree = "<group>"; };
|
||||
65AA857F25E6DDEE00772A01 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
65AA858125E6DDEE00772A01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
65AD1F492F6289110033176F /* EvilIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = EvilIcons.ttf; path = ../fonts/EvilIcons.ttf; sourceTree = SOURCE_ROOT; };
|
||||
65B5014025A672B200E2D264 /* Make Note.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Make Note.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
65B5014325A672B200E2D264 /* ShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = "<group>"; };
|
||||
65B5014625A672B200E2D264 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
@@ -141,6 +144,7 @@
|
||||
65E03409257B9FF100793428 /* Notesnook-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Notesnook-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
65E0340A257B9FF100793428 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
|
||||
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotesWidgetExtensionDebug.entitlements; sourceTree = "<group>"; };
|
||||
684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "ios-build.active.xcconfig"; path = "build-configs/ios-build.active.xcconfig"; sourceTree = SOURCE_ROOT; };
|
||||
77124A1C1FB538ED63FDEF82 /* Pods-Notesnook.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook.release.xcconfig"; path = "Target Support Files/Pods-Notesnook/Pods-Notesnook.release.xcconfig"; sourceTree = "<group>"; };
|
||||
7D3F6A50D104CE391243515F /* Pods-Notesnook-Add to Notes.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-Add to Notes.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-Add to Notes/Pods-Notesnook-Add to Notes.release.xcconfig"; sourceTree = "<group>"; };
|
||||
88211800FA8BF60638327ADE /* Pods-Make Note.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.debug.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -291,6 +295,7 @@
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
65AD1F492F6289110033176F /* EvilIcons.ttf */,
|
||||
653F4E042EA3887C009A42A2 /* Inter-Bold.ttf */,
|
||||
653F4E052EA3887C009A42A2 /* Inter-Italic.ttf */,
|
||||
653F4E062EA3887C009A42A2 /* Inter-Regular.ttf */,
|
||||
@@ -308,6 +313,7 @@
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
DA84645424CFBF19A44D64B3 /* Pods */,
|
||||
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */,
|
||||
684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@@ -536,6 +542,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
659201D82EA78265006B16DB /* Inter-Bold.ttf in Resources */,
|
||||
65AD1F4B2F6289110033176F /* EvilIcons.ttf in Resources */,
|
||||
659201D92EA78265006B16DB /* Inter-Italic.ttf in Resources */,
|
||||
659201DA2EA78265006B16DB /* Inter-Regular.ttf in Resources */,
|
||||
659201DB2EA78265006B16DB /* Inter-SemiBold.ttf in Resources */,
|
||||
@@ -581,6 +588,7 @@
|
||||
659201DE2EA7826D006B16DB /* Inter-Regular.ttf in Resources */,
|
||||
659201DF2EA7826D006B16DB /* Inter-SemiBold.ttf in Resources */,
|
||||
65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
|
||||
65AD1F4A2F6289110033176F /* EvilIcons.ttf in Resources */,
|
||||
65C149872A61151B005C40F1 /* extension.bundle in Resources */,
|
||||
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
|
||||
);
|
||||
@@ -1029,7 +1037,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2174;
|
||||
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1104,13 +1112,13 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.15;
|
||||
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_MAIN_BUNDLE_ID)";
|
||||
PRODUCT_NAME = Notesnook;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
|
||||
@@ -1135,7 +1143,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2174;
|
||||
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1210,17 +1218,17 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.15;
|
||||
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_MAIN_BUNDLE_ID)";
|
||||
PRODUCT_NAME = Notesnook;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook 2026";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "$(IOS_MAIN_PROFILE)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1367,7 +1375,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2174;
|
||||
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1379,10 +1387,10 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.15;
|
||||
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_WIDGET_BUNDLE_ID)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -1410,7 +1418,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2174;
|
||||
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1423,12 +1431,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.15;
|
||||
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_WIDGET_BUNDLE_ID)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Note Widget 2026";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "$(IOS_WIDGET_PROFILE)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
@@ -1453,7 +1461,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2174;
|
||||
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1534,10 +1542,10 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.3.15;
|
||||
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_SHARE_BUNDLE_ID)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
|
||||
@@ -1565,7 +1573,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2174;
|
||||
CURRENT_PROJECT_VERSION = "$(IOS_CURRENT_PROJECT_VERSION)";
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1647,12 +1655,12 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.3.15;
|
||||
MARKETING_VERSION = "$(IOS_MARKETING_VERSION)";
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(IOS_SHARE_BUNDLE_ID)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share 2026";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "$(IOS_SHARE_PROFILE)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -1662,6 +1670,7 @@
|
||||
};
|
||||
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
@@ -1737,6 +1746,7 @@
|
||||
};
|
||||
83CBBA211A601CBA00E9B192 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 684AA0A1B1BE6091A02D42FF /* ios-build.active.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Notesnook</string>
|
||||
<string>$(APP_DISPLAY_NAME)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -97,6 +97,7 @@
|
||||
<string>Inter-Regular.ttf</string>
|
||||
<string>Inter-Bold.ttf</string>
|
||||
<string>MaterialCommunityIcons.ttf</string>
|
||||
<string>EvilIcons.ttf</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user