Compare commits

..

25 Commits

Author SHA1 Message Date
Ammar Ahmed
cbcef9d44a mobile: update mmkv package 2023-11-25 15:18:53 +05:00
Abdullah Atta
b9aa49e17a core: fix logs sorting 2023-11-25 14:42:14 +05:00
Abdullah Atta
bab65528db core: optimize logger 2023-11-25 14:30:48 +05:00
Abdullah Atta
4a743d98b7 web: use removeMulti to bulk remove old logs 2023-11-25 13:40:19 +05:00
Ammar Ahmed
4b44c39ec8 mobile: fix app freeze when deleting old logs 2023-11-25 13:17:31 +05:00
Ammar Ahmed
a7d85cb3f6 core: add removeMulti to storage 2023-11-25 13:17:10 +05:00
Ammar Ahmed
50137e0994 mobile: fix subscriptions not fulfilling on ios 2023-11-25 10:32:24 +05:00
Abdullah Atta
8b5babd5f8 desktop: use node.js fetch instead of net.fetch 2023-11-25 10:20:19 +05:00
Abdullah Atta
39e83200c8 web: fix attachment upload progress on desktop 2023-11-25 10:19:57 +05:00
Abdullah Atta
ead0e0107e core: allow deleting unuploaded attachments 2023-11-25 10:19:38 +05:00
Abdullah Atta
1c888bd4fd web: always open announcement link in browser on desktop app 2023-11-25 10:18:20 +05:00
Krishna Bharadwaj
ff92530339 web: hide email address from status bar (#3859) 2023-11-24 11:18:46 +05:00
Ammar Ahmed
ee38032403 mobile: release v2.6.11 2023-11-22 14:45:16 +05:00
Ammar Ahmed
2ce245659a mobile: fix pricing plans 2023-11-22 14:27:20 +05:00
Abdullah Atta
2c635eb4d7 web: bump version to 2.6.11 2023-11-22 10:44:39 +05:00
Abdullah Atta
d653223df0 editor: load images threadsafely 2023-11-22 10:36:48 +05:00
Muhammad Ali
290b0dfc78 web: close reminder dialog on setting snooze time (#3842) 2023-11-21 10:53:53 +05:00
Muhammad Ali
06bb9e654b web: fix weird janky animation when opening dialogs (#3826) 2023-11-21 08:20:26 +05:00
Muhammad Ali
576fe78e33 clipper: hide clip preview button when there's no clip data (#3772) 2023-11-21 08:18:28 +05:00
Abdullah Atta
0c16bfb5ff web: fix issue with service worker not getting registered 2023-11-20 12:34:07 +05:00
Abdullah Atta
a101dbdf31 web: use bloburl when previewing images 2023-11-20 11:12:02 +05:00
Abdullah Atta
682609044f editor: add support for previewing SVG images 2023-11-20 11:12:02 +05:00
Abdullah Atta
c0a7fb3a5c misc: remove unnecessary files 2023-11-20 10:01:08 +05:00
Abdullah Atta
a153aa791c web: bump version to 2.6.10 2023-11-20 09:49:31 +05:00
Abdullah Atta
76b3dd1f96 web: check if file exists before starting upload 2023-11-20 09:41:35 +05:00
44 changed files with 791 additions and 622 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.6.9",
"version": "2.6.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.6.9",
"version": "2.6.11",
"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.9",
"version": "2.6.11",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",

View File

@@ -17,7 +17,7 @@ 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 { protocol, net } from "electron";
import { protocol } from "electron";
import { isDevelopment } from "./index";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
@@ -62,8 +62,25 @@ function registerProtocol() {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
return net.fetch(request, {
bypassCustomProtocolHandlers: true
if (request.headers.has("X-Content-Length")) {
request.headers.set(
"Content-Length",
request.headers.get("X-Content-Length") || "0"
);
request.headers.delete("X-Content-Length");
}
const headers = Object.fromEntries(request.headers.entries());
return await fetch(request.url, {
signal: request.signal,
mode: request.mode,
headers,
method: request.method,
body: request.body,
credentials: request.credentials,
referrer: (request as any).referrer,
duplex: "half",
redirect: "manual"
});
}
});

View File

@@ -16,8 +16,8 @@ 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/>.
*/
import { MMKVLoader } from "react-native-mmkv-storage";
import { initalize } from "@notesnook/core/dist/logger";
import { MMKVLoader } from "react-native-mmkv-storage";
import { KV } from "./storage";
const LoggerStorage = new MMKVLoader()
@@ -26,4 +26,4 @@ const LoggerStorage = new MMKVLoader()
initalize(new KV(LoggerStorage));
export {};
export { LoggerStorage };

View File

@@ -90,6 +90,10 @@ export class KV {
return this.storage.removeItem(key);
}
async removeMulti(keys) {
return this.storage.removeItems(...keys);
}
async clear() {
return this.storage.clearStore();
}
@@ -145,6 +149,7 @@ export default {
clear: () => DefaultStorage.clear(),
getAllKeys: () => DefaultStorage.getAllKeys(),
writeMulti: (items) => DefaultStorage.writeMulti(items),
removeMulti: (keys) => DefaultStorage.removeMulti(keys),
encrypt,
decrypt,
decryptMulti,

View File

@@ -34,10 +34,69 @@ import BaseDialog from "../dialog/base-dialog";
import { allowedOnPlatform, renderItem } from "./functions";
import { useCallback } from "react";
/**
* Test announcement
* {
id: "some-announcement",
type: "dialog",
body: [
{
type: "title",
text: "This is a title",
platforms: ["all"]
},
{
type: "description",
text: "Most of you are too busy to keep up to date with what's happening in Notesnook. That is unfortunate because Notesnook has come a looooong way.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "description",
text: "To solve this, we are launching the Notesnook Digest — a newsletter to help you stay updated about Notesnook development. And to keep things interesting I'll also sprinkle this newsletter with other interesting stuff like privacy tips & news, interesting books, things I am looking forward to etc.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "description",
text: "So be sure to subscribe. There won't be a proper schedule to this (yet) maybe once or twice a month. I promise no spam — only more awesomeness.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "description",
text: "— May privacy reign.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "callToActions",
actions: [
{
type: "promo",
title: "15% Off",
platforms: ["android"],
data: "com.streetwriters.notesnook.sub.yr.15"
}
],
platforms: ["all"]
}
]
}
*/
export const AnnouncementDialog = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [info, setInfo] = useState(null);
const [info, setInfo] = useState();
const remove = useMessageStore((state) => state.remove);
useEffect(() => {
@@ -51,7 +110,9 @@ export const AnnouncementDialog = () => {
const open = (data) => {
setInfo(data);
setVisible(true);
setImmediate(() => {
setVisible(true);
});
};
const close = useCallback(() => {

View File

@@ -48,23 +48,23 @@ export const Expiring = () => {
const [visible, setVisible] = useState(false);
const [status, setStatus] = useState({
title: "Your trial is ending soon",
offer: null,
offer: "Get 30% off",
extend: true
});
const pricing = usePricing("yearly");
const promo = status.offer
? {
promoCode:
pricing?.info?.discount > 30
? pricing.info.sku
: "com.streetwriters.notesnook.sub.yr.trialoffer",
text: `GET ${
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
}% OFF on yearly`,
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
}
: null;
const promo =
status.offer && pricing?.info
? {
promoCode:
pricing?.info?.discount > 30
? pricing.info.sku
: "com.streetwriters.notesnook.sub.yr.trialoffer",
text: `GET ${
pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
}% OFF on yearly`,
discount: pricing?.info?.discount > 30 ? pricing?.info?.discount : 30
}
: null;
useEffect(() => {
eSubscribeEvent(eOpenTrialEndingDialog, open);

View File

@@ -28,7 +28,8 @@ import RNIap from "react-native-iap";
export const PricingItem = ({
product,
onPress,
compact
compact,
strikethrough
}: {
product: {
type: "yearly" | "monthly";
@@ -36,6 +37,7 @@ export const PricingItem = ({
info: string;
offerType?: "yearly" | "monthly";
};
strikethrough?: boolean;
onPress?: () => void;
compact?: boolean;
}) => {
@@ -50,8 +52,10 @@ export const PricingItem = ({
paddingHorizontal: 12,
paddingVertical: compact ? 15 : 10,
width: compact ? null : "100%",
minWidth: 150
minWidth: 150,
opacity: strikethrough ? 0.7 : 1
}}
disabled={strikethrough}
>
{!compact && (
<View>
@@ -67,8 +71,18 @@ export const PricingItem = ({
)}
<View>
<Paragraph size={SIZE.sm}>
<Heading size={SIZE.lg - 2}>
<Paragraph
style={{
textDecorationLine: strikethrough ? "line-through" : undefined
}}
size={SIZE.sm}
>
<Heading
style={{
textDecorationLine: strikethrough ? "line-through" : undefined
}}
size={SIZE.lg - 2}
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid | undefined)
?.subscriptionOfferDetails[0].pricingPhases

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Platform, Text, View } from "react-native";
import * as RNIap from "react-native-iap";
import { db } from "../../common/database";
import { DatabaseLogger, db } from "../../common/database";
import { usePricing } from "../../hooks/use-pricing";
import {
eSendEvent,
@@ -49,6 +49,20 @@ import { Walkthrough } from "../walkthroughs";
import { PricingItem } from "./pricing-item";
import { useSettingStore } from "../../stores/use-setting-store";
const UUID_PREFIX = "0bdaea";
const UUID_VERSION = "4";
const UUID_VARIANT = "a";
function toUUID(str: string) {
return [
UUID_PREFIX + str.substring(0, 2), // 6 digit prefix + first 2 oid digits
str.substring(2, 6), // # next 4 oid digits
UUID_VERSION + str.substring(6, 9), // # 1 digit version(0x4) + next 3 oid digits
UUID_VARIANT + str.substring(9, 12), // # 1 digit variant(0b101) + 1 zero bit + next 3 oid digits
str.substring(12)
].join("-");
}
const promoCyclesMonthly = {
1: "first month",
2: "first 2 months",
@@ -177,11 +191,15 @@ export const PricingPlans = ({
.offerToken
: null;
DatabaseLogger.info(
`Subscription Requested initiated for user ${toUUID(user.id)}`
);
await RNIap.requestSubscription({
sku: product?.productId,
obfuscatedAccountIdAndroid: user.id,
obfuscatedProfileIdAndroid: user.id,
appAccountToken: user.id,
appAccountToken: toUUID(user.id),
andDangerouslyFinishTransactionAutomaticallyIOS: false,
subscriptionOffers: androidOfferToken
? [
@@ -213,6 +231,35 @@ export const PricingPlans = ({
}
};
function getStandardPrice() {
if (!product) return;
const productType = product.offerType;
if (Platform.OS === "android") {
const pricingPhaseListItem = (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList?.[1];
if (!pricingPhaseListItem) {
const product =
productType === "monthly"
? monthlyPlan?.product
: yearlyPlan?.product;
return (product as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList?.[0]
?.formattedPrice;
}
return pricingPhaseListItem?.formattedPrice;
} else {
const productDefault =
productType === "monthly" ? monthlyPlan?.product : yearlyPlan?.product;
return (
(product.data as RNIap.SubscriptionIOS)?.localizedPrice ||
(productDefault as RNIap.SubscriptionIOS)?.localizedPrice
);
}
}
return loading ? (
<View
style={{
@@ -292,34 +339,84 @@ export const PricingPlans = ({
) : (
<>
{product?.type === "promo" ? (
<Heading
<View
style={{
paddingVertical: 15,
alignSelf: "center",
textAlign: "center"
alignItems: "center"
}}
size={SIZE.lg - 4}
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0].formattedPrice
: (product.data as RNIap.SubscriptionIOS)?.introductoryPrice}
<Paragraph
{product?.offerType === "monthly" ? (
<PricingItem
product={{
type: "monthly",
data: monthlyPlan?.product,
info: "Pay once a month, cancel anytime."
}}
strikethrough={true}
/>
) : (
<PricingItem
onPress={() => {
if (!monthlyPlan?.product) return;
buySubscription(monthlyPlan?.product);
}}
product={{
type: "yearly",
data: yearlyPlan?.product,
info: "Pay once a year, cancel anytime."
}}
strikethrough={true}
/>
)}
<Heading
style={{
textDecorationLine: "line-through",
color: colors.secondary.paragraph
paddingTop: 15,
fontSize: SIZE.lg
}}
size={SIZE.sm}
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[1]?.pricingPhases
.pricingPhaseList?.[1]?.formattedPrice
: (product.data as RNIap.SubscriptionIOS)?.localizedPrice}
</Paragraph>{" "}
for {product.cycleText}
</Heading>
Special offer for you
</Heading>
<View
style={{
paddingVertical: 20,
paddingBottom: 10
}}
>
<Heading
style={{
alignSelf: "center",
textAlign: "center"
}}
size={SIZE.xxl}
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0]?.formattedPrice
: (product.data as RNIap.SubscriptionIOS)
?.introductoryPrice ||
(product.data as RNIap.SubscriptionIOS)
?.localizedPrice}{" "}
{product?.cycleText
? `for ${product.cycleText}`
: product?.offerType}
</Heading>
{product?.cycleText ? (
<Paragraph
style={{
color: colors.secondary.paragraph,
alignSelf: "center",
textAlign: "center"
}}
size={SIZE.md}
>
then {getStandardPrice()} {product?.offerType}.
</Paragraph>
) : null}
</View>
</View>
) : null}
{user && !product ? (
@@ -334,7 +431,12 @@ export const PricingPlans = ({
marginBottom: 20
}}
>
<Heading color={colors.primary.accent}>
<Heading
style={{
textAlign: "center"
}}
color={colors.primary.accent}
>
Get {monthlyPlan?.info?.discount}% off in{" "}
{monthlyPlan?.info?.country}
</Heading>
@@ -369,7 +471,7 @@ export const PricingPlans = ({
product={{
type: "monthly",
data: monthlyPlan?.product,
info: "Pay monthly, cancel anytime."
info: "Pay once a month, cancel anytime."
}}
/>
@@ -391,7 +493,7 @@ export const PricingPlans = ({
product={{
type: "yearly",
data: yearlyPlan?.product,
info: "Pay yearly"
info: "Pay once a year, cancel anytime."
}}
/>
</View>
@@ -463,7 +565,7 @@ export const PricingPlans = ({
width={250}
style={{
paddingHorizontal: 12,
marginTop: 30,
marginTop: product?.type === "promo" ? 0 : 30,
marginBottom: 10
}}
/>

View File

@@ -460,7 +460,6 @@ export const useAppEvents = () => {
}
clearMessage();
subscribeToIAPListeners();
if (!login) {
user = await db.user.fetchUser();
setUser(user);
@@ -479,7 +478,11 @@ export const useAppEvents = () => {
userEmailConfirmed: true
});
}
subscribeToIAPListeners();
} catch (e) {
DatabaseLogger.error(error);
ToastEvent.error(e, "An error occurred", "global");
}
@@ -503,14 +506,15 @@ export const useAppEvents = () => {
);
const subscribeToIAPListeners = useCallback(async () => {
RNIap.flushFailedPurchasesCachedAsPendingAndroid()
.catch(() => {})
.then(() => {
refValues.current.subsriptionSuccessListener =
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
refValues.current.subsriptionErrorListener =
RNIap.purchaseErrorListener(onSubscriptionError);
});
if (Platform.OS === "android") {
try {
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
} catch (e) {}
}
refValues.current.subsriptionSuccessListener =
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
refValues.current.subsriptionErrorListener =
RNIap.purchaseErrorListener(onSubscriptionError);
}, []);
const unSubscribeFromIAPListeners = () => {

View File

@@ -42,6 +42,9 @@ export const Subscription = () => {
user?.subscription?.type !== SUBSCRIPTION_STATUS.PREMIUM &&
user?.subscription?.type !== SUBSCRIPTION_STATUS.BETA;
const hasCancelledPremium =
SUBSCRIPTION_STATUS.PREMIUM_CANCELLED === user?.subscription?.type;
const subscriptionProviderInfo =
SUBSCRIPTION_PROVIDER[user?.subscription?.provider];
@@ -63,10 +66,7 @@ export const Subscription = () => {
return;
}
if (
user?.subscription?.type === SUBSCRIPTION_STATUS.PREMIUM_CANCELLED &&
Platform.OS === "android"
) {
if (hasCancelledPremium && Platform.OS === "android") {
if (user.subscription?.provider === 3) {
ToastEvent.show({
heading: "Subscribed on web",
@@ -111,16 +111,14 @@ export const Subscription = () => {
title={
!user?.isEmailConfirmed
? "Confirm your email"
: user.subscription?.provider === 3 &&
user.subscription?.type ===
SUBSCRIPTION_STATUS.PREMIUM_CANCELLED
: user.subscription?.provider === 3 && hasCancelledPremium
? "Manage subscription from desktop app"
: user.subscription?.type ===
SUBSCRIPTION_STATUS.PREMIUM_CANCELLED &&
: hasCancelledPremium &&
Platform.OS === "android" &&
Config.GITHUB_RELEASE !== "true"
? "Resubscribe from Google Playstore"
: user.subscription?.type === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED
: user.subscription?.type ===
SUBSCRIPTION_STATUS.PREMIUM_EXPIRED || hasCancelledPremium
? `Resubscribe to Pro (${getPrice() || "$4.49"} / mo)`
: `Get Pro (${getPrice() || "$4.49"} / mo)`
}

View File

@@ -233,7 +233,7 @@ const subscriptions = {
*
* @returns {RNIap.Purchase} subscription
*/
get: async () => {
get: () => {
if (Platform.OS === "android") return;
let _subscriptions = MMKV.getString("subscriptionsIOS");
if (!_subscriptions) return [];
@@ -299,12 +299,14 @@ const subscriptions = {
"Content-Type": "application/json"
}
};
console.log("Subscription.verify", requestData);
try {
let result = await fetch(
"https://payments.streetwriters.co/apple/verify",
"http://192.168.43.5:4264/apple/verify",
requestData
);
console.log("Subscribed", result);
let text = await result.text();
if (!result.ok) {
@@ -312,6 +314,8 @@ const subscriptions = {
await subscriptions.clear(subscription);
}
return;
} else {
await subscriptions.clear(subscription);
}
} catch (e) {
console.log("subscription error", e);
@@ -321,15 +325,18 @@ const subscriptions = {
},
clear: async (_subscription) => {
if (Platform.OS === "android") return;
let _subscriptions = await subscriptions.get();
let _subscriptions = subscriptions.get();
let subscription = null;
if (_subscription) {
subscription = _subscription;
} else {
subscription = _subscriptions.length > 0 ? _subscriptions[0] : null;
}
if (subscription) {
await RNIap.finishTransaction(subscription.transactionId);
await RNIap.finishTransaction({
purchase: subscription
});
await RNIap.clearTransactionIOS();
await subscriptions.remove(subscription.transactionId);
}

View File

@@ -75,7 +75,7 @@ export const SUBSCRIPTION_STATUS_STRINGS = {
2: Platform.OS === "ios" ? "Pro" : "Beta",
5: "Pro",
6: "Expired",
7: "Pro"
7: "Pro (cancelled)"
};
export const SUBSCRIPTION_PROVIDER = {

View File

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

View File

@@ -1,7 +1,3 @@
- Fixed HTML & PDF exports
- Improved task lists in editor
- Improved loading images
- Allow restoring backups when logged out
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -997,7 +997,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2061;
CURRENT_PROJECT_VERSION = 2062;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1071,7 +1071,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1102,7 +1102,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2061;
CURRENT_PROJECT_VERSION = 2062;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1176,7 +1176,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1335,7 +1335,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2061;
CURRENT_PROJECT_VERSION = 2062;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1347,7 +1347,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1378,7 +1378,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2061;
CURRENT_PROJECT_VERSION = 2062;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1391,7 +1391,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1421,7 +1421,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2061;
CURRENT_PROJECT_VERSION = 2062;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1495,7 +1495,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1526,7 +1526,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2061;
CURRENT_PROJECT_VERSION = 2062;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1601,7 +1601,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -327,7 +327,7 @@ PODS:
- React-Core
- react-native-keep-awake (1.2.0):
- React-Core
- react-native-mmkv-storage (0.10.0-alpha.9):
- react-native-mmkv-storage (0.10.0-alpha.11):
- MMKV (~> 1.3.1)
- React
- React-Core
@@ -875,7 +875,7 @@ SPEC CHECKSUMS:
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
react-native-keep-awake: caee3ff89eaa21dfe29010f0d143566874a04441
react-native-mmkv-storage: d4ad55ab411b7f0d4e6269d801b31821ef48970b
react-native-mmkv-storage: e798639f91601896f1c84f4eb3d6f19af2f4a160
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628

View File

@@ -44,7 +44,7 @@
"react-native-image-picker": "4.1.2",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.9",
"react-native-mmkv-storage": "^0.10.0-alpha.11",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.6.10",
"version": "2.6.11",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "2.6.9",
"version": "2.6.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "2.6.9",
"version": "2.6.11",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",

View File

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

View File

@@ -291,7 +291,9 @@ function CalltoAction({ action, variant, sx, dismissAnnouncement }) {
case "link": {
const url = new URL(action.data);
const target =
url.origin === window.location.origin ? "_self" : "_blank";
url.origin === window.location.origin && !IS_DESKTOP_APP
? "_self"
: "_blank";
window.open(action.data, target, "noopener noreferrer");
break;
}

View File

@@ -80,11 +80,10 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
border: 0,
zIndex: 999,
backgroundColor: "var(--backdrop)"
},
overlay: {
opacity: 1
}
// overlay: {
// zIndex: 999,
// background: "var(--backdrop)"
// }
}}
>
<ScopedThemeProvider

View File

@@ -430,13 +430,15 @@ export function Editor(props: EditorProps) {
onContentChange={onContentChange}
onChange={onEditorChange}
onDownloadAttachment={(attachment) => saveAttachment(attachment.hash)}
onPreviewAttachment={async ({ hash, dataurl }) => {
onPreviewAttachment={async (data) => {
const { hash } = data;
const attachment = db.attachments?.attachment(hash);
if (attachment && attachment.metadata.type.startsWith("image/")) {
const container = document.getElementById("dialogContainer");
if (!(container instanceof HTMLElement)) return;
dataurl = dataurl || (await downloadAttachment(hash, "base64"));
const dataurl =
data.bloburl || (await downloadAttachment(hash, "base64"));
if (!dataurl)
return showToast("error", "This image cannot be previewed.");

View File

@@ -137,6 +137,7 @@ export type Attachment = {
mime: string;
size: number;
dataurl?: string;
bloburl?: string;
};
type AddAttachmentOptions = {

View File

@@ -97,8 +97,7 @@ function StatusBar() {
ml={1}
sx={{ color: "paragraph" }}
>
{user?.email}
{user?.isEmailConfirmed ? "" : " (not verified)"}
{user?.isEmailConfirmed ? "" : "Email not confirmed"}
</Text>
</Button>

View File

@@ -105,6 +105,7 @@ export default function ReminderPreviewDialog(
id: reminder.id,
snoozeUntil: Date.now() + time.interval
});
props.onClose(false);
}}
sx={{
borderRadius: 100,

View File

@@ -226,8 +226,10 @@ type UploadAdditionalData = {
async function uploadFile(filename: string, requestOptions: RequestOptions) {
const fileHandle = await streamablefs.readFile(filename);
if (!fileHandle)
throw new Error(`File stream not found. (File hash: ${filename})`);
if (!fileHandle || !(await exists(filename)))
throw new Error(
`File is corrupt or missing data. Please upload the file again. (File hash: ${filename})`
);
try {
if (fileHandle.file.additionalData?.uploaded) {
await checkUpload(filename);
@@ -359,6 +361,7 @@ async function multiPartUploadFile(
);
};
onUploadProgress({ bytes: 0, loaded: 0 });
for (let i = uploadedChunks.length; i < TOTAL_PARTS; ++i) {
const blob = await fileHandle.readChunks(
i * UPLOAD_PART_REQUIRED_CHUNKS,
@@ -370,7 +373,7 @@ async function multiPartUploadFile(
.request({
url,
method: "PUT",
headers: { "Content-Type": "" },
headers: { "Content-Type": "", "X-Content-Length": data.byteLength },
signal,
data,
onUploadProgress
@@ -391,6 +394,8 @@ async function multiPartUploadFile(
});
await fileHandle.addAdditionalData("uploadedChunks", uploadedChunks);
await fileHandle.addAdditionalData("uploadedBytes", uploadedBytes);
onUploadProgress({ bytes: 0, loaded: blob.size });
}
await axios

View File

@@ -70,6 +70,10 @@ export class NNStorage {
return this.database.delete(key);
}
removeMulti(keys: string[]) {
return this.database.deleteMany(keys);
}
clear() {
return this.database.clear();
}

View File

@@ -56,26 +56,23 @@ export function register(config: ServiceWorkerRegistrationConfig) {
return;
}
window.addEventListener("load", () => {
const swUrl = `${PUBLIC_URL}/service-worker.js`;
const swUrl = `${PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit https://cra.link/PWA"
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit https://cra.link/PWA"
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
}
}

14
bugs.md
View File

@@ -1,14 +0,0 @@
Mobile:
1. Select all notes in a topic -> Click on add button
2. Edit button in a notebook doesn't work
3. Edit notebook sheet doesn't close after saving edits
4. Removing notes from a topic doesn't update the notes count in the list automatically
5. Notes inside a notebook/topic keep showing the notebook tag at the top
6.
Web:
1. When a note is in a single topic, its impossible to move it to its parent notebook
2. When a note is in a single notebook, its impossible to remove it
3. Disable sync -> Refresh app -> Enable sync & notice how the sync status is not updated nor does the sync run automatically

View File

@@ -1,81 +0,0 @@
workflows:
react-native-ios:
name: Notesnook iOS Build
max_build_duration: 120
instance_type: mac_mini
environment:
groups:
- appstore_credentials
- certificate_credentials
- provisioning_profile
vars:
XCODE_WORKSPACE: "Notesnook.xcworkspace" # <-- Put the name of your Xcode workspace here
XCODE_SCHEME: "Notesnook" # <-- Put the name of your Xcode scheme here
BUNDLE_ID: "org.streetwriters.notesnook" # <-- Put your Bundle Id here e.g com.domain.myapp
APP_STORE_APP_ID: 1544027013 # <-- Put the app id number here. This is found in App Store Connect > App > General > App Information
node: 16.13.2
xcode: latest
cocoapods: default
cache:
cache_paths:
- $HOME/Library/Caches/CocoaPods
scripts:
- name: Install npm dependencies
script: |
npm i
- name: Install CocoaPods dependencies
script: |
yarn prepare:ios
- name: Set up keychain to be used for codesigning using Codemagic CLI 'keychain' command
script: |
keychain initialize
- name: Set up Provisioning profiles from environment variables
script: |
PROFILES_HOME="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_HOME"
for profile in "${!FCI_PROVISIONING_PROFILE_@}"; do
PROFILE_PATH="$(mktemp "$HOME/Library/MobileDevice/Provisioning Profiles"/ios_$(uuidgen).mobileprovision)"
echo ${!profile} | base64 --decode > "$PROFILE_PATH"
echo "Saved provisioning profile $PROFILE_PATH"
done
- name: Set up signing certificate
script: |
echo $FCI_CERTIFICATE | base64 --decode > /tmp/certificate.p12
if [ -z ${FCI_CERTIFICATE_PASSWORD+x} ]; then
# when using a certificate that is not password-protected
keychain add-certificates --certificate /tmp/certificate.p12
else
# when using a password-protected certificate
keychain add-certificates --certificate /tmp/certificate.p12 --certificate-password $FCI_CERTIFICATE_PASSWORD
fi
- name: Set up code signing settings on Xcode project
script: xcode-project use-profiles
- name: Build packages
script: |
yarn build
- name: Build ipa for distribution
script: |
xcode-project build-ipa --workspace "$FCI_BUILD_DIR/apps/mobile/native/ios/$XCODE_WORKSPACE" --scheme "$XCODE_SCHEME"
artifacts:
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.app
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.dSYM
publishing:
# See the following link for details about email publishing - https://docs.codemagic.io/publishing-yaml/distribution/#email
email:
recipients:
- ammarahmed6506@gmail.com
notify:
success: true # To not receive a notification when a build succeeds
failure: false # To not receive a notification when a build fails
app_store_connect:
api_key: $APP_STORE_CONNECT_PRIVATE_KEY # Contents of the API key, can also reference environment variable such as $APP_STORE_CONNECT_PRIVATE_KEY
key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER # Alphanumeric value that identifies the API key, can also reference environment variable such as $APP_STORE_CONNECT_KEY_IDENTIFIER
issuer_id: $APP_STORE_CONNECT_ISSUER_ID # Alphanumeric value that identifies who created the API key, can also reference environment variable such as $APP_STORE_CONNECT_ISSUER_ID
submit_to_testflight: false # Optional boolean, defaults to false. Whether or not to submit the uploaded build to TestFlight beta review. Required for distributing to beta groups. Note: This action is performed during post-processing.
# beta_groups: # Specify the names of beta tester groups that will get access to the build once it has passed beta review.
# - group name 1
# - group name 2

View File

@@ -306,7 +306,7 @@ export function Main() {
</Button>
))}
{clipData && !isClipping && (
{clipData && clipData.data && !isClipping && (
<Text
variant="body"
sx={{
@@ -322,7 +322,6 @@ export function Main() {
}
}}
onClick={async () => {
if (!clipData) return;
const winUrl = URL.createObjectURL(
new Blob(["\ufeff", clipData.data], { type: "text/html" })
);

View File

@@ -0,0 +1,3 @@
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -210,7 +210,12 @@ export default class Attachments extends Collection {
if (!localOnly && !(await this._canDetach(attachment)))
throw new Error("This attachment is inside a locked note.");
if (await this._db.fs.deleteFile(attachment.metadata.hash, localOnly)) {
if (
await this._db.fs.deleteFile(
attachment.metadata.hash,
localOnly || !attachment.dateUploaded
)
) {
if (!localOnly) {
await this.detach(attachment);
}

View File

@@ -48,6 +48,10 @@ export default class Storage {
return this.storage.remove(key);
}
removeMulti(keys) {
return this.storage.removeMulti(keys);
}
getAllKeys() {
return this.storage.getAllKeys();
}

View File

@@ -59,8 +59,7 @@ class DatabaseLogWriter {
*/
constructor(storage) {
this.storage = storage;
this.key = new Date().toLocaleDateString();
this.queue = {};
this.queue = new Map();
this.hasCleared = false;
setInterval(() => {
setTimeout(() => {
@@ -74,34 +73,29 @@ class DatabaseLogWriter {
}
push(message) {
this.queue[`${this.key}:${message.timestamp}`] = message;
}
async read() {
const logKeys = await this.storage.getAllKeys();
const keys = [];
for (let key of logKeys) {
if (key.startsWith(this.key)) keys.push(key);
}
return Object.values(await this.storage.readMulti(keys));
const key = new Date(message.timestamp).toLocaleDateString();
this.queue.set(`${key}:${message.timestamp}`, message);
}
async flush() {
if (Object.keys(this.queue).length === 0) return;
const queueCopy = Object.entries(this.queue);
this.queue = {};
if (this.queue.size === 0) return;
const queueCopy = Array.from(this.queue.entries());
this.queue = new Map();
await this.storage.writeMulti(queueCopy);
}
async rotate() {
const logKeys = (await this.storage.getAllKeys()).sort();
const keysToRemove = [];
for (let key of logKeys) {
const keyParts = key.split(":");
if (keyParts.length === 1 || parseInt(keyParts[1]) < Date.now() - WEEK) {
await this.storage.remove(key);
keysToRemove.push(key);
}
}
if (keysToRemove.length) await this.storage.removeMulti(keysToRemove);
}
}
@@ -116,42 +110,41 @@ class DatabaseLogManager {
async get() {
const logKeys = await this.storage.getAllKeys();
const logs = {};
const logs = await this.storage.readMulti(logKeys);
const logGroups = {};
for (const logKey of logKeys) {
const keyParts = logKey.split(":");
for (const [key, log] of logs) {
const keyParts = key.split(":");
if (keyParts.length === 1) continue;
const key = keyParts[0];
const log = await this.storage.read(logKey, true);
if (!logs[key]) logs[key] = [];
logs[key].push(log);
const groupKey = keyParts[0];
if (!logGroups[groupKey]) logGroups[groupKey] = [];
logGroups[groupKey].push(log);
}
return Object.keys(logs).map((key) => ({
key: key,
logs: logs[key]?.sort((a, b) => a.timestamp - b.timestamp)
}));
return Object.keys(logGroups)
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
.map((key) => ({
key,
logs: logGroups[key]?.sort((a, b) => a.timestamp - b.timestamp)
}));
}
async clear() {
const logKeys = await this.storage.getAllKeys();
for (const key of logKeys) {
await this.storage.remove(key);
}
await this.storage.removeMulti(logKeys);
}
async delete(key) {
const logKeys = await this.storage.getAllKeys();
const keysToRemove = [];
for (const logKey of logKeys) {
const keyParts = logKey.split(":");
if (keyParts.length === 1) continue;
const currKey = keyParts[0];
if (currKey === key) {
await this.storage.remove(logKey);
}
if (currKey === key) keysToRemove.push(logKey);
}
if (keysToRemove.length) await this.storage.removeMulti(keysToRemove);
}
}

View File

@@ -39,6 +39,7 @@
"@tiptap/extension-underline": "2.1.12",
"@tiptap/pm": "2.1.12",
"@tiptap/starter-kit": "2.1.12",
"async-mutex": "^0.4.0",
"clipboard-polyfill": "4.0.0",
"detect-indent": "^7.0.0",
"entities": "^4.5.0",
@@ -1739,6 +1740,14 @@
"node": "*"
}
},
"node_modules/async-mutex": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
"integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -3113,7 +3122,6 @@
"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"
@@ -3134,7 +3142,6 @@
"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",
@@ -3267,7 +3274,6 @@
"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"
@@ -3533,8 +3539,7 @@
"node_modules/tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
"dev": true
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
},
"node_modules/type-detect": {
"version": "4.0.8",
@@ -4085,7 +4090,8 @@
"@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=="
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
"requires": {}
},
"@emotion/utils": {
"version": "1.2.1",
@@ -4559,87 +4565,104 @@
"@tiptap/core": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.12.tgz",
"integrity": "sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ=="
"integrity": "sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-Qb3YRlCfugx9pw7VgLTb+jY37OY4aBJeZnqHzx4QThSm13edNYjasokbX0nTwL1Up4NPTcY19JUeHt6fVaVVGg==",
"requires": {}
},
"@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=="
"integrity": "sha512-AZGxIxcGU1/y6V2YEbKsq6BAibL8yQrbRm6EdcBnby41vj1WziewEKswhLGmZx5IKM2r2ldxld03KlfSIlKQZg==",
"requires": {}
},
"@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=="
"integrity": "sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-CRiRq5OTC1lFgSx6IMrECqmtb93a0ZZKujEnaRhzWliPBjLIi66va05f/P1vnV6/tHaC3yfXys6dxB5A4J8jxw==",
"requires": {}
},
"@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=="
"integrity": "sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==",
"requires": {}
},
"@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=="
"integrity": "sha512-Myd6iSbPJvvclr+NRBEdE0k52QlQrXZnJljk4JKn0b25cl60ERA40FH9QLBjkpTed7SDbI3oX7LWIzTUoCj39w==",
"requires": {}
},
"@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=="
"integrity": "sha512-0QNfAkCcFlB9O8cUNSwTSIQMV9TmoEhfEaLz/GvbjwEq4skXK3bU+OQX7Ih07waCDVXIGAZ7YAZogbvrn/WbOw==",
"requires": {}
},
"@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=="
"integrity": "sha512-0tT/q8nL4NBCYPxr9T0Brck+RQbWuczm9nV0bnxgt0IiQXoRHutfPWdS7GA65PTuVRBS/3LOco30fbjFhkfz/A==",
"requires": {}
},
"@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=="
"integrity": "sha512-1PAvmtilBD1OWfr1I+oKND1MLNGWMEx/Qa7xC2YDzonxhiBd56Xog6fDE/+2Bbud0vPEIrMHrg8QOjCDH413vw==",
"requires": {}
},
"@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=="
"integrity": "sha512-zFYdZCqPgpwoB7whyuwpc8EYLYjUE5QYKb8vICvc+FraBUDM51ujYhFSgJC3rhs8EjI+8GcK8ShLbSMIn49YOQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-nqKcAYGEOafg9D+2cy1E4gHNGuL12LerVa0eS2SQOb+PT8vSel9OTKU1RyZldsWSQJ5rq/w4uIjmLnrSR2w6Yw==",
"requires": {}
},
"@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=="
"integrity": "sha512-MoANP3POAP68Ko9YXarfDKLM/kXtscgp6m+xRagPAghRNujVY88nK1qBMZ3JdvTVN6b/ATJhp8UdrZX96TLV2w==",
"requires": {}
},
"@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=="
"integrity": "sha512-6b7UFVkvPjq3LVoCTrYZAczt5sQrQUaoDWAieVClVZoFLfjga2Fwjcfgcie8IjdPt8YO2hG/sar/c07i9vM0Sg==",
"requires": {}
},
"@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=="
"integrity": "sha512-RRuoK4KxrXRrZNAjJW5rpaxjiP0FJIaqpi7nFbAua2oHXgsCsG8qbW2Y0WkbIoS8AJsvLZ3fNGsQ8gpdliuq3A==",
"requires": {}
},
"@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=="
"integrity": "sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A==",
"requires": {}
},
"@tiptap/extension-link": {
"version": "2.1.12",
@@ -4652,92 +4675,110 @@
"@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=="
"integrity": "sha512-Gk7hBFofAPmNQ8+uw8w5QSsZOMEGf7KQXJnx5B022YAUJTYYxO3jYVuzp34Drk9p+zNNIcXD4kc7ff5+nFOTrg==",
"requires": {}
},
"@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=="
"integrity": "sha512-f19nGaqhIZhssM2k8nYR+zcoMc7UCLcW6YCNhTXSrybUsb6SMFVob9OL7+sy1x2n5Was5IqsvyAGakLjdTEwAw==",
"requires": {}
},
"@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=="
"integrity": "sha512-tF6VGl+D2avCgn9U/2YLJ8qVmV6sPE/iEzVAFZuOSe6L0Pj7SQw4K6AO640QBob/d8VrqqJFHCb6l10amJOnXA==",
"requires": {}
},
"@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=="
"integrity": "sha512-hoH/uWPX+KKnNAZagudlsrr4Xu57nusGekkJWBcrb5MCDE91BS+DN2xifuhwXiTHxnwOMVFjluc0bPzQbkArsw==",
"requires": {}
},
"@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=="
"integrity": "sha512-K52o7B1zkP4vaVy3z4ZwHn+tQy6KlXtedj1skLg+796ImwH2GYS5z6MFOTfKzBO2hLncUzLco/s0C5PLCD6SDw==",
"requires": {}
},
"@tiptap/extension-strike": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.12.tgz",
"integrity": "sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g=="
"integrity": "sha512-HlhrzIjYUT8oCH9nYzEL2QTTn8d1ECnVhKvzAe6x41xk31PjLMHTUy8aYjeQEkWZOWZ34tiTmslV1ce6R3Dt8g==",
"requires": {}
},
"@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=="
"integrity": "sha512-tb1jysEvf4SIiXwEOgDTXiyrG39RVNHvn/zsGMg5wy5t9qUp9m1k7kKYTH084ktuKDAPQonCcpn3hwc+ngTFzg==",
"requires": {}
},
"@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=="
"integrity": "sha512-ek6L+DNsrjiJieArlgTvQt1VfJ56d8V19WAPW/ciRhq88YRlTEY9nSO3QuUCSUO1nGmE5OWQpgrsiW/XZbONVw==",
"requires": {}
},
"@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=="
"integrity": "sha512-q/DuKZ4j1ycRfuFdb9rBJ3MglGNxlM2BQ1csScX/BrVIsAQI5B8sdzy1BrIlepQ6DRu4DCzHcKMI8u4/edUSWA==",
"requires": {}
},
"@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=="
"integrity": "sha512-hextcfVTdwX8G7s8Q/V6LW2aUhGvPgu1dfV+kVVO42AFHxG+6PIkDOUuHphGajG3Nrs129bjMDWb8jphj38dUg==",
"requires": {}
},
"@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=="
"integrity": "sha512-a4WZ5Z7gqQ/QlK8cK2d1ONYdma/J5+yH/0SNtQhkfELoS45GsLJh89OyKO0W0FnY6Mg0RoH1FsoBD+cqm0yazA==",
"requires": {}
},
"@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=="
"integrity": "sha512-0kPr+zngQC1YQRcU6+Fl3CpIW/SdJhVJ5qOLpQleXrLPdjmZQd3Z1DXvOSDphYjXCowGPCxeUa++6bo7IoEMJw==",
"requires": {}
},
"@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=="
"integrity": "sha512-uqrDTO4JwukZUt40GQdvB6S+oDhdp4cKNPMi0sbteWziQugkSMLlkYvxU0Hfb/YeziaWWwFI7ssPu/hahyk6dQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-BUpYlEWK+Q3kw9KIiOqvhd0tUPhMcOf1+fJmCkluJok+okAxMbP1umAtCEQ3QkoCwLr+vpHJov7h3yi9+dwgeQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-rCNUd505p/PXwU9Jgxo4ZJv4A3cIBAyAqlx/dtcY6cjztCQuXJhuQILPhjGhBTOLEEL4kW2wQtqzCmb7O8i2jg==",
"requires": {}
},
"@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=="
"integrity": "sha512-siMlwrkgVrAxxgmZn8GOc75J7UZi2CVrP9vDHkUPPyKm/fjssYekXwGCEk4Vswii1BbOh2gt+MDsRkeYRGyDlQ==",
"requires": {}
},
"@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=="
"integrity": "sha512-nfjWXX0JSRHLcscfiMESh+RN+Z7bG8nio/C9+8yQASM90VxU9f8oKgF8HnnSYsSrD4lLf44Q6XjmB7aMVUuikg==",
"requires": {}
},
"@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=="
"integrity": "sha512-NwwdhFT8gDD0VUNLQx85yFBhP9a8qg8GPuxlGzAP/lPTV8Ubh3vSeQ5N9k2ZF/vHlEvnugzeVCbmYn7wf8vn1g==",
"requires": {}
},
"@tiptap/pm": {
"version": "2.1.12",
@@ -4968,6 +5009,14 @@
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true
},
"async-mutex": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
"integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
"requires": {
"tslib": "^2.4.0"
}
},
"babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
@@ -5790,7 +5839,8 @@
}
},
"prosemirror-codemark": {
"version": "0.4.2"
"version": "0.4.2",
"requires": {}
},
"prosemirror-collab": {
"version": "1.3.0",
@@ -5938,26 +5988,26 @@
}
},
"re-resizable": {
"version": "6.9.9"
"version": "6.9.9",
"requires": {}
},
"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"
"version": "5.6.1",
"requires": {}
},
"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",
@@ -6044,7 +6094,6 @@
"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"
@@ -6237,8 +6286,7 @@
"tslib": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
"dev": true
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
},
"type-detect": {
"version": "4.0.8",
@@ -6272,7 +6320,8 @@
}
},
"use-sync-external-store": {
"version": "1.2.0"
"version": "1.2.0",
"requires": {}
},
"util-deprecate": {
"version": "1.0.2",

View File

@@ -34,6 +34,7 @@
"@tiptap/extension-underline": "2.1.12",
"@tiptap/pm": "2.1.12",
"@tiptap/starter-kit": "2.1.12",
"async-mutex": "^0.4.0",
"clipboard-polyfill": "4.0.0",
"detect-indent": "^7.0.0",
"entities": "^4.5.0",

View File

@@ -43,7 +43,7 @@ export const AnimatedImage = motion(Image);
export function ImageComponent(
props: SelectionBasedReactNodeViewProps<
ImageAttributes & ImageAlignmentOptions
Partial<ImageAttributes & ImageAlignmentOptions>
>
) {
const { editor, node, selected } = props;
@@ -57,7 +57,8 @@ export function ImageComponent(
height,
textDirection,
hash,
aspectRatio
aspectRatio,
mime
} = node.attrs;
const float = isMobile ? false : node.attrs.float;
@@ -67,6 +68,7 @@ export function ImageComponent(
const imageRef = useRef<HTMLImageElement>(null);
const downloadOptions = useToolbarStore((store) => store.downloadOptions);
const isReadonly = !editor.current?.isEditable;
const isSVG = !!mime && mime.includes("/svg");
const relativeHeight = aspectRatio
? editor.view.dom.clientWidth / aspectRatio
: undefined;
@@ -87,6 +89,7 @@ export function ImageComponent(
? "start"
: "end",
position: "relative",
mt: isSVG ? `24px` : 0,
":hover .drag-handle, :active .drag-handle": {
opacity: 1
}
@@ -139,7 +142,8 @@ export function ImageComponent(
top: -40,
right: 0,
mb: 2,
alignItems: "end"
alignItems: "end",
zIndex: 999
}}
>
<ToolbarGroup
@@ -184,7 +188,31 @@ export function ImageComponent(
}}
/>
)}
{isSVG ? (
<Box
sx={{
width: "100%",
display: editor.isEditable ? "flex" : "none",
position: "absolute",
top: -24,
height: 24,
justifyContent: "end",
p: "small",
bg: editor.isEditable
? "var(--background-secondary)"
: "transparent",
borderTopLeftRadius: "default",
borderTopRightRadius: "default",
borderColor: selected ? "border" : "var(--border-secondary)",
cursor: "pointer",
":hover": {
borderColor: "border"
}
}}
></Box>
) : null}
<AnimatedImage
as={isSVG ? "object" : "img"}
initial={{ opacity: 0 }}
animate={{ opacity: bloburl || src ? 1 : 0 }}
transition={{ duration: 0.5, ease: "easeIn" }}
@@ -192,11 +220,20 @@ export function ImageComponent(
ref={imageRef}
alt={alt}
crossOrigin="anonymous"
src={
toBlobURL("", hash) ||
bloburl ||
corsify(src, downloadOptions?.corsHost)
}
{...(isSVG
? {
data:
toBlobURL("", hash) ||
bloburl ||
corsify(src, downloadOptions?.corsHost),
type: mime
}
: {
src:
toBlobURL("", hash) ||
bloburl ||
corsify(src, downloadOptions?.corsHost)
})}
title={title}
sx={{
objectFit: "contain",
@@ -207,37 +244,47 @@ export function ImageComponent(
: "2px solid transparent !important",
borderRadius: "default"
}}
onDoubleClick={() =>
editor.current?.commands.previewAttachment(node.attrs)
}
onLoad={async () => {
onDoubleClick={() => {
const { hash, filename, mime, size } = node.attrs;
if (!!hash && !!filename && !!mime && !!size)
editor.current?.commands.previewAttachment({
hash,
filename,
mime,
size
});
}}
onLoad={async function onLoad() {
if (!imageRef.current) return;
const { clientHeight, clientWidth } = imageRef.current;
if (!isDataUrl(src) && canParse(src)) {
const { url, size, blob, mimeType } = await downloadImage(
src,
downloadOptions
);
editor.current?.commands.updateImage(
{ src, hash },
{
src: await toDataURL(blob),
bloburl: url,
size: size,
mime: mimeType,
aspectRatio:
!height && !width && !aspectRatio
? clientWidth / clientHeight
: undefined
}
if (src && !isDataUrl(src) && canParse(src)) {
const image = await downloadImage(src, downloadOptions);
if (!image) return;
const { url, size, blob, mimeType } = image;
const dataurl = await toDataURL(blob);
await editor.threadsafe((editor) =>
editor.commands.updateImage(
{ src, hash },
{
src: dataurl,
bloburl: url,
size: size,
mime: mimeType,
aspectRatio:
!height && !width && !aspectRatio
? clientWidth / clientHeight
: undefined
}
)
);
} else if (!height && !width && !aspectRatio) {
editor.current?.commands.updateImage(
{ src, hash },
{
aspectRatio: clientWidth / clientHeight
}
await editor.threadsafe((editor) =>
editor.commands.updateImage(
{ src, hash },
{
aspectRatio: clientWidth / clientHeight
}
)
);
}
}}

View File

@@ -49,10 +49,9 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) {
setError(undefined);
try {
const { blob, size, mimeType } = await downloadImage(
url,
downloadOptions
);
const image = await downloadImage(url, downloadOptions);
if (!image) return;
const { blob, size, mimeType } = image;
onInsert({ src: await toDataURL(blob), size, mime: mimeType });
} catch (e) {
if (e instanceof Error) setError(e.message);

View File

@@ -18,10 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { UnionCommands, Editor as TiptapEditor } from "@tiptap/core";
import { Mutex } from "async-mutex";
export type PermissionRequestEvent = CustomEvent<{ id: keyof UnionCommands }>;
export class Editor extends TiptapEditor {
private mutex: Mutex = new Mutex();
/**
* Use this to get the latest instance of the editor.
* This is required to reduce unnecessary rerenders of
@@ -45,4 +47,15 @@ export class Editor extends TiptapEditor {
return this.current;
}
/**
* Performs editor state changes in a thread-safe manner using a mutex
* ensuring that all changes are applied sequentially. Use this when
* you are getting `RangeError: Applying a mismatched transaction` errors.
*/
threadsafe(callback: (editor: TiptapEditor) => void) {
return this.mutex.runExclusive(() =>
this.current ? callback(this.current) : void 0
);
}
}

View File

@@ -17,6 +17,8 @@ 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 DataURL from "@notesnook/core/dist/utils/dataurl";
export type DownloadOptions = {
corsHost: string;
};
@@ -58,14 +60,17 @@ const UTITypes: Record<string, string> = {
"public.heifs": "image/heif-sequence"
};
export function corsify(url: string, host?: string) {
export function corsify(url?: string, host?: string) {
if (host && url && !url.startsWith("blob:") && !isDataUrl(url))
return `${host}/${url}`;
return url;
}
export async function downloadImage(url: string, options?: DownloadOptions) {
const response = await fetch(corsify(url, options?.corsHost), {
const corsifiedURL = corsify(url, options?.corsHost);
if (!corsifiedURL) return;
const response = await fetch(corsifiedURL, {
mode: "cors",
credentials: "omit",
cache: "force-cache"
@@ -119,8 +124,10 @@ export function toBlobURL(dataurl: string, id?: string) {
if (id && OBJECT_URL_CACHE[id]) return OBJECT_URL_CACHE[id];
if (!isDataUrl(dataurl)) return;
const { data, mime } = DataURL.toObject(dataurl); //.split(",");
if (!data || !mime) return;
const objectURL = URL.createObjectURL(
new Blob([Buffer.from(dataurl.split(",")[1], "base64")])
new Blob([Buffer.from(data, "base64")], { type: mime })
);
if (id) OBJECT_URL_CACHE[id] = objectURL;