Compare commits

...

48 Commits

Author SHA1 Message Date
Ammar Ahmed
5e68dd129e Update android.publish.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-27 01:03:48 +05:00
Ammar Ahmed
2350d0e41d mobile: minor fix 2024-02-27 01:02:39 +05:00
Ammar Ahmed
fbd5c8e3cd mobile: fix github release 2024-02-27 01:01:07 +05:00
Abdullah Atta
ee5c4077fe docs: improve skiff pages import guide 2024-02-13 01:07:14 +05:00
Abdullah Atta
47bbfc618e docs: add guide for migrating from skiff pages 2024-02-12 10:49:40 +05:00
Abdullah Atta
b377329a8b core: fix attachment mime type on read 2024-02-02 16:02:20 +05:00
Abdullah Atta
ae8bcf3865 ci: fix you may only define one of branches and branches-ignore for a single event
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-01-31 12:46:32 +05:00
Abdullah Atta
2f67611829 ci: add workflows for v3 beta publishing 2024-01-31 12:41:08 +05:00
Ammar Ahmed
058c612f4f ci: update default build tools version android
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-01-30 11:41:20 +05:00
Ammar Ahmed
be8df90ccb iOS Publish Workflow (#4194)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-01-30 11:32:48 +05:00
Abdullah Atta
aa730b5b93 web: fix title only txt exports 2024-01-30 00:29:46 +05:00
Abdullah Atta
34bbcf6387 web: add feature highlights for v2.6.15 2024-01-29 23:33:15 +05:00
Abdullah Atta
33c3c78e2f desktop: get rid of configuration.win.signingHashAlgorithms should be an array 2024-01-29 23:27:45 +05:00
Ammar Ahmed
bf144dcda4 mobile: add ExportOptions.plist 2024-01-29 23:23:48 +05:00
Ammar Ahmed
284ebdb86f ci: @notesnook/ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-01-29 23:23:01 +05:00
Abdullah Atta
9dac4cf7f4 desktop: set signing has to sha256 2024-01-29 23:22:29 +05:00
Abdullah Atta
fb973a5514 ci: run build job no matter what 2024-01-29 23:22:29 +05:00
Abdullah Atta
f9226510f9 desktop: use full path for FilesCatalog 2024-01-29 23:22:29 +05:00
Abdullah Atta
5c0a964699 ci: add inputs to disable build for specific platforms 2024-01-29 23:22:29 +05:00
Abdullah Atta
41ffaab3dc ci: install azurecodesigning module 2024-01-29 23:22:29 +05:00
Abdullah Atta
764c721f1d desktop: add windows code signing 2024-01-29 23:22:29 +05:00
Ammar Ahmed
3d267f91ef mobile: bump version code 2024-01-29 19:55:41 +05:00
Ammar Ahmed
f10f28a7f8 mobile: release v2.6.15 2024-01-29 19:32:14 +05:00
Ammar Ahmed
8d04f89a18 mobile: remove progress bar 2024-01-29 19:06:01 +05:00
Ammar Ahmed
9d1374dcd4 editor: fix progress not showing in image on mobile 2024-01-29 19:05:49 +05:00
Abdullah Atta
094a65e514 editor: hide progress on image if 0 2024-01-29 18:26:44 +05:00
Ammar Ahmed
372da979e1 mobile: support ignoring edits 2024-01-29 18:26:44 +05:00
Abdullah Atta
d3371326f2 editor: add progress indicator to image 2024-01-29 18:26:44 +05:00
Abdullah Atta
57452bd0e6 core: allow setting custom date edited 2024-01-29 18:26:44 +05:00
Abdullah Atta
993b8460bf web: do not update dateEdited if ignoring edits 2024-01-29 18:26:44 +05:00
Abdullah Atta
081201aa42 editor: add support for ignoring edits 2024-01-29 18:26:44 +05:00
Abdullah Atta
270e6778d1 editor: optimize image loading when fast scrolling 2024-01-29 16:23:59 +05:00
Abdullah Atta
2074320583 editor: revoke blob urls on image unmount 2024-01-29 16:09:05 +05:00
Abdullah Atta
cd90030616 web: fix note title in toolbar not changing 2024-01-29 15:45:13 +05:00
Abdullah Atta
807bd683b7 web: do not show success prompt on unlock cancel 2024-01-29 15:36:01 +05:00
Abdullah Atta
35929026a4 web: monograph.notesnook.com > monogr.ph 2024-01-29 15:22:22 +05:00
Ammar Ahmed
41eb94ee47 mobile: fix exporting locked notes 2024-01-29 11:02:32 +05:00
Ammar Ahmed
ba51e110ad mobile: fix opening webclips 2024-01-29 11:02:32 +05:00
Ammar Ahmed
ecad06d249 mobile: disable rtl mode 2024-01-29 11:02:32 +05:00
Abdullah Atta
19a151f637 editor: fix image query matching all images in editor 2024-01-28 17:17:45 +05:00
Ammar Ahmed
7c4a6f1e17 global: update deps 2024-01-27 23:06:24 +05:00
Ammar Ahmed
bc88e953d1 mobile: make sure tools render in toolbar 2024-01-27 23:05:30 +05:00
Ammar Ahmed
7d89ec676a mobile: fix attachments not loading when added from share ext 2024-01-27 22:50:41 +05:00
Ammar Ahmed
c8f8bcca93 mobile: organize bulk exports 2024-01-27 21:23:29 +05:00
Abdullah Atta
9fa904e849 web: fix exporting of single note 2024-01-27 21:23:29 +05:00
Abdullah Atta
05cc947cb4 web: fix pdf exports 2024-01-27 21:23:29 +05:00
alihamuh
a20a683b17 web: code refactoring 2024-01-27 21:23:29 +05:00
alihamuh
5fc43c931e web: added directory paths for exports 2024-01-27 21:23:29 +05:00
75 changed files with 1067 additions and 432 deletions

View File

@@ -1,4 +1,4 @@
name: Publish @notesnook/mobile
name: Publish @notesnook/android
on: workflow_dispatch
@@ -28,31 +28,6 @@ jobs:
- name: Make Gradlew Executable
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Build unsigned app bundle
run: yarn release:android:bundle
- name: Sign app bundle for Playstore release
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
BUILD_TOOLS_VERSION: "33.0.0"
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Build apks for Github release
run: yarn release:android
@@ -65,6 +40,7 @@ jobs:
alias: ${{ secrets.PUBLIC_ALIAS }}
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Rename apk files

View File

@@ -18,6 +18,21 @@ on:
required: true
default: true
description: "Publish on GitHub releases?"
build-windows:
type: boolean
required: true
default: true
description: "Build for Windows?"
build-linux:
type: boolean
required: true
default: true
description: "Build for Linux?"
build-mac:
type: boolean
required: true
default: true
description: "Build for macOS?"
jobs:
build:
@@ -71,6 +86,7 @@ jobs:
build-macos:
name: Build for macOS
needs: build
if: inputs.build-mac
runs-on: macos-12
steps:
@@ -163,6 +179,7 @@ jobs:
build-linux:
name: Build for Linux
needs: build
if: inputs.build-linux
runs-on: ubuntu-22.04
steps:
@@ -221,12 +238,22 @@ jobs:
build-windows:
name: Build for Windows
needs: build
if: inputs.build-windows
runs-on: windows-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Install AzureCodeSigning
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
shell: pwsh
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -247,6 +274,9 @@ jobs:
- name: Publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: |
if ($${{ inputs.publish-github }} -eq $true) {
yarn electron-builder --win --publish always

67
.github/workflows/ios.publish.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Publish @notesnook/ios
on: workflow_dispatch
jobs:
build:
runs-on: macos-13
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Build packages
run: npx nx run @notesnook/mobile:build
- name: Install Pods
run: npm run prepare:ios
- name: Build iOS App
uses: yukiarrr/ios-build-action@v1.11.2
with:
bundle-identifier: org.streetwriters.notesnook
scheme: Notesnook
configuration: "Release"
export-options: apps/mobile/native/ios/ExportOptions.plist
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/native/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_APP }}
${{ secrets.APPLE_MOBILE_PROVISION_SHARE }}
${{ secrets.APPLE_MOBILE_PROVISION_WIDGET }}
- name: 'Upload app to TestFlight'
uses: apple-actions/upload-testflight-build@v1
with:
app-path: Notesnook.ipa
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
api-key-id: ${{ secrets.API_KEY_ID }}
api-private-key: ${{ secrets.API_KEY }}
- name: Upload to Github
uses: actions/upload-artifact@v2
with:
name: Notesnook.ipa
path: Notesnook.ipa

View File

@@ -1,10 +1,11 @@
name: Publish @notesnook/web Beta
on:
on:
workflow_dispatch:
push:
branches:
- master
- '!v3-beta'
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
@@ -22,6 +23,7 @@ on:
- synchronize
branches:
- master
- '!v3-beta'
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"

View File

@@ -0,0 +1,40 @@
name: Publish @notesnook/web v3 Beta
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- 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: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate beta build
run: npm run build:beta:web
- name: Publish to Cloudflare Pages
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
id: pages
with:
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
cloudflare-project-name: notesnook-v3-app
directory: ./apps/web/build
github-token: ${{ secrets.GITHUB_TOKEN }}
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}

3
.gitignore vendored
View File

@@ -9,4 +9,5 @@ nx-cloud.env
.env.local
.nyc_output
site
node_modules.backup
node_modules.backup
.nx

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.6.14",
"version": "2.6.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.6.14",
"version": "2.6.15",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.14",
"version": "2.6.15",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -135,6 +135,10 @@
]
}
],
"signingHashAlgorithms": [
"sha256"
],
"sign": "./sign.js",
"icon": "assets/icons/app.ico"
},
"portable": {

60
apps/desktop/sign.js Normal file
View File

@@ -0,0 +1,60 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const { writeFileSync, rmSync, readFileSync } = require("fs");
const { execSync } = require("child_process");
const { relative, join } = require("path");
module.exports = async function (configuration) {
const Endpoint = "https://weu.codesigning.azure.net";
const CodeSigningAccountName = "Notesnook";
const CertificateProfileName = "Notesnook";
const FileDigest = configuration.hash.toUpperCase();
const TimestampRfc3161 = "http://timestamp.acs.microsoft.com";
const TimestampDigest = configuration.hash.toUpperCase();
const Description = "The Notesnook app";
const DescriptionUrl = "https://notesnook.com/";
const FilesCatalog = createCatalog(configuration.path);
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
console.debug("Signing", configuration.path, "using command", command);
psexec(command);
console.debug("Signed", configuration.path);
deleteCatalog();
};
function createCatalog(path) {
writeFileSync("_catalog", relative(__dirname, path));
return join(__dirname, "_catalog");
}
function deleteCatalog() {
rmSync("_catalog");
}
function psexec(cmd) {
return execSync(cmd, {
env: process.env,
stdio: "inherit",
shell: "pwsh"
});
}

View File

@@ -17,13 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "@azure/core-asynciterator-polyfill";
import SettingsService from "./services/settings";
import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
import { I18nManager, View } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
@@ -32,11 +31,16 @@ import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
import { TipManager } from "./services/tip-manager";
import { useThemeStore } from "./stores/use-theme-store";
import { useUserStore } from "./stores/use-user-store";
import { themeTrpcClient } from "./screens/settings/theme-selector";
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(false);
SettingsService.checkOrientation();
const App = () => {

View File

@@ -34,7 +34,8 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir } from "./io";
import { createCacheDir, exists } from "./io";
import { IOS_APPGROUPID } from "../../utils/constants";
export const FileDownloadStatus = {
Success: 1,
@@ -182,7 +183,8 @@ export default async function downloadAttachment(
cache: false,
throwError: false,
groupId: undefined,
base64: false
base64: false,
text: false
}
) {
await createCacheDir();
@@ -214,13 +216,15 @@ export default async function downloadAttachment(
attachment.metadata.hash
);
if (
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
)
if (!(await exists(attachment.metadata.hash))) {
return;
}
if (options.base64) {
return await db.attachments.read(attachment.metadata.hash, "base64");
if (options.base64 || options.text) {
return await db.attachments.read(
attachment.metadata.hash,
options.base64 ? "base64" : "text"
);
}
let filename = getFileNameWithExtension(
@@ -239,7 +243,8 @@ export default async function downloadAttachment(
mime: attachment.metadata.type,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize
chunkSize: attachment.chunkSize,
appGroupId: IOS_APPGROUPID
};
let fileUri = await Sodium.decryptFile(

View File

@@ -48,7 +48,7 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { formatBytes } from "@notesnook/common";
const Actions = ({ attachment, setAttachments, fwdRef }) => {
const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
const { colors } = useThemeColors();
const contextId = attachment.metadata.hash;
const [filename, setFilename] = useState(attachment.metadata.filename);
@@ -117,7 +117,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
});
}
setAttachments([...db.attachments.all]);
setAttachments();
setLoading({
name: null
});
@@ -140,7 +140,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
filename: value
});
setFilename(value);
setAttachments([...db.attachments.all]);
setAttachments();
}
},
positiveText: "Rename"
@@ -152,8 +152,8 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
name: "Delete",
onPress: async () => {
await db.attachments.remove(attachment.metadata.hash, false);
setAttachments([...db.attachments.all]);
eSendEvent(eCloseSheet, contextId);
setAttachments();
close();
},
icon: "delete-outline"
}
@@ -362,8 +362,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
Actions.present = (attachment, set, context) => {
presentSheet({
context: context,
component: (ref) => (
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
component: (ref, close) => (
<Actions
fwdRef={ref}
setAttachments={set}
close={close}
attachment={attachment}
/>
)
});
};

View File

@@ -68,7 +68,7 @@ export const AttachmentDialog = ({ note }) => {
!attachmentSearchValue.current ||
attachmentSearchValue.current === ""
) {
setAttachments([...attachments]);
setAttachments(filterAttachments(currentFilter));
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
@@ -77,13 +77,16 @@ export const AttachmentDialog = ({ note }) => {
attachmentSearchValue.current
);
if (results.length === 0) return;
setAttachments(results);
setAttachments(filterAttachments(currentFilter, results));
}, 300);
};
const renderItem = ({ item }) => (
<AttachmentItem
setAttachments={setAttachments}
setAttachments={() => {
setAttachments(filterAttachments(currentFilter));
}}
attachment={item}
context="attachments-list"
/>
@@ -133,11 +136,12 @@ export const AttachmentDialog = ({ note }) => {
}
];
const filterAttachments = (type) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
isDocument;
const filterAttachments = (type, _attachments) => {
const attachments =
_attachments || note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
switch (type) {
case "all":
return attachments;

View File

@@ -101,6 +101,7 @@ const SheetProvider = ({ context = "global" }) => {
setVisible(false);
setData(null);
}}
keyboardHandlerDisabled={data?.keyboardHandlerDisabled}
bottomPadding={!data.noBottomPadding}
enableGesturesInScrollView={
typeof data.enableGesturesInScrollView === "undefined"

View File

@@ -43,6 +43,7 @@ import Paragraph from "../../ui/typography/paragraph";
import { eSendEvent } from "../../../services/event-manager";
import { eCloseSheet } from "../../../utils/events";
import { requestInAppReview } from "../../../services/app-review";
import { Dialog } from "../../dialog";
const ExportNotesSheet = ({ notes, update }) => {
const { colors } = useThemeColors();
@@ -157,6 +158,8 @@ const ExportNotesSheet = ({ notes, update }) => {
</>
) : null}
<Dialog context="export-notes" />
<View style={styles.buttonContainer}>
{!exporting && !complete ? (
actions.map((item) => (
@@ -346,7 +349,8 @@ ExportNotesSheet.present = (notes, allNotes) => {
notes={allNotes ? db.notes.all : notes}
update={update}
/>
)
),
keyboardHandlerDisabled: true
});
};

View File

@@ -37,7 +37,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { requestInAppReview } from "../../../services/app-review";
const PublishNoteSheet = ({ note: item, update }) => {
const PublishNoteSheet = ({ note: item }) => {
const { colors } = useThemeColors();
const actionSheetRef = useRef();
@@ -48,8 +48,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
const [note, setNote] = useState(item);
const [publishing, setPublishing] = useState(false);
const publishUrl =
note &&
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
note && `https://monogr.ph/${db?.monographs.monograph(note?.id)}`;
const isPublished = note && db?.monographs.isPublished(note?.id);
const pwdInput = useRef();
const passwordValue = useRef();

View File

@@ -37,11 +37,11 @@ const SheetWrapper = ({
onOpen,
closeOnTouchBackdrop = true,
onHasReachedTop,
keyboardMode,
overlay,
overlayOpacity = 0.3,
enableGesturesInScrollView = false,
bottomPadding = true
bottomPadding = true,
keyboardHandlerDisabled
}) => {
const localRef = useRef(null);
const { colors } = useThemeColors("sheet");
@@ -121,8 +121,9 @@ const SheetWrapper = ({
initialOffsetFromBottom={1}
onPositionChanged={onHasReachedTop}
closeOnTouchBackdrop={closeOnTouchBackdrop}
keyboardMode={keyboardMode}
keyboardHandlerEnabled={sheetKeyboardHandler}
keyboardHandlerEnabled={
keyboardHandlerDisabled ? false : sheetKeyboardHandler
}
closeOnPressBack={closeOnTouchBackdrop}
indicatorColor={colors.secondary.background}
onOpen={_onOpen}

View File

@@ -550,15 +550,6 @@ export const useActions = ({ close = () => null, item }) => {
}
async function exportNote() {
if (item.locked) {
ToastEvent.show({
heading: "Note is locked",
type: "error",
message: "Locked notes cannot be exported",
context: "local"
});
return;
}
ExportNotesSheet.present([item]);
}

View File

@@ -481,7 +481,7 @@ export const useAppEvents = () => {
subscribeToIAPListeners();
} catch (e) {
DatabaseLogger.error(error);
DatabaseLogger.error(e);
ToastEvent.error(e, "An error occurred", "global");
}
@@ -623,15 +623,20 @@ export const useAppEvents = () => {
}, [loading, onUserUpdated]);
const initializeDatabase = useCallback(async () => {
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
await db.init();
try {
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
await db.init();
}
if (IsDatabaseMigrationRequired()) return;
initialize();
setLoading(false);
Walkthrough.init();
} catch (e) {
DatabaseLogger.error(e);
ToastEvent.error(e, "Error initializing database", "global");
}
if (IsDatabaseMigrationRequired()) return;
initialize();
setLoading(false);
Walkthrough.init();
}, [IsDatabaseMigrationRequired]);
useEffect(() => {

View File

@@ -31,7 +31,8 @@
"url": "^0.11.0",
"validator": "^13.5.2",
"zustand": "^3.6.0",
"katex": "0.16.2"
"katex": "0.16.2",
"@readme/data-urls": "3.0.0"
},
"sideEffects": false
}

View File

@@ -45,6 +45,7 @@ import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { editorController } from "./tiptap/utils";
import { useThemeColors } from "@notesnook/theme";
const style: ViewStyle = {
height: "100%",
@@ -176,6 +177,7 @@ export default Editor;
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
const readonly = useEditorStore((state) => state.readonly);
const keyboard = useKeyboard();
const { colors } = useThemeColors();
const onPress = async () => {
if (editor.note.current) {
@@ -191,7 +193,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
name="pencil-lock"
type="grayBg"
onPress={onPress}
color="accent"
color={colors.primary.accent}
customStyle={{
position: "absolute",
bottom: 60,

View File

@@ -28,5 +28,5 @@ const EditorMobileSourceUrl =
* The url should be something like this: http://192.168.100.126:3000/index.html
*/
export const EDITOR_URI = __DEV__
? "http://192.168.8.107:3000/index.html"
? EditorMobileSourceUrl
: EditorMobileSourceUrl;

View File

@@ -97,6 +97,7 @@ export type SavePayload = {
type?: Content["type"];
sessionId?: string | null;
sessionHistoryId?: number;
ignoreEdit: boolean;
};
export type AppState = {

View File

@@ -128,6 +128,8 @@ const showActionsheet = async (editor: useEditorType) => {
}
};
type ContentMessage = { html: string; ignoreEdit: boolean };
export const useEditorEvents = (
editor: useEditorType,
{ readonly: editorPropReadonly, noHeader, noToolbar }: Partial<EditorProps>
@@ -331,14 +333,16 @@ export const useEditorEvents = (
if (editorMessage.type === EventTypes.content) {
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value as string,
content: (editorMessage.value as ContentMessage).html,
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
forSessionId: editorMessage.sessionId
});
} else if (editorMessage.type === EventTypes.title) {
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string,
forSessionId: editorMessage.sessionId
forSessionId: editorMessage.sessionId,
ignoreEdit: false
});
}
@@ -427,7 +431,8 @@ export const useEditorEvents = (
attachment.type
);
downloadAttachment(attachment.hash, true, {
base64: true,
base64: attachment.type === "image",
text: attachment.type === "web-clip",
silent: true,
groupId: editor.note.current?.id,
cache: true

View File

@@ -162,7 +162,8 @@ export const useEditor = (
data,
type,
sessionId: currentSessionId,
sessionHistoryId: currentSessionHistoryId
sessionHistoryId: currentSessionHistoryId,
ignoreEdit
}: SavePayload) => {
if (
readonly ||
@@ -194,6 +195,12 @@ export const useEditor = (
};
noteData.title = title;
if (ignoreEdit) {
console.log("Ignoring edits...");
noteData.dateEdited = note?.dateEdited;
}
if (data) {
noteData.content = {
data: data,
@@ -454,12 +461,14 @@ export const useEditor = (
title,
content,
type,
forSessionId
forSessionId,
ignoreEdit
}: {
title?: string;
content?: string;
type: string;
forSessionId: string;
ignoreEdit: boolean;
}) => {
if (lock.current || lockedSessionId.current === forSessionId) return;
lastContentChangeTime.current = Date.now();
@@ -490,7 +499,8 @@ export const useEditor = (
type: "tiptap",
sessionId: forSessionId,
id: noteId,
sessionHistoryId: sessionHistoryId.current
sessionHistoryId: sessionHistoryId.current,
ignoreEdit
};
withTimer(
noteId || "newnote",
@@ -508,7 +518,7 @@ export const useEditor = (
}
saveNote(params);
},
150
ignoreEdit ? 0 : 150
);
},
[withTimer, onChange, saveNote]

View File

@@ -115,7 +115,7 @@ export const EditorWrapper = ({ width }) => {
style={{ height: 1, padding: 0, width: 1, position: "absolute" }}
blurOnSubmit={false}
/>
<ProgressBar />
{/* <ProgressBar /> */}
<Editor key="editor" withController={true} />
</KeyboardAvoidingViewIOS>
)}

View File

@@ -18,17 +18,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { decode, EntityLevel } from "entities";
import { zipSync } from "fflate";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
import * as ScopedStorage from "react-native-scoped-storage";
import RNFetchBlob from "react-native-blob-util";
import { zip } from "react-native-zip-archive";
import { DatabaseLogger, db } from "../common/database/index";
import Storage from "../common/database/storage";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { sanitizeFilename } from "@notesnook/common";
import { presentDialog } from "../components/dialog/functions";
import { useSettingStore } from "../stores/use-setting-store";
import BiometicService from "./biometrics";
import { ToastEvent } from "./event-manager";
const MIMETypes = {
txt: "text/plain",
@@ -99,8 +101,8 @@ async function save(path, data, fileName, extension) {
return uri || path;
}
async function makeHtml(note) {
let html = await db.notes.note(note.id).export("html");
async function makeHtml(note, content) {
let html = await db.notes.note(note.id).export("html", content);
html = decode(html, {
level: EntityLevel.HTML
});
@@ -111,23 +113,25 @@ async function makeHtml(note) {
*
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
*/
async function exportAs(type, note, bulk) {
async function exportAs(type, note, bulk, content) {
let data;
switch (type) {
case "html":
{
data = await makeHtml(note);
data = await makeHtml(note, content);
}
break;
case "md":
data = await db.notes.note(note.id).export("md");
data = await db.notes.note(note.id).export("md", content);
break;
case "md-frontmatter":
data = await db.notes.note(note.id).export("md-frontmatter");
data = await db.notes
.note(note.id)
.export("md-frontmatter", content?.data);
break;
case "pdf":
{
let html = await makeHtml(note);
let html = await makeHtml(note, content);
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
});
@@ -153,21 +157,84 @@ async function exportAs(type, note, bulk) {
}
break;
case "txt":
data = await convertNoteToText(note);
{
data = await db.notes?.note(note.id).export("txt", content);
}
break;
}
return data;
}
async function unlockVault() {
let biometry = await BiometicService.isBiometryAvailable();
let fingerprint = await BiometicService.hasInternetCredentials("nn_vault");
if (biometry && fingerprint) {
let credentials = await BiometicService.getCredentials(
"Unlock vault",
"Unlock vault to export locked notes"
);
if (credentials) {
return db.vault.unlock(credentials.password);
}
}
useSettingStore.getState().setSheetKeyboardHandler(false);
return new Promise((resolve) => {
setImmediate(() => {
presentDialog({
context: "export-notes",
input: true,
secureTextEntry: true,
positiveText: "Unlock",
title: "Unlock vault",
paragraph: "Some exported notes are locked, Unlock to export them",
inputPlaceholder: "Enter password",
positivePress: async (value) => {
const unlocked = await db.vault.unlock(value);
if (!unlocked) {
ToastEvent.show({
heading: "Invalid password",
message: "Please enter a valid password",
type: "error",
context: "local"
});
return false;
}
resolve(unlocked);
useSettingStore.getState().setSheetKeyboardHandler(true);
return true;
},
onClose: () => {
resolve(false);
useSettingStore.getState().setSheetKeyboardHandler(true);
}
});
});
});
}
/**
*
* @param {"txt" | "pdf" | "md" | "html" | "md-frontmatter"} type
*/
async function exportNote(note, type) {
let content;
if (note.locked) {
try {
let unlocked = await unlockVault();
if (!unlocked) return null;
const unlockedNote = await db.vault.open(note.id);
content = unlockedNote.content;
} catch (e) {
DatabaseLogger.error(e);
}
}
let path = await getPath(FolderNames[type]);
if (!path) return;
let result = await exportAs(type, note);
let result = await exportAs(type, note, false, content);
if (!result) return null;
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
@@ -195,11 +262,6 @@ function copyFileAsync(source, dest) {
});
}
function zipsync(results) {
let data = zipSync(results);
return Buffer.from(data.buffer).toString("base64");
}
function getUniqueFileName(fileName, results) {
const chunks = fileName.split(".");
const ext = chunks.pop();
@@ -221,19 +283,108 @@ async function bulkExport(notes, type, callback) {
let path = await getPath(FolderNames[type]);
if (!path) return;
const exportCacheFolder =
RNFetchBlob.fs.dirs.CacheDir + `/export_${Date.now()}`;
await RNFetchBlob.fs.mkdir(exportCacheFolder).catch((e) => console.log(e));
const mkdir = async (dir) => {
const folder = `${exportCacheFolder}/${dir}`;
if (!(await RNFetchBlob.fs.exists(folder))) {
await RNFetchBlob.fs.mkdir(folder);
}
};
const writeFile = async (path, result) => {
const cacheFilePath = exportCacheFolder + path;
await RNFetchBlob.fs.writeFile(
cacheFilePath,
result,
type === "pdf" ? "base64" : "utf8"
);
};
const results = {};
for (var i = 0; i < notes.length; i++) {
try {
await sleep(1);
let note = notes[i];
if (note.locked) continue;
let result = await exportAs(type, note);
let content;
if (note.locked) {
try {
let unlocked = !db.vault.unlocked ? await unlockVault() : true;
if (!unlocked) {
continue;
}
const unlockedNote = await db.vault.open(note.id);
content = unlockedNote.content;
} catch (e) {
DatabaseLogger.error(e);
continue;
}
}
let result = await exportAs(type, note, true, content);
let fileName = sanitizeFilename(note.title, {
replacement: "_"
});
if (result) {
results[getUniqueFileName(fileName + `.${type}`, results)] =
Buffer.from(result, type === "pdf" ? "base64" : "utf-8");
const notebooks = [
...(db.relations
?.to({ id: note.id, type: "note" }, "notebook")
.map((notebook) => ({
title: notebook.title
})) || []),
...(note.notebooks || []).map((ref) => {
const notebook = db.notebooks?.notebook(ref.id);
const topics = notebook?.topics.all || [];
return {
title: notebook?.title,
topics: ref.topics
.map((topicId) => topics.find((topic) => topic.id === topicId))
.filter(Boolean)
};
})
];
for (const notebook of notebooks) {
results[notebook.title] = results[notebook.title] || {};
await mkdir(notebook.title);
if (notebook.topics && notebook.topics.length) {
for (const topic of notebook.topics) {
results[notebook.title][topic.title] =
results[notebook.title][topic.title] || {};
await mkdir(`${notebook.title}/${topic.title}`);
const exportedNoteName = getUniqueFileName(
fileName + `.${type}`,
results[notebook.title][topic.title]
);
results[notebook.title][topic.title][exportedNoteName] = true;
writeFile(
`/${notebook.title}/${topic.title}/${exportedNoteName}`,
result
);
}
} else {
const exportedNoteName = getUniqueFileName(
fileName + `.${type}`,
results[notebook.title]
);
results[notebook.title][exportedNoteName] = true;
writeFile(`/${notebook.title}/${exportedNoteName}`, result);
}
}
if (!notebooks.length) {
const exportedNoteName = getUniqueFileName(
fileName + `.${type}`,
results
);
results[exportedNoteName] = true;
writeFile(`/${exportedNoteName}`, result);
}
}
callback(`${i + 1}/${notes.length}`);
} catch (e) {
@@ -244,26 +395,24 @@ async function bulkExport(notes, type, callback) {
try {
callback("zipping");
await sleep(1);
const zipped = zipsync(results);
const zipOutputPath =
Platform.OS === "ios"
? path + `/${fileName}`
: RNFetchBlob.fs.dirs.CacheDir + `/${fileName}`;
await zip(exportCacheFolder, zipOutputPath);
callback("saving to disk");
await sleep(1);
if (Platform.OS === "ios") {
RNFetchBlob.fs.writeFile(path + `/${fileName}`, zipped, "base64");
} else {
const templFile = RNFetchBlob.fs.dirs.CacheDir + `/${fileName}`;
await RNFetchBlob.fs.writeFile(templFile, zipped, "base64");
if (Platform.OS === "android") {
const file = await ScopedStorage.createFile(
path,
fileName,
"application/zip"
);
path = file.uri;
await copyFileAsync("file://" + templFile, path);
await RNFetchBlob.fs.unlink(templFile);
await copyFileAsync("file://" + zipOutputPath, path);
await RNFetchBlob.fs.unlink(zipOutputPath);
callback();
}
RNFetchBlob.fs.unlink(exportCacheFolder);
} catch (e) {
DatabaseLogger.error(e);
}

View File

@@ -61,7 +61,10 @@ export const getGithubVersion = async () => {
if (!res?.ok) return null;
const data = (await res?.json()) as GithubRelease[];
const versions = data?.filter((tag) => tag.tag_name.endsWith("android"));
const versions = data?.filter(
(tag) =>
tag.tag_name.endsWith("android") && !tag.tag_name.endsWith("beta-android")
);
const latestVersion = versions[0];
const version = latestVersion.tag_name.replace("-android", "");
return {

View File

@@ -21,7 +21,7 @@ import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { db } from "../common/database";
import { DatabaseLogger, db } from "../common/database";
import { IOS_APPGROUPID } from "./constants";
const santizeUri = (uri) => {
@@ -57,6 +57,7 @@ export async function attachFile(uri, hash, type, filename, options) {
return true;
} catch (e) {
if (Platform.OS === "ios") RNFetchBlob.fs.unlink(uri).catch(console.log);
DatabaseLogger.error(e, "Attach file error");
console.log("attach file error: ", e);
return false;
}
@@ -107,26 +108,31 @@ async function createNotes(bundle) {
uri: uri,
type: "cache"
});
await attachFile(uri, hash, file.type, file.name, {
const attached = await attachFile(uri, hash, file.type, file.name, {
type: "cache",
id: id,
appGroupId: IOS_APPGROUPID
});
let content = ``;
if (isImage(file.type)) {
content = `<img data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" />`;
} else {
content = `<p><span data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" data-size="${file.size}" /></p>`;
if (attached) {
if (isImage(file.type)) {
content = `<img data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" />`;
} else {
content = `<p><span data-hash="${hash}" data-mime="${file.type}" data-filename="${file.name}" data-size="${file.size}" /></p>`;
}
const rawContent = await db.content.raw(
db.notes.note(id).data?.contentId
);
await db.notes.add({
id: id,
content: {
type: "tiptap",
data: rawContent?.data ? rawContent?.data + content : content
},
sessionId: sessionId
});
}
const rawContent = await db.content.raw(db.notes.note(id).data?.contentId);
await db.notes.add({
id: id,
content: {
type: "tiptap",
data: rawContent?.data ? rawContent?.data + content : content
},
sessionId: sessionId
});
}
}

View File

@@ -111,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2074
versionCode 2076
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -5,7 +5,7 @@
<!-- Customize your theme here. -->
<item name="android:windowDisablePreview">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:datePickerDialogTheme">@style/DialogDatePicker.Theme</item>
</style>
@@ -20,6 +20,7 @@
<style name="BootTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/bootsplash_background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/bootsplash_logo_dark</item>
<item name="android:navigationBarColor">@color/bootsplash_background</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
@@ -31,6 +32,7 @@
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="DialogDatePicker.Theme" parent="Theme.AppCompat.Light.Dialog">

View File

@@ -5,7 +5,7 @@
<!-- Customize your theme here. -->
<item name="android:windowDisablePreview">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:datePickerDialogTheme">@style/DialogDatePicker.Theme</item>
</style>
@@ -20,6 +20,7 @@
<style name="BootTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/bootsplash_background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/bootsplash_logo</item>
<item name="android:navigationBarColor">@color/bootsplash_background</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
@@ -31,6 +32,7 @@
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:editTextBackground">@drawable/edit_text</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="DialogDatePicker.Theme" parent="Theme.AppCompat.Light.Dialog">

View File

@@ -1,3 +1,10 @@
- Bug fixes and performance improvements
- Support for exporting locked notes
- Maintain directory structure in exports based on Notebooks/Topics
- Match color of navigation bar in sheets with app theme
- Improved image loading and show progress when loading images
- Support for setting trash interval to daily
- Fix android app UI issues with RTL languages
- Fix pasting links in editor
- Other Bug fixes and performance improvements
Thank you for using Notesnook!

View 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 App Distribution 2024</string>
<key>org.streetwriters.notesnook.notewidget</key>
<string>Notesnook Widget Distribution 2024</string>
<key>org.streetwriters.notesnook.share</key>
<string>Notesnook Share Distribution 2024</string>
</dict>
</dict>
</plist>

View File

@@ -14,13 +14,13 @@
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* NotesnookTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* NotesnookTests.m */; };
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */; };
6510627627042895009661C3 /* OpenSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */; };
6510E6D72877215700DACAA9 /* build.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6510E6D62877215700DACAA9 /* build.bundle */; };
6515C42F2580AA3000E83E39 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6515C42E2580AA2F00E83E39 /* StoreKit.framework */; };
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */; };
6517B7C22B6838EB0079FF37 /* OpenSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */; };
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; };
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */; };
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
65AA857B25E6DDEC00772A01 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857A25E6DDEC00772A01 /* SwiftUI.framework */; };
@@ -110,12 +110,12 @@
4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Make Note.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6510E6D62877215700DACAA9 /* build.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = build.bundle; path = "../../../../packages/editor-mobile/build.bundle"; sourceTree = "<group>"; };
6515C42E2580AA2F00E83E39 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-SemiBold.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; };
6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Bold.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = BootSplash.storyboard; path = Notesnook/BootSplash.storyboard; sourceTree = "<group>"; };
6529D2B0257B4A2900B49BC3 /* NotesnookDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NotesnookDebug.entitlements; path = Notesnook/NotesnookDebug.entitlements; sourceTree = "<group>"; };
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-SemiBold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-SemiBold.ttf"; sourceTree = "<group>"; };
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
6593E4A2281C345400492C50 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Notesnook/AppDelegate.mm; sourceTree = "<group>"; };
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Bold.ttf"; path = "../android/app/src/main/assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
659BE46625E11A5100E05671 /* notesnook-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "notesnook-text.png"; path = "Notesnook/Images.xcassets/notesnook-text.png"; sourceTree = "<group>"; };
65A7F34F255689AD00699170 /* Notesnook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Notesnook.entitlements; path = Notesnook/Notesnook.entitlements; sourceTree = "<group>"; };
65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotesWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -293,13 +293,13 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */,
6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */,
6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */,
65C400DD2A80B68D00AA3DF5 /* MaterialCommunityIcons.ttf */,
65C149862A61151B005C40F1 /* extension.bundle */,
6510E6D62877215700DACAA9 /* build.bundle */,
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */,
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */,
659670B12A2754FD00C5D2AF /* OpenSans-Bold.ttf */,
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */,
65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */,
13B07FAE1A68108700A75B9A /* Notesnook */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
@@ -542,12 +542,12 @@
buildActionMask = 2147483647;
files = (
65C400DE2A80B68D00AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */,
659670B22A2754FD00C5D2AF /* OpenSans-Bold.ttf in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */,
6517B7C22B6838EB0079FF37 /* OpenSans-SemiBold.ttf in Resources */,
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */,
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
6510627627042895009661C3 /* OpenSans-SemiBold.ttf in Resources */,
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */,
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -997,7 +997,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2066;
CURRENT_PROJECT_VERSION = 2067;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1102,7 +1102,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2066;
CURRENT_PROJECT_VERSION = 2067;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1335,7 +1335,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2066;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1378,7 +1378,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2066;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1421,7 +1421,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2066;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1526,7 +1526,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2066;
CURRENT_PROJECT_VERSION = 2067;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "2.6.14",
"version": "2.6.15",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "2.6.14",
"version": "2.6.15",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -62,7 +62,8 @@
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -81,7 +82,7 @@
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"cross-env": "^7.0.3",
@@ -117,7 +118,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -248,6 +249,7 @@
"dependencies": {
"@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96",
"@readme/data-urls": "3.0.0",
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
@@ -7095,6 +7097,14 @@
"nanoid": "^3.1.23"
}
},
"node_modules/@readme/data-urls": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@readme/data-urls/-/data-urls-3.0.0.tgz",
"integrity": "sha512-b0L7VWqbLZqOSSAFUrxS5ZwUfec35WDsAwwCH481vYnhk0dWO3nvmNVNCbP8CY4cXqwL1W4uCAnhDz+CUmXM3g==",
"engines": {
"node": ">=18"
}
},
"node_modules/@sayem314/react-native-keep-awake": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@sayem314/react-native-keep-awake/-/react-native-keep-awake-1.2.0.tgz",
@@ -26831,11 +26841,12 @@
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/crypto": "file:../crypto",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"async-mutex": "^0.3.2",
@@ -26882,7 +26893,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -26971,6 +26982,7 @@
"requires": {
"@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96",
"@readme/data-urls": "3.0.0",
"@streetwriters/showdown": "^3.0.1-alpha.2",
"absolutify": "^0.1.0",
"buffer": "^6.0.3",
@@ -27640,6 +27652,11 @@
"nanoid": "^3.1.23"
}
},
"@readme/data-urls": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@readme/data-urls/-/data-urls-3.0.0.tgz",
"integrity": "sha512-b0L7VWqbLZqOSSAFUrxS5ZwUfec35WDsAwwCH481vYnhk0dWO3nvmNVNCbP8CY4cXqwL1W4uCAnhDz+CUmXM3g=="
},
"@sayem314/react-native-keep-awake": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@sayem314/react-native-keep-awake/-/react-native-keep-awake-1.2.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.6.15",
"version": "2.6.16",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -10,6 +10,7 @@
"scripts": {
"postinstall": "patch-package && node ./scripts/optimize-fonts.mjs",
"run-android": "cd native && react-native run-android --active-arch-only",
"build": "echo 'No build script defined'",
"run-ios": "cd native && react-native run-ios",
"start": "cd native && react-native start",
"repack": "cd native && react-native webpack-start",

View File

@@ -1377,7 +1377,7 @@
},
"../web": {
"name": "@notesnook/web",
"version": "2.6.14",
"version": "2.6.15",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "2.6.14",
"version": "2.6.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "2.6.14",
"version": "2.6.15",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",
@@ -215,7 +215,8 @@
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -234,7 +235,7 @@
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"cross-env": "^7.0.3",
@@ -279,7 +280,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -438,7 +439,7 @@
},
"../desktop": {
"name": "@notesnook/desktop",
"version": "2.6.14",
"version": "2.6.15",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.6.14",
"version": "2.6.15",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -21,6 +21,11 @@ import { TaskManager } from "./task-manager";
import { ExportStream } from "../utils/streams/export-stream";
import { createZipStream } from "../utils/streams/zip-stream";
import { createWriteStream } from "../utils/stream-saver";
import { showToast } from "../utils/toast";
import Vault from "./vault";
import { db } from "./db";
import Note from "@notesnook/core/dist/models/note";
import { sanitizeFilename } from "@notesnook/common";
export async function exportToPDF(
title: string,
@@ -74,12 +79,91 @@ export async function exportNotes(
title: "Exporting notes",
subtitle: "Please wait while your notes are exported.",
action: async (report) => {
await new ExportStream(noteIds, format, undefined, (c, text) =>
report({ total: noteIds.length, current: c, text })
)
.pipeThrough(createZipStream())
.pipeTo(await createWriteStream("notes.zip"));
return true;
try {
let progress = 0;
await createNoteStream(noteIds)
.pipeThrough(
new TransformStream<Note, Note>({
transform(note, controller) {
controller.enqueue(note);
report({
total: noteIds.length,
current: progress++,
text: `Exporting "${note?.title || "Unknown note"}"`
});
}
})
)
.pipeThrough(new ExportStream(format))
.pipeThrough(createZipStream())
.pipeTo(await createWriteStream("notes.zip"));
return true;
} catch (e) {
if (e instanceof Error) showToast("error", e.message);
}
return false;
}
});
}
function createNoteStream(noteIds: string[]) {
let i = 0;
return new ReadableStream<Note>({
start() {},
async pull(controller) {
const noteId = noteIds[i++];
if (!noteId) controller.close();
else controller.enqueue(db.notes?.note(noteId));
},
async cancel(reason) {
throw new Error(reason);
}
});
}
const FORMAT_TO_EXT = {
pdf: "pdf",
md: "md",
txt: "txt",
html: "html",
"md-frontmatter": "md"
} as const;
export async function exportNote(
note: Note,
format: keyof typeof FORMAT_TO_EXT,
disableTemplate = false
) {
if (!db.vault?.unlocked && note.data.locked && !(await Vault.unlockVault())) {
showToast("error", `Skipping note "${note.title}" as it is locked.`);
return false;
}
const rawContent = note.data.contentId
? await db.content?.raw(note.data.contentId)
: undefined;
const content =
rawContent &&
!rawContent.deleted &&
(typeof rawContent.data === "object"
? await db.vault?.decryptContent(rawContent)
: rawContent);
const exported = await note
.export(format === "pdf" ? "html" : format, content, disableTemplate)
.catch((e: Error) => {
console.error(note.data, e);
showToast("error", `Failed to export note "${note.title}": ${e.message}`);
return false as const;
});
if (typeof exported === "boolean" && !exported) return false;
const filename = sanitizeFilename(note.title, { replacement: "-" });
const ext = FORMAT_TO_EXT[format];
return {
filename: [filename, ext].join("."),
content: exported
};
}

View File

@@ -72,11 +72,12 @@ type DocumentPreview = {
function onEditorChange(
noteId: string | undefined,
sessionId: number,
content: string
content: string,
ignoreEdit: boolean
) {
if (!content) return;
editorstore.get().saveSessionContent(noteId, sessionId, {
editorstore.get().saveSessionContent(noteId, sessionId, ignoreEdit, {
type: "tiptap",
data: content
});
@@ -535,7 +536,7 @@ function PreviewModeNotice(props: PreviewModeNoticeProps) {
async (cancelled: boolean) => {
const { id, sessionId } = editorstore.get().session;
if (!cancelled) {
await editorstore.saveSessionContent(id, sessionId, content);
await editorstore.saveSessionContent(id, sessionId, false, content);
}
onDiscard();
},

View File

@@ -61,7 +61,8 @@ import { useStore as useThemeStore } from "../../stores/theme-store";
type OnChangeHandler = (
id: string | undefined,
sessionId: number,
content: string
content: string,
ignoreEdit: boolean
) => void;
type TipTapProps = {
editorContainer: HTMLElement;
@@ -92,6 +93,7 @@ function save(
editor: Editor,
content: Fragment,
preventSave: boolean,
ignoreEdit: boolean,
onChange?: OnChangeHandler
) {
configureEditor({
@@ -105,7 +107,7 @@ function save(
if (preventSave) return;
const html = getHTMLFromFragment(content, editor.schema);
onChange?.(noteId, sessionId, html);
onChange?.(noteId, sessionId, html, ignoreEdit);
}
const deferredSave = debounceWithId(save, SAVE_INTERVAL);
@@ -211,7 +213,8 @@ function TipTap(props: TipTapProps) {
onUpdate: ({ editor, transaction }) => {
onContentChange?.();
const preventSave = transaction?.getMeta("preventSave") as boolean;
const preventSave = transaction.getMeta("preventSave") as boolean;
const ignoreEdit = transaction.getMeta("ignoreEdit") as boolean;
const { id, sessionId } = editorstore.get().session;
const content = editor.state.doc.content;
deferredSave(
@@ -221,6 +224,7 @@ function TipTap(props: TipTapProps) {
editor as Editor,
content,
preventSave || !editor.isEditable,
ignoreEdit,
onChange
);
},

View File

@@ -94,8 +94,7 @@ function Toolbar() {
}, [isTitleVisible]);
useEffect(() => {
if (onScrollTitleRef.current)
onScrollTitleRef.current.defaultValue = title;
if (onScrollTitleRef.current) onScrollTitleRef.current.value = title;
}, [title]);
const tools = useMemo(

View File

@@ -70,7 +70,7 @@ import { hashNavigate, navigate } from "../../navigation";
import { showPublishView } from "../publish-view";
import IconTag from "../icon-tag";
import { COLORS } from "../../common/constants";
import { exportNotes } from "../../common/export";
import { exportNote, exportNotes, exportToPDF } from "../../common/export";
import { Multiselect } from "../../common/multi-select";
import { store as selectionStore } from "../../stores/selection-store";
import {
@@ -86,7 +86,7 @@ import {
ReferencesWithDateEdited
} from "../list-container/types";
import { SchemeColors } from "@notesnook/theme";
import Vault from "../../common/vault";
import FileSaver from "file-saver";
type NoteProps = {
tags: Item[];
@@ -369,10 +369,9 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
onClick: async () => {
const { unlock, lock } = store.get();
if (!note.locked) {
await lock(note.id);
showToast("success", "Note locked successfully!");
} else {
await unlock(note.id);
if (await lock(note.id))
showToast("success", "Note locked successfully!");
} else if (await unlock(note.id)) {
showToast("success", "Note unlocked successfully!");
}
},
@@ -422,7 +421,12 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
isDisabled: !isSynced,
icon: Print.path,
onClick: async () => {
await exportNotes("pdf", [note.id]);
const item = db.notes?.note(note);
if (!item) return;
const result = await exportNote(item, "pdf");
if (!result) return;
await exportToPDF(note.title, result.content);
}
},
{
@@ -456,7 +460,24 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
// ? "Multiple notes cannot be exported as PDF."
// : false,
isPro: format.type !== "txt",
onClick: () => exportNotes(format.type, ids)
onClick: async () => {
if (ids.length === 1) {
const item = db.notes?.note(note);
if (!item) return;
const result = await exportNote(item, format.type);
if (!result) return;
if (format.type === "pdf")
return exportToPDF(note.title, result.content);
return FileSaver.saveAs(
new Blob([new TextEncoder().encode(result.content)]),
result.filename
);
}
await exportNotes(format.type, ids);
}
}))
},
multiSelect: true,
@@ -706,17 +727,11 @@ async function copyNote(noteId: string, format: "md" | "txt") {
try {
const note = db.notes?.note(noteId);
if (!note) throw new Error("No note with this id exists.");
if (note?.data.locked && !(await Vault.unlockVault()))
throw new Error("Please unlock this note to copy it.");
const rawContent = await db.content?.raw(note.data.contentId);
const content = note?.data.locked
? await db.vault?.decryptContent(rawContent)
: rawContent;
const result = await exportNote(note, format, true);
if (!result) return;
const text = await note.export(format, content, false);
if (!text) throw new Error(`Could not convert note to ${format}.`);
await navigator.clipboard.writeText(text);
await navigator.clipboard.writeText(result.content);
showToast("success", "Copied!");
} catch (e) {
if (e instanceof Error)

View File

@@ -125,7 +125,7 @@ function PublishView(props) {
variant="body"
as="a"
target="_blank"
href={`https://monograph.notesnook.com/${publishId}`}
href={`https://monogr.ph/${publishId}`}
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
@@ -134,13 +134,13 @@ function PublishView(props) {
mr: 2
}}
>
{`https://monograph.notesnook.com/${publishId}`}
{`https://monogr.ph/${publishId}`}
</Text>
<Button
variant="anchor"
className="copyPublishLink"
onClick={() => {
writeText(`https://monograph.notesnook.com/${publishId}`);
writeText(`https://monogr.ph/${publishId}`);
}}
>
<Copy size={20} color="accent" />

View File

@@ -89,7 +89,23 @@ const features: Record<FeatureKeys, Feature> = {
)
}
]
: [],
: [
{
title: "Daily trash cleanup interval",
subtitle:
"You can now set the trash cleanup interval to Daily in addition to Weekly, Monthly & Yearly."
},
{
title: "Organized bulk exports",
subtitle:
"Bulk exports are now automatically sorted/organized into folders based on your Notebook/Topic organization."
},
{
title: "New domain for Monographs",
subtitle:
"Monographs will now be published to monogr.ph instead of monograph.notesnook.com. Don't worry, all your published notes will automatically redirect."
}
],
cta: {
title: "Got it",
icon: Checkmark,

View File

@@ -306,8 +306,17 @@ class EditorStore extends BaseStore {
return this.saveSession(noteId, { [name]: value });
};
saveSessionContent = (noteId, sessionId, content) => {
return this.saveSession(noteId, { sessionId, content });
/**
*
* @param {*} noteId
* @param {*} sessionId
* @param {boolean} ignoreEdit if this is set to true, we save the content but do not update the dateEdited. Useful for metadata only changes in content.
* @param {*} content
* @returns
*/
saveSessionContent = (noteId, sessionId, ignoreEdit, content) => {
const dateEdited = ignoreEdit ? this.get().session.dateEdited : undefined;
return this.saveSession(noteId, { sessionId, content, dateEdited });
};
setTag = (tag) => {

View File

@@ -123,10 +123,11 @@ class NoteStore extends BaseStore {
};
lock = async (id) => {
await Vault.lockNote(id);
if (!(await Vault.lockNote(id))) return false;
this.refreshItem(id);
if (editorStore.get().session.id === id)
await editorStore.openSession(id, true);
return true;
};
readonly = async (id) => {

View File

@@ -17,97 +17,78 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { sanitizeFilename } from "@notesnook/common";
import Note from "@notesnook/core/dist/models/note";
import { db } from "../../common/db";
import { exportToPDF } from "../../common/export";
import Vault from "../../common/vault";
import { showToast } from "../toast";
import { exportNote } from "../../common/export";
import { makeUniqueFilename } from "./utils";
import { ZipFile } from "./zip-stream";
const FORMAT_TO_EXT = {
pdf: "pdf",
md: "md",
txt: "txt",
html: "html",
"md-frontmatter": "md"
} as const;
export class ExportStream extends ReadableStream<ZipFile> {
export class ExportStream extends TransformStream<Note, ZipFile> {
constructor(
noteIds: string[],
format: "pdf" | "md" | "txt" | "html" | "md-frontmatter",
signal?: AbortSignal,
onProgress?: (current: number, text: string) => void
signal?: AbortSignal
) {
const textEncoder = new TextEncoder();
let index = 0;
const counters: Record<string, number> = {};
let vaultUnlocked = false;
super({
async start() {
if (noteIds.length === 1 && db.notes?.note(noteIds[0])?.data.locked) {
vaultUnlocked = await Vault.unlockVault();
if (!vaultUnlocked) return false;
} else if (noteIds.length > 1 && (await db.vault?.exists())) {
vaultUnlocked = await Vault.unlockVault();
if (!vaultUnlocked)
showToast(
"error",
"Failed to unlock vault. Locked notes will be skipped."
);
}
},
async pull(controller) {
if (signal?.aborted) {
controller.close();
return;
}
async transform(note, controller) {
try {
if (signal?.aborted) {
controller.terminate();
return;
}
const note = db.notes?.note(noteIds[index++]);
if (!note) return;
if (!vaultUnlocked && note.data.locked) return;
if (!note || format === "pdf") return;
onProgress && onProgress(index, `Exporting "${note.title}"...`);
const result = await exportNote(note, format);
if (!result) return;
const rawContent = await db.content?.raw(note.data.contentId);
const content = note.data.locked
? await db.vault?.decryptContent(rawContent)
: rawContent;
const { filename, content } = result;
const exported = await note
.export(format === "pdf" ? "html" : format, content)
.catch((e: Error) => {
console.error(note.data, e);
showToast(
"error",
`Failed to export note "${note.title}": ${e.message}`
);
const notebooks = [
...(
db.relations?.to({ id: note.id, type: "note" }, "notebook") || []
).map((n) => ({ title: n.title, topics: [] })),
...(note.notebooks || []).map(
(ref: { id: string; topics: string[] }) => {
const notebook = db.notebooks?.notebook(ref.id);
const topics: any[] = notebook?.topics.all || [];
return {
title: notebook?.title,
topics: ref.topics
.map((topicId: string) =>
topics.find((topic) => topic.id === topicId)
)
.filter(Boolean)
};
}
)
];
const filePaths: Array<string> =
notebooks.length > 0
? notebooks
.map((notebook) => {
if (notebook.topics.length > 0)
return notebook.topics.map((topic: { title: string }) =>
[notebook.title, topic.title, filename].join("/")
);
return [notebook.title, filename].join("/");
})
.flat()
: [filename];
filePaths.forEach((filePath) => {
controller.enqueue({
path: makeUniqueFilename(filePath, counters),
data: content,
mtime: new Date(note.data.dateEdited),
ctime: new Date(note.data.dateCreated)
});
});
if (typeof exported !== "string") {
showToast("error", `Failed to export note "${note.title}"`);
return;
}
if (format === "pdf") {
await exportToPDF(note.title, exported);
controller.close();
return;
}
const filename = sanitizeFilename(note.title, { replacement: "-" });
const ext = FORMAT_TO_EXT[format];
controller.enqueue({
path: makeUniqueFilename([filename, ext].join("."), counters),
data: textEncoder.encode(exported),
mtime: new Date(note.data.dateEdited),
ctime: new Date(note.data.dateCreated)
});
if (index === noteIds.length) {
controller.close();
} catch (e) {
controller.error(e);
}
}
});

View File

@@ -17,16 +17,19 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { path } from "@notesnook-importer/core/dist/src/utils/path";
export function makeUniqueFilename(
filePath: string,
counters: Record<string, number>
) {
filePath = filePath.toLowerCase();
counters[filePath] = (counters[filePath] || 0) + 1;
if (counters[filePath] === 1) return filePath;
const matchablePath = filePath.toLowerCase();
const count = (counters[matchablePath] = (counters[matchablePath] || 0) + 1);
if (count === 1) return filePath;
const parts = filePath.split(".");
return `${parts.slice(0, -1).join(".")}-${counters[filePath]}.${
parts[parts.length - 1]
}`;
const ext = path.extname(filePath);
const basename = ext
? `${path.basename(filePath, ext)}-${count}${ext}`
: `${path.basename(filePath)}-${count}`;
return path.join(path.dirname(filePath), basename);
}

View File

@@ -18,13 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Deflate, Inflate } from "./fflate-shim";
import { Uint8ArrayReader, ZipWriter, configure } from "@zip.js/zip.js";
import {
Uint8ArrayReader,
TextReader,
ZipWriter,
configure
} from "@zip.js/zip.js";
configure({ Deflate, Inflate });
export type ZipFile = {
path: string;
data: Uint8Array;
data: string | Uint8Array;
mtime?: Date;
ctime?: Date;
};
@@ -43,10 +48,16 @@ export function createZipStream(signal?: AbortSignal) {
if (written.has(chunk.path)) return;
await writer
.add(chunk.path, new Uint8ArrayReader(chunk.data), {
creationDate: chunk.ctime,
lastModDate: chunk.mtime
})
.add(
chunk.path,
typeof chunk.data === "string"
? new TextReader(chunk.data)
: new Uint8ArrayReader(chunk.data),
{
creationDate: chunk.ctime,
lastModDate: chunk.mtime
}
)
.catch(async (e) => {
await ts.writable.abort(e);
await ts.readable.cancel(e);

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,40 @@
---
title: Skiff Pages
---
# How do I import notes from Skiff Pages?
The following steps will help you quickly import your notes from Skiff Pages into Notesnook.
## Exporting your Skiff Pages
1. Open the [Skiff Pages](https://app.skiff.com) app
2. Open Settings > Export or just go directly to [https://app.skiff.com/dashboard/?settingTab=export](https://app.skiff.com/dashboard/?settingTab=export)
![](/static/skiff-importer/1.png)
3. Click on the Export button next to `Pages and Files` — this might take a few minutes depending on how many pages you have.
4. Once the export is complete, save the `Skiff.zip` file at your preferred location.
![](/static/skiff-importer/2.png)
## Importing Skiff.zip file into Notesnook
Once you have the `Skiff.zip` file containing your Skiff pages, its time to import them into Notesnook.
1. Go to [https://importer.notesnook.com/](https://importer.notesnook.com/) on your PC/Laptop.
2. From the list of note apps to import from, select "Markdown".
![](/static/skiff-importer/3.png)
3. Drop your Skiff.zip file, or click anywhere inside the box to browse and select your Skiff.zip file. Then click "Start processing".
![](/static/skiff-importer/4.png)
4. Once the Importer finishes processing your files, download the .zip file.
![](/static/import-ready.png)
5. After you have downloaded the `.zip` file, [go to the Notesnook Web App](https://app.notesnook.com/) > Settings > Notesnook Importer. Select the .zip you downloaded earlier and click "Start import" button.
![](/static/import-zip-app.png)
6. Once importing completes you should see all your notes in Notesnook. If you face any issues during importing, feel free to [report them on GitHub](https://github.com/streetwriters/notesnook-importer).
## Supported formats
- [x] Images
- [x] Code blocks
- [ ] Math blocks (Skiff Pages doesn't mark them properly so there's no way to detect them.)
- [x] Tables
- [x] Rich text (bold, italic, headings, lists etc.)
- [x] Task lists

View File

@@ -38,6 +38,7 @@ navigation:
- path: importing-notes/import-notes-from-simplenote.md
- path: importing-notes/import-notes-from-standardnotes.md
- path: importing-notes/import-notes-from-zoho-notebook.md
- path: importing-notes/import-notes-from-skiff-pages.md
- path: importing-notes/import-notes-from-obsidian.md
- path: importing-notes/import-notes-from-html-files.md
- path: importing-notes/import-notes-from-markdown-files.md

View File

@@ -1,3 +1,10 @@
- Bug fixes and performance improvements
- Support for exporting locked notes
- Maintain directory structure in exports based on Notebooks/Topics
- Match color of navigation bar in sheets with app theme
- Improved image loading and show progress when loading images
- Support for setting trash interval to daily
- Fix android app UI issues with RTL languages
- Fix pasting links in editor
- Other Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -0,0 +1,10 @@
- Support for exporting locked notes
- Maintain directory structure in exports based on Notebooks/Topics
- Match color of navigation bar in sheets with app theme
- Improved image loading and show progress when loading images
- Support for setting trash interval to daily
- Fix android app UI issues with RTL languages
- Fix pasting links in editor
- Other Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -31,7 +31,8 @@
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -50,7 +51,7 @@
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"cross-env": "^7.0.3",
@@ -147,11 +148,12 @@
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/crypto": "file:../crypto",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"async-mutex": "^0.3.2",

View File

@@ -75,7 +75,7 @@ function sanitize(input: string, replacement: string) {
.replace(windowsReservedRe, replacement)
.replace(windowsTrailingRe, replacement);
return sanitized.slice(0, 254).toLowerCase();
return sanitized.slice(0, 254);
}
export function sanitizeFilename(

View File

@@ -4189,8 +4189,7 @@
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"dev": true,
"requires": {}
"dev": true
}
}
},
@@ -4975,8 +4974,7 @@
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"requires": {}
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q=="
},
"xml-name-validator": {
"version": "4.0.0",

View File

@@ -61,6 +61,10 @@ export default class Vault {
});
}
get unlocked() {
return !!this._vaultPassword;
}
/**
* Creates a new vault
* @param {string} password The password

View File

@@ -296,7 +296,7 @@ export default class Attachments extends Collection {
if (!data) return;
return outputType === "base64"
? dataurl.fromObject({ type: "application/octet-stream", data })
? dataurl.fromObject({ type: attachment.metadata.type, data })
: data;
}

View File

@@ -83,6 +83,8 @@ export default class Content extends Collection {
}
async raw(id) {
if (!id) return;
const content = await this._collection.getItem(id);
if (!content) return;
return content;
@@ -118,6 +120,7 @@ export default class Content extends Collection {
async downloadMedia(groupId, contentItem, notify = true) {
if (!contentItem) return contentItem;
const content = getContentFromData(contentItem.type, contentItem.data);
if (!content) console.log(contentItem);
contentItem.data = await content.insertMedia(async (hashes) => {
const attachments = hashes.map((h) => this._db.attachments.attachment(h));
await this._db.fs.queueDownloads(

View File

@@ -129,7 +129,8 @@ export default class Notes extends Collection {
readonly: !!note.readonly,
dateCreated: note.dateCreated,
dateEdited: note.dateEdited || note.dateCreated || Date.now(),
dateEdited:
noteArg.dateEdited || note.dateEdited || note.dateCreated || Date.now(),
dateModified: note.dateModified
};

View File

@@ -57,7 +57,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",
@@ -21707,7 +21707,7 @@
"@tiptap/extension-font-family": "2.1.12",
"@tiptap/extension-history": "2.1.12",
"@tiptap/extension-horizontal-rule": "2.1.12",
"@tiptap/extension-link": "2.1.12",
"@tiptap/extension-link": "^2.1.16",
"@tiptap/extension-list-keymap": "2.1.12",
"@tiptap/extension-placeholder": "2.1.12",
"@tiptap/extension-subscript": "2.1.12",

View File

@@ -44,7 +44,7 @@ import Tags from "./tags";
import Title from "./title";
import { toBlobURL } from "@notesnook/editor/dist/utils/downloader";
globalThis.toBlobURL = toBlobURL;
globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL;
const Tiptap = ({ settings }: { settings: Settings }) => {
const [tick, setTick] = useState(0);
@@ -61,8 +61,11 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
});
const _editor = useTiptap(
{
onUpdate: ({ editor }) => {
global.editorController.contentChange(editor as Editor);
onUpdate: ({ editor, transaction }) => {
global.editorController.contentChange(
editor as Editor,
transaction.getMeta("ignoreEdit")
);
},
onOpenAttachmentPicker: (editor, type) => {
global.editorController.openFilePicker(type);
@@ -252,7 +255,7 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
}}
editor={_editor}
location="bottom"
tools={settings.tools}
tools={[...settings.tools]}
defaultFontFamily={settings.fontFamily}
defaultFontSize={settings.fontSize}
/>

View File

@@ -32,7 +32,6 @@ import {
} from "react";
import { EventTypes, isReactNative, post, randId, saveTheme } from "../utils";
import { injectCss, transform } from "../utils/css";
type Attachment = {
hash: string;
filename: string;
@@ -88,7 +87,7 @@ function scrollIntoView(editor: Editor) {
export type EditorController = {
selectionChange: (editor: Editor) => void;
titleChange: (title: string) => void;
contentChange: (editor: Editor) => void;
contentChange: (editor: Editor, ignoreEdit?: boolean) => void;
scroll: (event: React.UIEvent<HTMLDivElement, UIEvent>) => void;
title: string;
setTitle: React.Dispatch<React.SetStateAction<string>>;
@@ -139,17 +138,27 @@ export function useEditorController(update: () => void): EditorController {
}, [colors]);
const contentChange = useCallback(
(editor: Editor) => {
(editor: Editor, ignoreEdit) => {
const currentSessionId = globalThis.sessionId;
post(EventTypes.contentchange);
if (!editor) return;
if (typeof timers.current.change === "number") {
clearTimeout(timers.current?.change);
}
timers.current.change = setTimeout(() => {
htmlContentRef.current = editor.getHTML();
post(EventTypes.content, htmlContentRef.current, currentSessionId);
}, 300);
timers.current.change = setTimeout(
() => {
htmlContentRef.current = editor.getHTML();
post(
EventTypes.content,
{
html: htmlContentRef.current,
ignoreEdit: ignoreEdit
},
currentSessionId
);
},
ignoreEdit ? 0 : 300
);
countWords(5000);
},

View File

@@ -95,7 +95,8 @@
"@microsoft/signalr": "^7.0.10",
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"entities": "^4.3.1",
@@ -114,7 +115,7 @@
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"cross-env": "^7.0.3",
@@ -3122,6 +3123,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -3142,6 +3144,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -3274,6 +3277,7 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -4090,8 +4094,7 @@
"@emotion/use-insertion-effect-with-fallbacks": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
"requires": {}
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw=="
},
"@emotion/utils": {
"version": "1.2.1",
@@ -4344,11 +4347,12 @@
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
"@notesnook/crypto": "file:../crypto",
"@notesnook/logger": "file:../logger",
"@streetwriters/showdown": "^3.0.2-alpha",
"@readme/data-urls": "^3.0.0",
"@streetwriters/showdown": "^3.0.4-alpha",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.1",
"@types/prismjs": "^1.26.0",
"@types/showdown": "^2.0.0",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.6",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"async-mutex": "^0.3.2",
@@ -4565,104 +4569,87 @@
"@tiptap/core": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.12.tgz",
"integrity": "sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==",
"requires": {}
"integrity": "sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ=="
},
"@tiptap/extension-blockquote": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.1.12.tgz",
"integrity": "sha512-Qb3YRlCfugx9pw7VgLTb+jY37OY4aBJeZnqHzx4QThSm13edNYjasokbX0nTwL1Up4NPTcY19JUeHt6fVaVVGg==",
"requires": {}
"integrity": "sha512-Qb3YRlCfugx9pw7VgLTb+jY37OY4aBJeZnqHzx4QThSm13edNYjasokbX0nTwL1Up4NPTcY19JUeHt6fVaVVGg=="
},
"@tiptap/extension-bold": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.12.tgz",
"integrity": "sha512-AZGxIxcGU1/y6V2YEbKsq6BAibL8yQrbRm6EdcBnby41vj1WziewEKswhLGmZx5IKM2r2ldxld03KlfSIlKQZg==",
"requires": {}
"integrity": "sha512-AZGxIxcGU1/y6V2YEbKsq6BAibL8yQrbRm6EdcBnby41vj1WziewEKswhLGmZx5IKM2r2ldxld03KlfSIlKQZg=="
},
"@tiptap/extension-bullet-list": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz",
"integrity": "sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==",
"requires": {}
"integrity": "sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ=="
},
"@tiptap/extension-character-count": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.1.12.tgz",
"integrity": "sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ==",
"requires": {}
"integrity": "sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ=="
},
"@tiptap/extension-code": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.1.12.tgz",
"integrity": "sha512-CRiRq5OTC1lFgSx6IMrECqmtb93a0ZZKujEnaRhzWliPBjLIi66va05f/P1vnV6/tHaC3yfXys6dxB5A4J8jxw==",
"requires": {}
"integrity": "sha512-CRiRq5OTC1lFgSx6IMrECqmtb93a0ZZKujEnaRhzWliPBjLIi66va05f/P1vnV6/tHaC3yfXys6dxB5A4J8jxw=="
},
"@tiptap/extension-code-block": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.1.12.tgz",
"integrity": "sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==",
"requires": {}
"integrity": "sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA=="
},
"@tiptap/extension-color": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-2.1.12.tgz",
"integrity": "sha512-Myd6iSbPJvvclr+NRBEdE0k52QlQrXZnJljk4JKn0b25cl60ERA40FH9QLBjkpTed7SDbI3oX7LWIzTUoCj39w==",
"requires": {}
"integrity": "sha512-Myd6iSbPJvvclr+NRBEdE0k52QlQrXZnJljk4JKn0b25cl60ERA40FH9QLBjkpTed7SDbI3oX7LWIzTUoCj39w=="
},
"@tiptap/extension-document": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.12.tgz",
"integrity": "sha512-0QNfAkCcFlB9O8cUNSwTSIQMV9TmoEhfEaLz/GvbjwEq4skXK3bU+OQX7Ih07waCDVXIGAZ7YAZogbvrn/WbOw==",
"requires": {}
"integrity": "sha512-0QNfAkCcFlB9O8cUNSwTSIQMV9TmoEhfEaLz/GvbjwEq4skXK3bU+OQX7Ih07waCDVXIGAZ7YAZogbvrn/WbOw=="
},
"@tiptap/extension-dropcursor": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.12.tgz",
"integrity": "sha512-0tT/q8nL4NBCYPxr9T0Brck+RQbWuczm9nV0bnxgt0IiQXoRHutfPWdS7GA65PTuVRBS/3LOco30fbjFhkfz/A==",
"requires": {}
"integrity": "sha512-0tT/q8nL4NBCYPxr9T0Brck+RQbWuczm9nV0bnxgt0IiQXoRHutfPWdS7GA65PTuVRBS/3LOco30fbjFhkfz/A=="
},
"@tiptap/extension-font-family": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-font-family/-/extension-font-family-2.1.12.tgz",
"integrity": "sha512-1PAvmtilBD1OWfr1I+oKND1MLNGWMEx/Qa7xC2YDzonxhiBd56Xog6fDE/+2Bbud0vPEIrMHrg8QOjCDH413vw==",
"requires": {}
"integrity": "sha512-1PAvmtilBD1OWfr1I+oKND1MLNGWMEx/Qa7xC2YDzonxhiBd56Xog6fDE/+2Bbud0vPEIrMHrg8QOjCDH413vw=="
},
"@tiptap/extension-gapcursor": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.12.tgz",
"integrity": "sha512-zFYdZCqPgpwoB7whyuwpc8EYLYjUE5QYKb8vICvc+FraBUDM51ujYhFSgJC3rhs8EjI+8GcK8ShLbSMIn49YOQ==",
"requires": {}
"integrity": "sha512-zFYdZCqPgpwoB7whyuwpc8EYLYjUE5QYKb8vICvc+FraBUDM51ujYhFSgJC3rhs8EjI+8GcK8ShLbSMIn49YOQ=="
},
"@tiptap/extension-hard-break": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.1.12.tgz",
"integrity": "sha512-nqKcAYGEOafg9D+2cy1E4gHNGuL12LerVa0eS2SQOb+PT8vSel9OTKU1RyZldsWSQJ5rq/w4uIjmLnrSR2w6Yw==",
"requires": {}
"integrity": "sha512-nqKcAYGEOafg9D+2cy1E4gHNGuL12LerVa0eS2SQOb+PT8vSel9OTKU1RyZldsWSQJ5rq/w4uIjmLnrSR2w6Yw=="
},
"@tiptap/extension-heading": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.1.12.tgz",
"integrity": "sha512-MoANP3POAP68Ko9YXarfDKLM/kXtscgp6m+xRagPAghRNujVY88nK1qBMZ3JdvTVN6b/ATJhp8UdrZX96TLV2w==",
"requires": {}
"integrity": "sha512-MoANP3POAP68Ko9YXarfDKLM/kXtscgp6m+xRagPAghRNujVY88nK1qBMZ3JdvTVN6b/ATJhp8UdrZX96TLV2w=="
},
"@tiptap/extension-history": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.12.tgz",
"integrity": "sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg==",
"requires": {}
"integrity": "sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg=="
},
"@tiptap/extension-horizontal-rule": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.12.tgz",
"integrity": "sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A==",
"requires": {}
"integrity": "sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A=="
},
"@tiptap/extension-italic": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.12.tgz",
"integrity": "sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A==",
"requires": {}
"integrity": "sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A=="
},
"@tiptap/extension-link": {
"version": "2.1.16",
@@ -4675,110 +4662,92 @@
"@tiptap/extension-list-item": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz",
"integrity": "sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg==",
"requires": {}
"integrity": "sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg=="
},
"@tiptap/extension-list-keymap": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-2.1.12.tgz",
"integrity": "sha512-f19nGaqhIZhssM2k8nYR+zcoMc7UCLcW6YCNhTXSrybUsb6SMFVob9OL7+sy1x2n5Was5IqsvyAGakLjdTEwAw==",
"requires": {}
"integrity": "sha512-f19nGaqhIZhssM2k8nYR+zcoMc7UCLcW6YCNhTXSrybUsb6SMFVob9OL7+sy1x2n5Was5IqsvyAGakLjdTEwAw=="
},
"@tiptap/extension-ordered-list": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.12.tgz",
"integrity": "sha512-tF6VGl+D2avCgn9U/2YLJ8qVmV6sPE/iEzVAFZuOSe6L0Pj7SQw4K6AO640QBob/d8VrqqJFHCb6l10amJOnXA==",
"requires": {}
"integrity": "sha512-tF6VGl+D2avCgn9U/2YLJ8qVmV6sPE/iEzVAFZuOSe6L0Pj7SQw4K6AO640QBob/d8VrqqJFHCb6l10amJOnXA=="
},
"@tiptap/extension-paragraph": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.12.tgz",
"integrity": "sha512-hoH/uWPX+KKnNAZagudlsrr4Xu57nusGekkJWBcrb5MCDE91BS+DN2xifuhwXiTHxnwOMVFjluc0bPzQbkArsw==",
"requires": {}
"integrity": "sha512-hoH/uWPX+KKnNAZagudlsrr4Xu57nusGekkJWBcrb5MCDE91BS+DN2xifuhwXiTHxnwOMVFjluc0bPzQbkArsw=="
},
"@tiptap/extension-placeholder": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.1.12.tgz",
"integrity": "sha512-K52o7B1zkP4vaVy3z4ZwHn+tQy6KlXtedj1skLg+796ImwH2GYS5z6MFOTfKzBO2hLncUzLco/s0C5PLCD6SDw==",
"requires": {}
"integrity": "sha512-K52o7B1zkP4vaVy3z4ZwHn+tQy6KlXtedj1skLg+796ImwH2GYS5z6MFOTfKzBO2hLncUzLco/s0C5PLCD6SDw=="
},
"@tiptap/extension-strike": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.12.tgz",
"integrity": "sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g==",
"requires": {}
"integrity": "sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g=="
},
"@tiptap/extension-subscript": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.1.12.tgz",
"integrity": "sha512-tb1jysEvf4SIiXwEOgDTXiyrG39RVNHvn/zsGMg5wy5t9qUp9m1k7kKYTH084ktuKDAPQonCcpn3hwc+ngTFzg==",
"requires": {}
"integrity": "sha512-tb1jysEvf4SIiXwEOgDTXiyrG39RVNHvn/zsGMg5wy5t9qUp9m1k7kKYTH084ktuKDAPQonCcpn3hwc+ngTFzg=="
},
"@tiptap/extension-superscript": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.1.12.tgz",
"integrity": "sha512-ek6L+DNsrjiJieArlgTvQt1VfJ56d8V19WAPW/ciRhq88YRlTEY9nSO3QuUCSUO1nGmE5OWQpgrsiW/XZbONVw==",
"requires": {}
"integrity": "sha512-ek6L+DNsrjiJieArlgTvQt1VfJ56d8V19WAPW/ciRhq88YRlTEY9nSO3QuUCSUO1nGmE5OWQpgrsiW/XZbONVw=="
},
"@tiptap/extension-table": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.1.12.tgz",
"integrity": "sha512-q/DuKZ4j1ycRfuFdb9rBJ3MglGNxlM2BQ1csScX/BrVIsAQI5B8sdzy1BrIlepQ6DRu4DCzHcKMI8u4/edUSWA==",
"requires": {}
"integrity": "sha512-q/DuKZ4j1ycRfuFdb9rBJ3MglGNxlM2BQ1csScX/BrVIsAQI5B8sdzy1BrIlepQ6DRu4DCzHcKMI8u4/edUSWA=="
},
"@tiptap/extension-table-cell": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.1.12.tgz",
"integrity": "sha512-hextcfVTdwX8G7s8Q/V6LW2aUhGvPgu1dfV+kVVO42AFHxG+6PIkDOUuHphGajG3Nrs129bjMDWb8jphj38dUg==",
"requires": {}
"integrity": "sha512-hextcfVTdwX8G7s8Q/V6LW2aUhGvPgu1dfV+kVVO42AFHxG+6PIkDOUuHphGajG3Nrs129bjMDWb8jphj38dUg=="
},
"@tiptap/extension-table-header": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.1.12.tgz",
"integrity": "sha512-a4WZ5Z7gqQ/QlK8cK2d1ONYdma/J5+yH/0SNtQhkfELoS45GsLJh89OyKO0W0FnY6Mg0RoH1FsoBD+cqm0yazA==",
"requires": {}
"integrity": "sha512-a4WZ5Z7gqQ/QlK8cK2d1ONYdma/J5+yH/0SNtQhkfELoS45GsLJh89OyKO0W0FnY6Mg0RoH1FsoBD+cqm0yazA=="
},
"@tiptap/extension-table-row": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.1.12.tgz",
"integrity": "sha512-0kPr+zngQC1YQRcU6+Fl3CpIW/SdJhVJ5qOLpQleXrLPdjmZQd3Z1DXvOSDphYjXCowGPCxeUa++6bo7IoEMJw==",
"requires": {}
"integrity": "sha512-0kPr+zngQC1YQRcU6+Fl3CpIW/SdJhVJ5qOLpQleXrLPdjmZQd3Z1DXvOSDphYjXCowGPCxeUa++6bo7IoEMJw=="
},
"@tiptap/extension-task-item": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.1.12.tgz",
"integrity": "sha512-uqrDTO4JwukZUt40GQdvB6S+oDhdp4cKNPMi0sbteWziQugkSMLlkYvxU0Hfb/YeziaWWwFI7ssPu/hahyk6dQ==",
"requires": {}
"integrity": "sha512-uqrDTO4JwukZUt40GQdvB6S+oDhdp4cKNPMi0sbteWziQugkSMLlkYvxU0Hfb/YeziaWWwFI7ssPu/hahyk6dQ=="
},
"@tiptap/extension-task-list": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.1.12.tgz",
"integrity": "sha512-BUpYlEWK+Q3kw9KIiOqvhd0tUPhMcOf1+fJmCkluJok+okAxMbP1umAtCEQ3QkoCwLr+vpHJov7h3yi9+dwgeQ==",
"requires": {}
"integrity": "sha512-BUpYlEWK+Q3kw9KIiOqvhd0tUPhMcOf1+fJmCkluJok+okAxMbP1umAtCEQ3QkoCwLr+vpHJov7h3yi9+dwgeQ=="
},
"@tiptap/extension-text": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.12.tgz",
"integrity": "sha512-rCNUd505p/PXwU9Jgxo4ZJv4A3cIBAyAqlx/dtcY6cjztCQuXJhuQILPhjGhBTOLEEL4kW2wQtqzCmb7O8i2jg==",
"requires": {}
"integrity": "sha512-rCNUd505p/PXwU9Jgxo4ZJv4A3cIBAyAqlx/dtcY6cjztCQuXJhuQILPhjGhBTOLEEL4kW2wQtqzCmb7O8i2jg=="
},
"@tiptap/extension-text-align": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.1.12.tgz",
"integrity": "sha512-siMlwrkgVrAxxgmZn8GOc75J7UZi2CVrP9vDHkUPPyKm/fjssYekXwGCEk4Vswii1BbOh2gt+MDsRkeYRGyDlQ==",
"requires": {}
"integrity": "sha512-siMlwrkgVrAxxgmZn8GOc75J7UZi2CVrP9vDHkUPPyKm/fjssYekXwGCEk4Vswii1BbOh2gt+MDsRkeYRGyDlQ=="
},
"@tiptap/extension-text-style": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.1.12.tgz",
"integrity": "sha512-nfjWXX0JSRHLcscfiMESh+RN+Z7bG8nio/C9+8yQASM90VxU9f8oKgF8HnnSYsSrD4lLf44Q6XjmB7aMVUuikg==",
"requires": {}
"integrity": "sha512-nfjWXX0JSRHLcscfiMESh+RN+Z7bG8nio/C9+8yQASM90VxU9f8oKgF8HnnSYsSrD4lLf44Q6XjmB7aMVUuikg=="
},
"@tiptap/extension-underline": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.1.12.tgz",
"integrity": "sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g==",
"requires": {}
"integrity": "sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g=="
},
"@tiptap/pm": {
"version": "2.1.12",
@@ -5839,8 +5808,7 @@
}
},
"prosemirror-codemark": {
"version": "0.4.2",
"requires": {}
"version": "0.4.2"
},
"prosemirror-collab": {
"version": "1.3.0",
@@ -5988,26 +5956,26 @@
}
},
"re-resizable": {
"version": "6.9.9",
"requires": {}
"version": "6.9.9"
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"react-colorful": {
"version": "5.6.1",
"requires": {}
"version": "5.6.1"
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -6094,6 +6062,7 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -6320,8 +6289,7 @@
}
},
"use-sync-external-store": {
"version": "1.2.0",
"requires": {}
"version": "1.2.0"
},
"util-deprecate": {
"version": "1.0.2",

View File

@@ -44,6 +44,7 @@ declare module "@tiptap/core" {
attachment: Partial<Attachment>,
options: {
preventUpdate?: boolean;
ignoreEdit?: boolean;
query: (attachment: Attachment) => boolean;
}
) => ReturnType;
@@ -169,6 +170,7 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
});
}
tr.setMeta("preventUpdate", options.preventUpdate || false);
tr.setMeta("ignoreEdit", options.ignoreEdit || false);
tr.setMeta("addToHistory", false);
if (dispatch) dispatch(tr);
return true;

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ThemeUIStyleObject } from "@theme-ui/core";
import { Box, Flex, Image } from "@theme-ui/components";
import { Box, Flex, Image, Text } from "@theme-ui/components";
import { ImageAttributes } from "./image";
import { useEffect, useState } from "react";
import { SelectionBasedReactNodeViewProps } from "../react";
@@ -34,6 +34,7 @@ import { Resizer } from "../../components/resizer";
import {
corsify,
downloadImage,
revokeBloburl,
toBlobURL,
toDataURL
} from "../../utils/downloader";
@@ -48,7 +49,7 @@ export function ImageComponent(
props: SelectionBasedReactNodeViewProps<Partial<ImageAttributes>>
) {
const { editor, node, selected } = props;
const { src, alt, title, textDirection, hash, aspectRatio, mime } =
const { src, alt, title, textDirection, hash, aspectRatio, mime, progress } =
node.attrs;
const [bloburl, setBloburl] = useState<string | undefined>(
toBlobURL("", "image", mime, hash)
@@ -56,7 +57,8 @@ export function ImageComponent(
const isMobile = useIsMobile();
const { inView, ref: imageRef } = useObserver<HTMLImageElement>({
threshold: 0.2
threshold: 0.2,
once: true
});
const float = isMobile ? false : node.attrs.float;
@@ -86,6 +88,13 @@ export function ImageComponent(
})();
}, [inView]);
useEffect(() => {
return () => {
if (!hash) return;
revokeBloburl(hash);
};
}, []);
return (
<>
<Box
@@ -147,6 +156,24 @@ export function ImageComponent(
</Flex>
)}
</DesktopOnly>
{progress ? (
<Flex
sx={{
position: "absolute",
bottom: 2,
right: 2,
bg: "background",
borderRadius: 100,
p: 1,
px: 2,
border: "1px solid var(--border)",
zIndex: 2
}}
>
<Icon path={Icons.loading} rotate size={14} sx={{ mr: 1 }} />
<Text variant="body">{progress}%</Text>
</Flex>
) : null}
{!isReadonly && selected && (
<Icon
className="drag-handle"
@@ -287,13 +314,14 @@ export function ImageComponent(
...fixedDimensions,
aspectRatio: naturalAspectRatio
},
{ query: makeImageQuery(src, hash) }
{ query: makeImageQuery(src, hash), ignoreEdit: true }
)
);
} else if (height !== fixedDimensions.height) {
await editor.threadsafe((editor) =>
editor.commands.updateAttachment(fixedDimensions, {
query: makeImageQuery(src, hash)
query: makeImageQuery(src, hash),
ignoreEdit: true
})
);
}
@@ -308,7 +336,9 @@ export function ImageComponent(
function makeImageQuery(src?: string, hash?: string) {
return (a: Attachment) =>
(a.type === "image" && a.src === src) || a.hash === hash;
a.type === "image" &&
((!!a.src && !!src && a.src === src) ||
(!!a.hash && !!hash && a.hash === hash));
}
function canParse(src: string) {
if (!src) return false;

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useState, useRef, useEffect, useCallback } from "react";
import { debounce } from "../utils/debounce";
type ObserverType = {
threshold: number;
@@ -32,7 +33,7 @@ export function useObserver<T extends Element = Element>({
}: ObserverType) {
const [inView, setInView] = useState<boolean>();
const ref = useRef<T>(null);
const iObserverRef = useRef<IntersectionObserver>();
const observer = useRef<IntersectionObserver>();
const updateInView = useCallback(
(val: boolean) => {
@@ -51,22 +52,25 @@ export function useObserver<T extends Element = Element>({
threshold: threshold
};
iObserverRef.current = new IntersectionObserver((entries) => {
updateInView(entries[0].isIntersecting);
}, options);
observer.current = new IntersectionObserver(
debounce((entries) => {
updateInView(entries[0].isIntersecting);
}, 500) as IntersectionObserverCallback,
options
);
});
useEffect(() => {
if (ref.current) {
iObserverRef?.current?.observe(ref.current);
observer?.current?.observe(ref.current);
}
const reference = ref.current;
return () => {
if (reference) {
iObserverRef.current?.unobserve(reference);
iObserverRef.current?.disconnect();
observer.current?.unobserve(reference);
observer.current?.disconnect();
}
};
});

View File

@@ -144,3 +144,11 @@ export function toBlobURL(
if (id) OBJECT_URL_CACHE[id] = objectURL;
return objectURL;
}
export function revokeBloburl(id: string) {
const url = OBJECT_URL_CACHE[id];
if (!url) return;
URL.revokeObjectURL(url);
OBJECT_URL_CACHE[id] = undefined;
}