Compare commits

...

29 Commits

Author SHA1 Message Date
Ammar Ahmed
070c90362a Merge pull request #8782 from streetwriters/fix-plans-showing-price
Fix prices on github release and open checkout link in browser
2025-10-17 09:58:29 +05:00
Ammar Ahmed
2685f60409 Merge pull request #8781 from streetwriters/mobile-release-3.3.3
mobile: release v3.3.3
2025-10-17 09:10:51 +05:00
Abdullah Atta
1427958eca Merge pull request #8457 from 01zulfi/editor/tasklist-checklist-styling
editor: improve checklist styling
2025-10-16 15:01:24 +05:00
Abdullah Atta
436e09c255 editor: fix text direction not working in check lists 2025-10-16 14:50:13 +05:00
Abdullah Atta
cc9b2dd5b0 editor: add back line through for checked items 2025-10-16 14:45:40 +05:00
Abdullah Atta
0ba25419f3 editor: use single li element for simple checklists 2025-10-16 14:43:51 +05:00
Abdullah Atta
9238de4cb4 Merge pull request #8772 from 01zulfi/editor/fix-images-in-outline-lists
editor: fix inserting images in outline lists
2025-10-16 11:48:34 +05:00
01zulfi
2d5fe1058c editor: add test for code block in outline list
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-16 10:46:31 +05:00
01zulfi
b01d1fc87e editor: improve checklist styling
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-16 09:28:02 +05:00
Abdullah Atta
9f4c0a4c9a editor: move outline list item indicator to li from first child 2025-10-16 09:23:19 +05:00
Abdullah Atta
9eebda86c2 editor: fix enter/tab not working in block nodes inside outline list item 2025-10-16 09:22:49 +05:00
Abdullah Atta
d43e856fea editor: add support for all block types in outline list item 2025-10-16 09:22:15 +05:00
Abdullah Atta
7af1596038 editor: remove custom inline image -> block image migration 2025-10-16 09:21:45 +05:00
01zulfi
652f6eab0b editor: fix inserting images in outline lists
* inserting images in outline list was splitting the outline list into two, this commit fixes it

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-15 11:14:24 +05:00
Abdullah Atta
9a1adf2065 Merge pull request #8767 from streetwriters/chore/update-electron-to-37
Update electron to v37
2025-10-13 14:12:01 +05:00
Abdullah Atta
f10b258f58 desktop: update electron to v37 2025-10-13 14:10:52 +05:00
Ammar Ahmed
641eb28ce7 mobile: open web purchase in browser. 2025-10-13 11:33:48 +05:00
Ammar Ahmed
2c59a5f6f9 mobile: fix plan price not showing properly on gh release 2025-10-13 09:30:48 +05:00
Abdullah Atta
e5ad405a23 Merge pull request #8754 from streetwriters/release/3.3.1
Release v3.3.1
2025-10-10 18:12:40 +05:00
Abdullah Atta
cd1b78d8df ci: fix desktop builds 2025-10-10 17:53:41 +05:00
Abdullah Atta
3b1ca46945 web: fix desktop tests 2025-10-10 16:23:11 +05:00
Abdullah Atta
96c1636f88 web: bump version to 3.3.1 2025-10-10 15:54:29 +05:00
Abdullah Atta
9ff4757ed6 Merge pull request #8753 from streetwriters/fix/make-table-with-adjustable
Make table width adjustable
2025-10-10 15:52:26 +05:00
Abdullah Atta
17c1e77f54 editor: make table width adjustable 2025-10-10 15:47:05 +05:00
Abdullah Atta
187a2a7262 Merge pull request #8735 from 01zulfi/editor/restrict-pasting-premium-features
editor: restrict pasting premium features
2025-10-10 15:38:11 +05:00
Abdullah Atta
4621230549 Merge pull request #8751 from streetwriters/fix/discount-price-on-checkout
Fix discounted price in checkout
2025-10-10 15:33:42 +05:00
Abdullah Atta
d7cb3317a2 web: fix discounted price in checkout 2025-10-10 15:09:41 +05:00
01zulfi
6d81f26864 editor: restrict pasting premium features
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-10-08 13:31:24 +05:00
Ammar Ahmed
105fb0fc69 mobile: release v3.3.3 2025-10-06 13:13:42 +05:00
32 changed files with 1243 additions and 292 deletions

View File

@@ -268,7 +268,6 @@ jobs:
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build snap
if: inputs.publish-snap
@@ -336,7 +335,6 @@ jobs:
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
# - name: Build snap
# if: inputs.publish-snap

View File

@@ -67,7 +67,6 @@ jobs:
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build app
run: |
@@ -111,7 +110,6 @@ jobs:
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build app
run: |
@@ -161,7 +159,6 @@ jobs:
- name: Build Electron wrapper
run: npm run tx @notesnook/desktop:release
working-directory: ./apps/desktop
- name: Build app
run: |

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.0",
"version": "3.3.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -47,7 +47,7 @@
"@types/node": "22.15.3",
"@types/yargs": "^17.0.33",
"chokidar": "^4.0.3",
"electron": "^36.0.0",
"electron": "^37.0.0",
"electron-builder": "^26.0.12",
"esbuild": "0.21.5",
"node-abi": "^4.5.0",

View File

@@ -919,7 +919,6 @@ const ReviewItem = (props: {
</View>
);
};
const PricingPlanCard = ({
plan,
pricingPlans,
@@ -932,7 +931,6 @@ const PricingPlanCard = ({
setStep: (step: number) => void;
}) => {
const { colors } = useThemeColors();
const isFreePlan = plan.id === "free";
const [regionalDiscount, setRegionaDiscount] = useState<SKUResponse>();
const { width } = useWindowDimensions();
const isTablet = width > 600;
@@ -996,8 +994,8 @@ const PricingPlanCard = ({
: "monthly"
}`
: pricingPlans.isGithubRelease
? (WebPlan?.period as string)
: (product?.productId as string)
? (WebPlan?.period as string)
: (product?.productId as string)
);
setStep(Steps.buy);
}}
@@ -1110,24 +1108,21 @@ const PricingPlanCard = ({
) : (
<View>
<Paragraph size={AppFontSize.lg}>
{isFreePlan
? "0.00"
: price ||
`${WebPlan?.price?.currency} ${WebPlan?.price?.gross}`}{" "}
{price || `${WebPlan?.price?.currency} ${WebPlan?.price?.gross}`}{" "}
<Paragraph>/{strings.month()}</Paragraph>
</Paragraph>
{isFreePlan || !product ? null : (
{!product && !WebPlan ? null : (
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
{annualBilling
? strings.billedAnnually(
pricingPlans?.getStandardPrice(
product as RNIap.Subscription
(product as RNIap.Subscription) || (WebPlan as Plan)
) as string
)
: strings.billedMonthly(
pricingPlans?.getStandardPrice(
product as RNIap.Subscription
(product as RNIap.Subscription) || (WebPlan as Plan)
) as string
)}
</Paragraph>

View File

@@ -21,7 +21,13 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import dayjs from "dayjs";
import React, { useEffect, useState } from "react";
import { ScrollView, Text, TouchableOpacity, View } from "react-native";
import {
Linking,
ScrollView,
Text,
TouchableOpacity,
View
} from "react-native";
import Config from "react-native-config";
import * as RNIap from "react-native-iap";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -60,27 +66,17 @@ export const BuyPlan = (props: {
return checkoutUrl ? (
<View
style={{
flex: 1
flex: 1,
justifyContent: "center",
alignItems: "center",
gap: DefaultAppStyles.GAP_VERTICAL
}}
>
<WebView
source={{
uri: checkoutUrl
}}
onMessage={(message) => {
try {
const data = JSON.parse(message.nativeEvent.data);
if (data.success) {
pricingPlans.finish();
}
} catch (e) {}
}}
domStorageEnabled
javaScriptEnabled
cacheEnabled
enabled
style={{
flex: 1
<Paragraph>{strings.finishPurchaseInBrowser()}</Paragraph>
<Button
title={strings.goBack()}
onPress={() => {
setCheckoutUrl(undefined);
}}
/>
</View>
@@ -245,12 +241,14 @@ export const BuyPlan = (props: {
}
onPress={async () => {
if (isGithubRelease) {
setCheckoutUrl(
await db.subscriptions.checkoutUrl(
(pricingPlans.selectedProduct as Plan).plan,
(pricingPlans.selectedProduct as Plan).period
)
const url = await db.subscriptions.checkoutUrl(
(pricingPlans.selectedProduct as Plan).plan,
(pricingPlans.selectedProduct as Plan).period
);
if (url) {
setCheckoutUrl(url);
Linking.openURL(url);
}
return;
}

View File

@@ -1 +1 @@
GITHUB_RELEASE=false
GITHUB_RELEASE=true

View File

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

View File

@@ -152,7 +152,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.streetwriters.notesnook" />
<data android:scheme="notesnook" />
</intent-filter>
</activity>

View File

@@ -1089,7 +1089,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1163,7 +1163,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1194,7 +1194,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1268,7 +1268,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1427,7 +1427,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1439,7 +1439,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1470,7 +1470,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1483,7 +1483,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1513,7 +1513,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1594,7 +1594,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1625,7 +1625,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2150;
CURRENT_PROJECT_VERSION = 2151;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1707,7 +1707,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = 3.3.2;
MARKETING_VERSION = 3.3.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.3.0",
"version": "3.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.3.0",
"version": "3.3.1",
"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": "3.3.0",
"version": "3.3.1",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -50,7 +50,6 @@ const TRIAL_PERIODS: Record<Period, number> = {
};
export function toPricingInfo(plan: Plan, user: User | undefined): PricingInfo {
console.log("TO PRICING INFO", plan);
return {
country: plan.country,
period: plan.period,
@@ -58,9 +57,9 @@ export function toPricingInfo(plan: Plan, user: User | undefined): PricingInfo {
currency: plan.currency,
id: plan.id,
period: plan.period,
subtotal: formatPrice(plan.price.gross, plan.currency),
subtotal: formatPrice(plan.price.net, plan.currency),
tax: formatPrice(plan.price.tax, plan.currency),
total: formatPrice(plan.price.net, plan.currency),
total: formatPrice(plan.price.gross, plan.currency),
trial_period: isTrialAvailableForPlan(plan.plan, user)
? {
frequency: TRIAL_PERIODS[plan.period]
@@ -74,9 +73,9 @@ export function toPricingInfo(plan: Plan, user: User | undefined): PricingInfo {
currency: plan.currency,
id: plan.id,
period: plan.period,
subtotal: formatPrice(plan.price.gross, plan.currency),
subtotal: formatPrice(plan.price.net, plan.currency),
tax: formatPrice(plan.price.tax, plan.currency),
total: formatPrice(plan.price.net, plan.currency)
total: formatPrice(plan.price.gross, plan.currency)
}
: undefined
};

View File

@@ -245,7 +245,7 @@ function getPricingInfo(
id: price.price_id,
period: options.period,
subtotal: formatPrice(
data.recurring_totals.subtotal,
data.recurring_totals.subtotal - data.recurring_totals.discount,
data.currency_code
),
total: formatPrice(data.recurring_totals.total, data.currency_code),
@@ -256,7 +256,10 @@ function getPricingInfo(
currency: data.currency_code,
id: price.price_id,
period: options.period,
subtotal: formatPrice(totals.subtotal, data.currency_code),
subtotal: formatPrice(
totals.subtotal - totals.discount,
data.currency_code
),
total: formatPrice(totals.total, data.currency_code),
tax: formatPrice(totals.tax, data.currency_code),
trial_period: price.trial_period

View File

@@ -119,7 +119,7 @@ function Checkout() {
useEffect(() => {
if (currentStep === 2) {
var event = EV.subscribe(EVENTS.userSubscriptionUpdated, () => {
const event = EV.subscribe(EVENTS.userSubscriptionUpdated, () => {
hardNavigate("/notes#/welcome");
});
return () => {

View File

@@ -0,0 +1,5 @@
- We have launches new pricing plans, Essential, Pro and Believer
- Free users can now upload attachments and create private vault
- Minor bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -17,8 +17,14 @@ 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 { keybindings } from "@notesnook/common";
import { KeyboardShortcutCommand, mergeAttributes, Node } from "@tiptap/core";
import {
findParentNodeClosestToPos,
KeyboardShortcutCommand,
mergeAttributes,
Node
} from "@tiptap/core";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
import { CheckList } from "../check-list/check-list";
export interface CheckListItemOptions {
onReadOnlyChecked?: (node: ProseMirrorNode, checked: boolean) => boolean;
@@ -97,94 +103,76 @@ export const CheckListItem = Node.create<CheckListItemOptions>({
addNodeView() {
return ({ node, getPos, editor }) => {
const listItem = document.createElement("li");
const checkboxWrapper = document.createElement("label");
const checkboxStyler = document.createElement("span");
const checkbox = document.createElement("input");
const content = document.createElement("div");
const li = document.createElement("li");
if (node.attrs.checked) li.classList.add("checked");
else li.classList.remove("checked");
checkboxWrapper.contentEditable = "false";
checkbox.type = "checkbox";
function onClick(e: MouseEvent | TouchEvent) {
if (e instanceof MouseEvent && e.button !== 0) return;
if (!(e.target instanceof HTMLElement)) return;
checkbox.addEventListener("mousedown", (event) => {
if (globalThis.keyboardShown) {
event.preventDefault();
}
});
const pos = typeof getPos === "function" ? getPos() : 0;
if (typeof pos !== "number") return;
const resolvedPos = editor.state.doc.resolve(pos);
checkbox.addEventListener("change", (event) => {
event.preventDefault();
// if the editor isnt editable and we don't have a handler for
// readonly checks we have to undo the latest change
if (!editor.isEditable && !this.options.onReadOnlyChecked) {
checkbox.checked = !checkbox.checked;
const { x, y, right } = li.getBoundingClientRect();
return;
const clientX =
e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
const clientY =
e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
const hitArea = { width: 40, height: 40 };
const isRtl =
e.target.dir === "rtl" ||
findParentNodeClosestToPos(
resolvedPos,
(node) => !!node.attrs.textDirection
)?.node.attrs.textDirection === "rtl";
let xStart = clientX >= x - hitArea.width;
let xEnd = clientX <= x;
const yStart = clientY >= y;
const yEnd = clientY <= y + hitArea.height;
if (isRtl) {
xEnd = clientX <= right + hitArea.width;
xStart = clientX >= right;
}
const { checked } = event.target as any;
if (editor.isEditable && typeof getPos === "function") {
editor
.chain()
.command(({ tr }) => {
const position = getPos();
const currentNode = tr.doc.nodeAt(position);
tr.setNodeMarkup(position, undefined, {
...currentNode?.attrs,
checked
});
return true;
})
.run();
if (xStart && xEnd && yStart && yEnd) {
e.preventDefault();
editor.commands.command(({ tr }) => {
tr.setNodeAttribute(
pos,
"checked",
!li.classList.contains("checked")
);
return true;
});
}
if (!editor.isEditable && this.options.onReadOnlyChecked) {
// Reset state if onReadOnlyChecked returns false
if (!this.options.onReadOnlyChecked(node, checked)) {
checkbox.checked = !checkbox.checked;
}
}
});
if (node.attrs.checked) {
checkbox.setAttribute("checked", "checked");
}
checkboxWrapper.append(checkbox, checkboxStyler);
listItem.append(checkboxWrapper, content);
li.onmousedown = onClick;
li.ontouchstart = onClick;
return {
dom: listItem,
contentDOM: content,
dom: li,
contentDOM: li,
update: (updatedNode) => {
if (updatedNode.type !== this.type) {
return false;
}
const isNested = updatedNode.lastChild?.type.name === CheckList.name;
listItem.dataset.checked = updatedNode.attrs.checked;
if (updatedNode.attrs.checked) {
checkbox.setAttribute("checked", "checked");
} else {
checkbox.removeAttribute("checked");
}
if (updatedNode.attrs.checked) li.classList.add("checked");
else li.classList.remove("checked");
return true;
}
};
};
}
// addInputRules() {
// return [
// wrappingInputRule({
// find: inputRegex,
// type: this.type,
// getAttributes: (match) => ({
// checked: match[match.length - 1] === "x"
// })
// })
// ];
// }
});

View File

@@ -42,7 +42,7 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
convertGoogleDocsChecklist(dom);
formatCodeblocks(dom);
convertBrToSingleSpacedParagraphs(dom);
removeImages(dom);
removePremiumFeatures(dom);
removeBlockId(dom);
}
return super.parseSlice(dom, options);
@@ -53,7 +53,7 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
convertGoogleDocsChecklist(dom);
formatCodeblocks(dom);
convertBrToSingleSpacedParagraphs(dom);
removeImages(dom);
removePremiumFeatures(dom);
removeBlockId(dom);
}
return super.parse(dom, options);
@@ -154,11 +154,29 @@ export function convertGoogleDocsChecklist(dom: HTMLElement | Document) {
}
}
export function removeImages(dom: HTMLElement | Document) {
let canInsertImages: boolean | null = null;
for (const img of dom.querySelectorAll(`img`)) {
canInsertImages = canInsertImages ?? hasPermission("insertImage");
if (!canInsertImages) img.remove();
const premiumFeatures = [
{
selector: "div.callout",
permission: "setCallout"
},
{
selector: 'ul[data-type="outlineList"]',
permission: "toggleOutlineList"
},
{
selector: "ul.checklist",
permission: "toggleTaskList"
}
] as const;
function removePremiumFeatures(dom: HTMLElement | Document) {
for (const feature of premiumFeatures) {
const elements = dom.querySelectorAll(feature.selector);
if (elements.length === 0) continue;
if (!hasPermission(feature.permission)) {
elements.forEach((element) => element.remove());
}
}
}

View File

@@ -0,0 +1,171 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`migration > inline image in outline list 1`] = `
{
"content": [
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 1",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "hello",
"type": "text",
},
],
"type": "paragraph",
},
{
"attrs": {
"align": undefined,
"aspectRatio": 1,
"filename": undefined,
"hash": undefined,
"height": null,
"mime": undefined,
"progress": 0,
"size": undefined,
"src": "image.png",
"type": "image",
"width": null,
},
"type": "image",
},
{
"content": [
{
"text": "world",
"type": "text",
},
],
"type": "paragraph",
},
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 2",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 3",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 4",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "doc",
}
`;
exports[`migration > inline image in paragraph 1`] = `
{
"content": [
{
"content": [
{
"text": "hello",
"type": "text",
},
],
"type": "paragraph",
},
{
"attrs": {
"align": undefined,
"aspectRatio": 1,
"filename": undefined,
"hash": undefined,
"height": null,
"mime": undefined,
"progress": 0,
"size": undefined,
"src": "image.png",
"type": "image",
"width": null,
},
"type": "image",
},
{
"content": [
{
"text": "world",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "doc",
}
`;

View File

@@ -0,0 +1,75 @@
/*
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/>.
*/
import {
createEditor,
h,
p,
outlineList,
outlineListItem
} from "../../../../test-utils/index.js";
import { test, expect, describe } from "vitest";
import { ImageNode } from "../index.js";
import { OutlineList } from "../../outline-list/outline-list.js";
import { OutlineListItem } from "../../outline-list-item/outline-list-item.js";
describe("migration", () => {
test(`inline image in paragraph`, async () => {
const el = p(["hello", h("img", [], { src: "image.png" }), "world"]);
const {
builder: { image },
editor
} = createEditor({
initialContent: el.outerHTML,
extensions: {
image: ImageNode.configure({})
}
});
expect(editor.getJSON()).toMatchSnapshot();
});
test(`inline image in outline list`, async () => {
const el = outlineList(
outlineListItem(["item 1"]),
outlineListItem(
["hello", h("img", [], { src: "image.png" }), "world"],
outlineList(
outlineListItem(["sub item 2"]),
outlineListItem(["sub item 3"])
)
),
outlineListItem(["item 4"])
);
const {
builder: { image },
editor
} = createEditor({
initialContent: el.outerHTML,
extensions: {
outlineList: OutlineList,
outlineListItem: OutlineListItem,
image: ImageNode
}
});
expect(editor.getJSON()).toMatchSnapshot();
});
});

View File

@@ -131,39 +131,6 @@ export const ImageNode = Node.create<ImageOptions>({
getAttrs(node) {
if (node.querySelectorAll("img").length <= 0) return false;
return null;
},
getContent: (dom, schema) => {
const wrapper = document.createElement("div");
let buffer = "";
const flushBuffer = () => {
if (buffer.trim().length > 0) {
const pEl = document.createElement("p");
pEl.innerHTML = buffer;
wrapper.appendChild(pEl);
buffer = "";
}
};
for (const child of dom.childNodes) {
if (
child.nodeType === globalThis.Node.ELEMENT_NODE &&
(child as HTMLElement).tagName === "IMG"
) {
flushBuffer();
wrapper.appendChild(child);
} else {
if (child.nodeType === globalThis.Node.ELEMENT_NODE) {
buffer += (child as HTMLElement).outerHTML;
} else if (child.nodeType === globalThis.Node.TEXT_NODE) {
buffer += child.textContent;
}
}
}
flushBuffer();
const parser = DOMParser.fromSchema(schema);
return parser.parse(wrapper).content;
}
},
{

View File

@@ -0,0 +1,130 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`outline list item > code block in outline list item 1`] = `
{
"content": [
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 1",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "hello",
"type": "text",
},
],
"type": "paragraph",
},
{
"attrs": {
"caretPosition": undefined,
"id": "codeblock-test-id-123456",
"indentLength": 2,
"indentType": "space",
"language": null,
"lines": [],
},
"content": [
{
"text": "const x = 1;",
"type": "text",
},
],
"type": "codeblock",
},
{
"content": [
{
"text": "world",
"type": "text",
},
],
"type": "paragraph",
},
{
"content": [
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 2",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "sub item 3",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "outlineListItem",
},
{
"attrs": {
"collapsed": false,
},
"content": [
{
"content": [
{
"text": "item 4",
"type": "text",
},
],
"type": "paragraph",
},
],
"type": "outlineListItem",
},
],
"type": "outlineList",
},
],
"type": "doc",
}
`;

View File

@@ -0,0 +1,73 @@
/*
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/>.
*/
import {
createEditor,
h,
li,
outlineList,
outlineListItem
} from "../../../../test-utils/index.js";
import { test, expect, describe, beforeAll, vi } from "vitest";
import { OutlineList } from "../../outline-list/outline-list.js";
import { OutlineListItem } from "../outline-list-item.js";
import { CodeBlock } from "../../code-block/code-block.js";
describe("outline list item", () => {
beforeAll(() => {
vi.mock("nanoid", () => ({
nanoid: () => "test-id-123456"
}));
});
test(`code block in outline list item`, async () => {
const subList = outlineList(
outlineListItem(["sub item 2"]),
outlineListItem(["sub item 3"])
);
const listItemWithCodeBlock = li(
[
h("p", ["hello"]),
h("pre", [h("code", ["const x = 1;"])]),
h("p", ["world"]),
subList
],
{ "data-type": "outlineListItem" }
);
const el = outlineList(
outlineListItem(["item 1"]),
listItemWithCodeBlock,
outlineListItem(["item 4"])
);
const {
builder: { codeBlock },
editor
} = createEditor({
initialContent: el.outerHTML,
extensions: {
outlineList: OutlineList,
outlineListItem: OutlineListItem,
codeBlock: CodeBlock
}
});
expect(editor.getJSON()).toMatchSnapshot();
});
});

View File

@@ -25,6 +25,7 @@ import {
import { findParentNodeOfTypeClosestToPos } from "../../utils/prosemirror.js";
import { OutlineList } from "../outline-list/outline-list.js";
import { keybindings, tiptapKeys } from "@notesnook/common";
import { Paragraph } from "../paragraph/paragraph.js";
export interface ListItemOptions {
HTMLAttributes: Record<string, unknown>;
@@ -52,13 +53,14 @@ export const OutlineListItem = Node.create<ListItemOptions>({
};
},
content: "paragraph+ list?",
content: "block+",
defining: true,
parseHTML() {
return [
{
priority: 100,
tag: `li[data-type="${this.name}"]`
}
];
@@ -92,21 +94,37 @@ export const OutlineListItem = Node.create<ListItemOptions>({
return true;
});
},
Enter: () => {
// const subList = findSublist(editor, this.type);
// if (!subList) return this.editor.commands.splitListItem(this.name);
// const { isCollapsed, subListPos } = subList;
// if (isCollapsed) {
// return this.editor.commands.toggleOutlineCollapse(subListPos, false);
// }
Enter: ({ editor }) => {
const { $anchor } = editor.state.selection;
if (
$anchor.parent.type.name !== Paragraph.name ||
($anchor.parent.type.name === Paragraph.name &&
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
)
return false;
return this.editor.commands.splitListItem(this.name);
},
Tab: () => this.editor.commands.sinkListItem(this.name),
[keybindings.liftListItem.keys]: () =>
this.editor.commands.liftListItem(this.name)
Tab: ({ editor }) => {
const { $anchor } = editor.state.selection;
if (
$anchor.parent.type.name !== Paragraph.name ||
($anchor.parent.type.name === Paragraph.name &&
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
)
return false;
return this.editor.commands.sinkListItem(this.name);
},
[keybindings.liftListItem.keys]: ({ editor }) => {
const { $anchor } = editor.state.selection;
if (
$anchor.parent.type.name !== Paragraph.name ||
($anchor.parent.type.name === Paragraph.name &&
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
)
return false;
return this.editor.commands.liftListItem(this.name);
}
};
},
@@ -124,7 +142,7 @@ export const OutlineListItem = Node.create<ListItemOptions>({
function onClick(e: MouseEvent | TouchEvent) {
if (e instanceof MouseEvent && e.button !== 0) return;
if (!(e.target instanceof HTMLParagraphElement)) return;
if (!(e.target instanceof HTMLElement)) return;
if (!li.classList.contains("nested")) return;
const pos = typeof getPos === "function" ? getPos() : 0;

View File

@@ -69,10 +69,7 @@ export function TableComponent(props: ReactNodeViewProps) {
/>
</DesktopOnly>
<SimpleBar autoHide>
<Box
dir={textDirection}
sx={{ "& table": { width: "100% !important" } }}
>
<Box dir={textDirection}>
<table
ref={(ref) => {
forwardRef?.(ref);

View File

@@ -30,6 +30,7 @@ const TEXT_DIRECTION_TYPES = [
"orderedList",
"bulletList",
"outlineList",
"checkList",
"taskList",
"table",
"blockquote",

View File

@@ -76,6 +76,14 @@
margin-bottom: 5px;
}
.ProseMirror li:last-of-type {
margin-bottom: 0px;
}
.ProseMirror li:first-of-type {
margin-top: 5px;
}
.ProseMirror ul.tasklist-content-wrapper {
padding-left: 0px;
}
@@ -536,11 +544,11 @@ p > *::selection {
.outline-list li.collapsed .outline-list {
display: none;
}
.outline-list li > :first-child {
.outline-list li {
position: relative;
}
.outline-list > li > :first-child::before {
.outline-list > li::before {
position: absolute;
top: 0px;
cursor: pointer;
@@ -560,13 +568,13 @@ p > *::selection {
left: -22px;
}
.outline-list li:not(.nested) > :first-child::before {
.outline-list li:not(.nested)::before {
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM4ODg4ODgiIGQ9Ik0xMiAyQTEwIDEwIDAgMCAwIDIgMTJhMTAgMTAgMCAwIDAgMTAgMTBhMTAgMTAgMCAwIDAgMTAtMTBBMTAgMTAgMCAwIDAgMTIgMloiLz48L3N2Zz4=)
no-repeat 50% 50%;
scale: 0.4;
}
.outline-list li.collapsed > :first-child::before {
.outline-list li.collapsed::before {
transform: rotate(-90deg);
}
@@ -638,40 +646,71 @@ p > *::selection {
transform: rotate(90deg);
}
.simple-checklist[dir="rtl"] li::after {
left: unset;
right: -24px;
}
.simple-checklist[dir="rtl"] li.checked::before {
left: unset;
right: -22px;
}
[dir="rtl"] .taskItemTools { right: unset; left: 0 }
/* Check list */
.ProseMirror ul.simple-checklist {
list-style: none;
padding: 0;
margin-block: 0px !important;
padding-inline: 0px !important;
padding-inline-start: 24px !important;
}
.ProseMirror ul.simple-checklist > li {
display: flex;
.ProseMirror li.nested > ul.simple-checklist {
padding-inline-start: 15px !important;
}
.ProseMirror ul.simple-checklist > li input {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
height: 1rem;
width: 1rem;
accent-color: var(--accent);
.simple-checklist li {
position: relative;
}
.ProseMirror ul.simple-checklist > li > div {
flex: 1 1 auto;
.simple-checklist > li::after {
position: absolute;
top: 0px;
cursor: pointer;
content: "";
background-size: 18px;
width: 14px;
height: 14px;
border: 2px solid var(--icon);
border-radius: 5px;
left: -24px;
}
@media screen and (max-width: 480px) {
.ProseMirror ul.simple-checklist > li > input {
height: 21px;
width: 21px;
}
.simple-checklist > li.checked::after {
border: 2px solid var(--accent);
}
.ProseMirror ul.simple-checklist > li > div {
margin-top: 2px;
}
.simple-checklist > li.checked::before {
position: absolute;
top: 2px;
cursor: pointer;
content: "";
background-size: 18px;
width: 14px;
height: 14px;
left: -22px;
background-color: var(--accent);
mask: url(data:image/svg+xml;base64,ICA8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0yMSw3TDksMTlMMy41LDEzLjVMNC45MSwxMi4wOUw5LDE2LjE3TDE5LjU5LDUuNTlMMjEsN1oiLz4KICA8L3N2Zz4K)
no-repeat 50% 50%;
mask-size: cover;
}
.simple-checklist > li.checked > p {
opacity: 0.8;
text-decoration-line: line-through;
}
/* Callout */

View File

@@ -92,3 +92,19 @@ export const p = elem("p");
export function text(text: string) {
return document.createTextNode(text);
}
export function outlineList(...children: HTMLLIElement[]) {
return ul(children, { "data-type": "outlineList" });
}
export function outlineListItem(
paragraphChildren: (string | HTMLElement)[],
subList?: HTMLUListElement
) {
const children: HTMLElement[] = [h("p", paragraphChildren)];
if (subList) children.push(subList);
return li(children, {
"data-type": "outlineListItem"
});
}

View File

@@ -2842,6 +2842,10 @@ msgstr "Filter attachments by filename, type or hash"
msgid "Filter languages"
msgstr "Filter languages"
#: src/strings.ts:2594
msgid "Finish your purchase in the browser."
msgstr "Finish your purchase in the browser."
#: src/strings.ts:1668
msgid "Fix it"
msgstr "Fix it"
@@ -3028,6 +3032,10 @@ msgstr "Getting recovery codes"
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr "GNU GENERAL PUBLIC LICENSE Version 3"
#: src/strings.ts:2595
msgid "Go back"
msgstr "Go back"
#: src/strings.ts:2445
msgid "Go back in tab"
msgstr "Go back in tab"

View File

@@ -2831,6 +2831,10 @@ msgstr ""
msgid "Filter languages"
msgstr ""
#: src/strings.ts:2594
msgid "Finish your purchase in the browser."
msgstr ""
#: src/strings.ts:1668
msgid "Fix it"
msgstr ""
@@ -3010,6 +3014,10 @@ msgstr ""
msgid "GNU GENERAL PUBLIC LICENSE Version 3"
msgstr ""
#: src/strings.ts:2595
msgid "Go back"
msgstr ""
#: src/strings.ts:2445
msgid "Go back in tab"
msgstr ""

View File

@@ -2590,5 +2590,7 @@ Use this if changes from other devices are not appearing on this device. This wi
t`You can change your subscription plan from the web app`,
announcement: () => t`ANNOUNCEMENT`,
cannotChangePlan: () =>
t`Your current subscription does not allow changing plans`
t`Your current subscription does not allow changing plans`,
finishPurchaseInBrowser: () => t`Finish your purchase in the browser.`,
goBack: () => t`Go back`
};