mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 02:58:34 +02:00
Compare commits
15 Commits
editor/com
...
fix-subscr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
144febb3cc | ||
|
|
22b2d50139 | ||
|
|
7ada9cacda | ||
|
|
852f1ec31a | ||
|
|
09fd5d8642 | ||
|
|
ea63e6a325 | ||
|
|
f7914c57c7 | ||
|
|
fc55cbf3d7 | ||
|
|
a5f2d39b07 | ||
|
|
e5395bf05a | ||
|
|
30466e4a76 | ||
|
|
499ab7cdfe | ||
|
|
2e49893841 | ||
|
|
c295e1c9be | ||
|
|
6dd16f6791 |
@@ -992,7 +992,7 @@ const PricingPlanCard = ({
|
||||
PremiumService.get() &&
|
||||
(pricingPlans?.user?.subscription?.productId ===
|
||||
(product as RNIap.Subscription)?.productId ||
|
||||
pricingPlans?.user?.subscription?.productId.startsWith(
|
||||
pricingPlans?.user?.subscription?.productId?.startsWith(
|
||||
(product as RNIap.Subscription)?.productId
|
||||
));
|
||||
pricingPlans?.selectPlan(
|
||||
|
||||
@@ -352,7 +352,7 @@ const ProductItem = (props: {
|
||||
props.pricingPlans.isSubscribed() &&
|
||||
(props.pricingPlans.user?.subscription?.productId ===
|
||||
(product as RNIap.Subscription)?.productId ||
|
||||
props.pricingPlans.user?.subscription?.productId.startsWith(
|
||||
props.pricingPlans.user?.subscription?.productId?.startsWith(
|
||||
(product as RNIap.Subscription)?.productId
|
||||
) ||
|
||||
props.pricingPlans.user?.subscription?.productId ===
|
||||
|
||||
@@ -103,7 +103,7 @@ export function PlanLimits() {
|
||||
|
||||
if (
|
||||
user?.subscription.plan !== SubscriptionPlan.FREE &&
|
||||
user?.subscription.productId.includes("5year")
|
||||
user?.subscription.productId?.includes("5year")
|
||||
) {
|
||||
ToastManager.show({
|
||||
message:
|
||||
|
||||
@@ -660,9 +660,25 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
|
||||
}
|
||||
}
|
||||
|
||||
function isSubscribedToPlan(planId: string) {
|
||||
if (!PremiumService.get()) return false;
|
||||
return user?.subscription?.productId?.includes(planId);
|
||||
}
|
||||
|
||||
function isSubscribedToProduct(productId: string) {
|
||||
if (!PremiumService.get()) return false;
|
||||
return (
|
||||
user?.subscription?.productId &&
|
||||
(user?.subscription?.productId === productId ||
|
||||
user?.subscription?.productId?.startsWith(productId))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
currentPlan: pricingPlans.find((p) => p.id === currentPlan),
|
||||
pricingPlans: plans,
|
||||
isSubscribedToPlan,
|
||||
isSubscribedToProduct,
|
||||
getStandardPrice: getLocalizedPrice,
|
||||
loadingPlans,
|
||||
loading,
|
||||
|
||||
@@ -62,6 +62,7 @@ import { fluidTabsRef } from "../../utils/global-refs";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { i18n } from "@lingui/core";
|
||||
import { useVaultStatus } from "../../hooks/use-vault-status";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
const style: ViewStyle = {
|
||||
height: "100%",
|
||||
@@ -231,7 +232,6 @@ const useLockedNoteHandler = () => {
|
||||
const unlockWithBiometrics = async () => {
|
||||
try {
|
||||
if (!tabRef.current?.session?.noteLocked || !tabRef.current) return;
|
||||
console.log("Trying to unlock with biometrics...");
|
||||
const credentials = await BiometricService.getCredentials(
|
||||
"Unlock note",
|
||||
"Unlock note to open it in editor."
|
||||
@@ -320,12 +320,16 @@ const useLockedNoteHandler = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const unlock = () => {
|
||||
const unlock = (forced?: boolean) => {
|
||||
const isMovedAway =
|
||||
useSettingStore.getState().deviceMode !== "mobile"
|
||||
? false
|
||||
: editorState().movedAway;
|
||||
if (
|
||||
(tabRef.current?.session?.locked,
|
||||
tabRef.current?.session?.locked &&
|
||||
useTabStore.getState().biometryAvailable &&
|
||||
useTabStore.getState().biometryEnrolled &&
|
||||
!editorState().movedAway)
|
||||
useTabStore.getState().biometryEnrolled &&
|
||||
(!isMovedAway || forced)
|
||||
) {
|
||||
setTimeout(() => {
|
||||
unlockWithBiometrics();
|
||||
@@ -344,13 +348,15 @@ const useLockedNoteHandler = () => {
|
||||
const subs = [
|
||||
eSubscribeEvent(eUnlockNote, unlock),
|
||||
eSubscribeEvent(eUnlockWithBiometrics, () => {
|
||||
unlock();
|
||||
unlock(true);
|
||||
}),
|
||||
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||
];
|
||||
|
||||
if (
|
||||
tabRef.current?.session?.locked &&
|
||||
fluidTabsRef.current?.page() === "editor"
|
||||
(fluidTabsRef.current?.page() === "editor" ||
|
||||
useSettingStore.getState().deviceMode !== "mobile")
|
||||
) {
|
||||
unlock();
|
||||
}
|
||||
|
||||
@@ -118,7 +118,8 @@ export const settingsGroups: SettingSection[] = [
|
||||
if (
|
||||
(user.subscription?.provider === SubscriptionProvider.GOOGLE ||
|
||||
user.subscription?.provider === SubscriptionProvider.APPLE) &&
|
||||
isCurrentPlatform
|
||||
isCurrentPlatform &&
|
||||
user?.subscription?.productId
|
||||
) {
|
||||
RNIap.deepLinkToSubscriptions({
|
||||
sku: user?.subscription.productId
|
||||
@@ -142,13 +143,18 @@ export const settingsGroups: SettingSection[] = [
|
||||
"dddd, MMMM D, YYYY h:mm A"
|
||||
);
|
||||
|
||||
if (user.subscription?.plan !== SubscriptionPlan.FREE) {
|
||||
if (
|
||||
user.subscription?.plan !== SubscriptionPlan.FREE &&
|
||||
user.subscription?.productId
|
||||
) {
|
||||
const status = user.subscription?.status;
|
||||
return status === SubscriptionStatus.TRIAL
|
||||
? strings.trialEndsOn(
|
||||
dayjs(user?.subscription?.start)
|
||||
.add(
|
||||
user?.subscription?.productId.includes("monthly") ? 7 : 14
|
||||
user?.subscription?.productId?.includes("monthly")
|
||||
? 7
|
||||
: 14
|
||||
)
|
||||
.format("dddd, MMMM D, YYYY h:mm A")
|
||||
)
|
||||
|
||||
4
apps/mobile/package-lock.json
generated
4
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.7",
|
||||
"version": "3.3.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.7",
|
||||
"version": "3.3.9",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -30,13 +30,18 @@ export async function handleDrop(
|
||||
item:
|
||||
| ItemReference
|
||||
| Context
|
||||
| { type: "trash" | "notebooks" | "favorites" | undefined }
|
||||
| { type: "trash" | "notebooks" | "favorites" | "archive" | undefined }
|
||||
) {
|
||||
if (!item.type) return;
|
||||
|
||||
const noteIds = getDragData(dataTransfer, "note");
|
||||
const notebookIds = getDragData(dataTransfer, "notebook");
|
||||
const { setColor, favorite, delete: trashNotes } = useNoteStore.getState();
|
||||
const {
|
||||
setColor,
|
||||
favorite,
|
||||
delete: trashNotes,
|
||||
archive
|
||||
} = useNoteStore.getState();
|
||||
switch (item.type) {
|
||||
case "notebook":
|
||||
if (noteIds.length > 0) {
|
||||
@@ -83,5 +88,8 @@ export async function handleDrop(
|
||||
await useNoteStore.getState().refresh();
|
||||
}
|
||||
break;
|
||||
case "archive":
|
||||
archive(true, ...noteIds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,11 @@ export async function exportNotes(
|
||||
const { createZipStream } = await import("../utils/streams/zip-stream");
|
||||
|
||||
const errors: Error[] = [];
|
||||
const exportStream = new ExportStream(report, (e) => errors.push(e));
|
||||
const exportStream = new ExportStream(
|
||||
report,
|
||||
(e) => errors.push(e),
|
||||
await notes.count()
|
||||
);
|
||||
await fromAsyncIterator(
|
||||
_exportNotes(notes, { format, unlockVault: Vault.unlockVault })
|
||||
)
|
||||
|
||||
@@ -226,11 +226,13 @@ export default function TabsView() {
|
||||
<TableOfContents sessionId={activeSession.id} />
|
||||
</Pane>
|
||||
) : null}
|
||||
{arePropertiesVisible && activeSession && (
|
||||
<Pane id="properties-pane" initialSize={250} minSize={250}>
|
||||
<Properties sessionId={activeSession.id} />
|
||||
</Pane>
|
||||
)}
|
||||
{arePropertiesVisible &&
|
||||
activeSession &&
|
||||
activeSession.type !== "new" && (
|
||||
<Pane id="properties-pane" initialSize={250} minSize={250}>
|
||||
<Properties sessionId={activeSession.id} />
|
||||
</Pane>
|
||||
)}
|
||||
</SplitPane>
|
||||
<DropZone overlayRef={overlayRef} />
|
||||
</ScopedThemeProvider>
|
||||
|
||||
@@ -118,7 +118,7 @@ function countCharacters(text: string) {
|
||||
function countParagraphs(fragment: Fragment) {
|
||||
let count = 0;
|
||||
fragment.nodesBetween(0, fragment.size, (node) => {
|
||||
if (node.type.name === "paragraph") {
|
||||
if (node.type.name === "paragraph" && node.content.size > 0) {
|
||||
count++;
|
||||
}
|
||||
return true;
|
||||
@@ -737,7 +737,7 @@ function toIEditor(editor: Editor): IEditor {
|
||||
function getSelectedParagraphs(editor: Editor, selection: Selection): number {
|
||||
let count = 0;
|
||||
editor.state.doc.nodesBetween(selection.from, selection.to, (node) => {
|
||||
if (node.type.name === "paragraph") {
|
||||
if (node.type.name === "paragraph" && node.content.size > 0) {
|
||||
count++;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -506,6 +506,8 @@ function RouteItem({
|
||||
? "trash"
|
||||
: item.path === "/favorites"
|
||||
? "favorites"
|
||||
: item.path == "/archive"
|
||||
? "archive"
|
||||
: undefined
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -147,7 +147,7 @@ export const BuyDialog = DialogManager.register(function BuyDialog(
|
||||
</Text>
|
||||
</Flex>
|
||||
<PlansList
|
||||
selectedPlan={user?.subscription.productId}
|
||||
selectedPlan={user?.subscription?.productId}
|
||||
recommendedPlan={SubscriptionPlan.PRO}
|
||||
onPlanSelected={(plan) => selectPlan(plan)}
|
||||
/>
|
||||
|
||||
@@ -42,7 +42,7 @@ import { useStore as useUserStore } from "../../stores/user-store";
|
||||
import { getCurrencySymbol } from "../../common/currencies";
|
||||
|
||||
type PlansListProps = {
|
||||
selectedPlan?: string;
|
||||
selectedPlan?: string | null;
|
||||
loadAllPlans?: boolean;
|
||||
ignoreTrial?: boolean;
|
||||
recommendedPlan?: SubscriptionPlan;
|
||||
|
||||
@@ -28,8 +28,13 @@ export class ExportStream extends TransformStream<
|
||||
> {
|
||||
progress = 0;
|
||||
constructor(
|
||||
report: (progress: { text: string; current?: number }) => void,
|
||||
handleError: (error: Error) => void
|
||||
report: (progress: {
|
||||
text: string;
|
||||
current?: number;
|
||||
total?: number;
|
||||
}) => void,
|
||||
handleError: (error: Error) => void,
|
||||
totalItems?: number
|
||||
) {
|
||||
super({
|
||||
transform: async (item, controller) => {
|
||||
@@ -69,7 +74,8 @@ export class ExportStream extends TransformStream<
|
||||
controller.enqueue(item);
|
||||
report({
|
||||
current: this.progress++,
|
||||
text: `Exporting note: ${item.path}`
|
||||
text: `Exporting note: ${item.path}`,
|
||||
total: totalItems
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,72 +9,71 @@ The following keyboard shortcuts will help you navigate Notesnook faster.
|
||||
|
||||
### General
|
||||
|
||||
| Description | Web | Windows/Linux | Mac |
|
||||
| --- | --- | --- | --- |
|
||||
| Search in notes list view if editor is not focused | Ctrl F | Ctrl F | ⌘ F |
|
||||
| Settings | Ctrl , | Ctrl , | ⌘ , |
|
||||
| Keyboard shortcuts | Ctrl / | Ctrl / | ⌘ / |
|
||||
| New note | - | Ctrl N | ⌘ N |
|
||||
| Description | Web | Windows/Linux | Mac |
|
||||
| -------------------------------------------------- | ------ | ------------- | --- |
|
||||
| Search in notes list view if editor is not focused | Ctrl F | Ctrl F | ⌘ F |
|
||||
| Settings | Ctrl , | Ctrl , | ⌘ , |
|
||||
| Keyboard shortcuts | Ctrl / | Ctrl / | ⌘ / |
|
||||
| New note | - | Ctrl N | ⌘ N |
|
||||
|
||||
### Navigation
|
||||
|
||||
| Description | Web | Windows/Linux | Mac |
|
||||
| --- | --- | --- | --- |
|
||||
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
|
||||
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
|
||||
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
|
||||
| Quick open | Ctrl P | Ctrl P | ⌘ P |
|
||||
| New tab | - | Ctrl T | ⌘ T |
|
||||
| Close active tab | - | Ctrl W | ⌘ W |
|
||||
| Close all tabs | - | Ctrl ⇧ W | ⌘ ⇧ W |
|
||||
| Description | Web | Windows/Linux | Mac |
|
||||
| ---------------- | ------------------------- | ------------- | ------- |
|
||||
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
|
||||
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
|
||||
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
|
||||
| Quick open | Ctrl P | Ctrl P | ⌘ P |
|
||||
| New tab | - | Ctrl T | ⌘ T |
|
||||
| Close active tab | - | Ctrl W | ⌘ W |
|
||||
| Close all tabs | - | Ctrl ⇧ W | ⌘ ⇧ W |
|
||||
|
||||
### Editor
|
||||
|
||||
| Description | Web | Windows/Linux | Mac |
|
||||
| --- | --- | --- | --- |
|
||||
| Add attachment | Ctrl ⇧ A | Ctrl ⇧ A | ⌘ ⇧ A |
|
||||
| Insert blockquote | Ctrl ⇧ B | Ctrl ⇧ B | ⌘ ⇧ B |
|
||||
| Toggle bold | Ctrl B | Ctrl B | ⌘ B |
|
||||
| Toggle bullet list | Ctrl ⇧ 8 | Ctrl ⇧ 8 | ⌘ ⇧ 8 |
|
||||
| Toggle check list | Ctrl ⇧ 9 | Ctrl ⇧ 9 | ⌘ ⇧ 9 |
|
||||
| Split list item | ↵ | ↵ | ↵ |
|
||||
| Lift list item | ⇧ Tab | ⇧ Tab | ⇧ Tab |
|
||||
| Sink list item | Ctrl ⇧ Down | Ctrl ⇧ Down | ⌘ ⇧ Down |
|
||||
| Toggle code | Ctrl E | Ctrl E | ⌘ E |
|
||||
| Toggle code block | Ctrl ⇧ C | Ctrl ⇧ C | ⌘ ⇧ C |
|
||||
| Insert date | Alt D | Alt D | ⌥ D |
|
||||
| Insert time | Alt T | Alt T | ⌥ T |
|
||||
| Insert date and time | Ctrl Alt D | Ctrl Alt D | ⌘ ⌥ D |
|
||||
| Insert date and time with timezone | Ctrl Alt Z | Ctrl Alt Z | ⌘ ⌥ Z |
|
||||
| Increase font size | Ctrl [ | Ctrl [ | ⌘ [ |
|
||||
| Decrease font size | Ctrl ] | Ctrl ] | ⌘ ] |
|
||||
| Insert paragraph | Ctrl Alt 0 | Ctrl Alt 0 | ⌘ ⌥ 0 |
|
||||
| Insert heading 1 | Ctrl Alt 1 | Ctrl Alt 1 | ⌘ ⌥ 1 |
|
||||
| Insert heading 2 | Ctrl Alt 2 | Ctrl Alt 2 | ⌘ ⌥ 2 |
|
||||
| Insert heading 3 | Ctrl Alt 3 | Ctrl Alt 3 | ⌘ ⌥ 3 |
|
||||
| Insert heading 4 | Ctrl Alt 4 | Ctrl Alt 4 | ⌘ ⌥ 4 |
|
||||
| Insert heading 5 | Ctrl Alt 5 | Ctrl Alt 5 | ⌘ ⌥ 5 |
|
||||
| Insert heading 6 | Ctrl Alt 6 | Ctrl Alt 6 | ⌘ ⌥ 6 |
|
||||
| Undo | Ctrl Z | Ctrl Z | ⌘ Z |
|
||||
| Redo | Ctrl ⇧ Z / Ctrl Y | Ctrl ⇧ Z / Ctrl Y | ⌘ ⇧ Z / ⌘ Y |
|
||||
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
|
||||
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
|
||||
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
|
||||
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
|
||||
| Insert link | Ctrl K | Ctrl K | ⌘ K |
|
||||
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
|
||||
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
|
||||
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
|
||||
| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space |
|
||||
| Open search | Ctrl F | Ctrl F | ⌘ F |
|
||||
| Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S |
|
||||
| Toggle subscript | Ctrl , | Ctrl , | ⌘ , |
|
||||
| Toggle superscript | Ctrl . | Ctrl . | ⌘ . |
|
||||
| Toggle task list | Ctrl ⇧ T | Ctrl ⇧ T | ⌘ ⇧ T |
|
||||
| Text align center | Ctrl ⇧ E | Ctrl ⇧ E | ⌘ ⇧ E |
|
||||
| Text align justify | Ctrl ⇧ J | Ctrl ⇧ J | ⌘ ⇧ J |
|
||||
| Text align left | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
|
||||
| Text align right | Ctrl ⇧ R | Ctrl ⇧ R | ⌘ ⇧ R |
|
||||
| Underline | Ctrl U | Ctrl U | ⌘ U |
|
||||
| Toggle highlight | Ctrl Alt H | Ctrl Alt H | ⌘ ⌥ H |
|
||||
| Insert comment | Ctrl Alt M | Ctrl Alt M | ⌘ ⌥ M |
|
||||
| Description | Web | Windows/Linux | Mac |
|
||||
| ---------------------------------- | ----------------- | ----------------- | ----------- |
|
||||
| Add attachment | Ctrl ⇧ A | Ctrl ⇧ A | ⌘ ⇧ A |
|
||||
| Insert blockquote | Ctrl ⇧ B | Ctrl ⇧ B | ⌘ ⇧ B |
|
||||
| Toggle bold | Ctrl B | Ctrl B | ⌘ B |
|
||||
| Toggle bullet list | Ctrl ⇧ 8 | Ctrl ⇧ 8 | ⌘ ⇧ 8 |
|
||||
| Toggle check list | Ctrl ⇧ 9 | Ctrl ⇧ 9 | ⌘ ⇧ 9 |
|
||||
| Split list item | ↵ | ↵ | ↵ |
|
||||
| Lift list item | ⇧ Tab | ⇧ Tab | ⇧ Tab |
|
||||
| Sink list item | Ctrl ⇧ Down | Ctrl ⇧ Down | ⌘ ⇧ Down |
|
||||
| Toggle code | Ctrl E | Ctrl E | ⌘ E |
|
||||
| Toggle code block | Ctrl ⇧ C | Ctrl ⇧ C | ⌘ ⇧ C |
|
||||
| Insert date | Alt D | Alt D | ⌥ D |
|
||||
| Insert time | Alt T | Alt T | ⌥ T |
|
||||
| Insert date and time | Ctrl Alt D | Ctrl Alt D | ⌘ ⌥ D |
|
||||
| Insert date and time with timezone | Ctrl Alt Z | Ctrl Alt Z | ⌘ ⌥ Z |
|
||||
| Increase font size | Ctrl [ | Ctrl [ | ⌘ [ |
|
||||
| Decrease font size | Ctrl ] | Ctrl ] | ⌘ ] |
|
||||
| Insert paragraph | Ctrl ⇧ 0 | Ctrl ⇧ 0 | ⌘ ⇧ 0 |
|
||||
| Insert heading 1 | Ctrl Alt 1 | Ctrl Alt 1 | ⌘ ⌥ 1 |
|
||||
| Insert heading 2 | Ctrl Alt 2 | Ctrl Alt 2 | ⌘ ⌥ 2 |
|
||||
| Insert heading 3 | Ctrl Alt 3 | Ctrl Alt 3 | ⌘ ⌥ 3 |
|
||||
| Insert heading 4 | Ctrl Alt 4 | Ctrl Alt 4 | ⌘ ⌥ 4 |
|
||||
| Insert heading 5 | Ctrl Alt 5 | Ctrl Alt 5 | ⌘ ⌥ 5 |
|
||||
| Insert heading 6 | Ctrl Alt 6 | Ctrl Alt 6 | ⌘ ⌥ 6 |
|
||||
| Undo | Ctrl Z | Ctrl Z | ⌘ Z |
|
||||
| Redo | Ctrl ⇧ Z / Ctrl Y | Ctrl ⇧ Z / Ctrl Y | ⌘ ⇧ Z / ⌘ Y |
|
||||
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
|
||||
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
|
||||
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
|
||||
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
|
||||
| Insert link | Ctrl K | Ctrl K | ⌘ K |
|
||||
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
|
||||
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
|
||||
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
|
||||
| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space |
|
||||
| Open search | Ctrl F | Ctrl F | ⌘ F |
|
||||
| Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S |
|
||||
| Toggle subscript | Ctrl , | Ctrl , | ⌘ , |
|
||||
| Toggle superscript | Ctrl . | Ctrl . | ⌘ . |
|
||||
| Toggle task list | Ctrl ⇧ T | Ctrl ⇧ T | ⌘ ⇧ T |
|
||||
| Text align center | Ctrl ⇧ E | Ctrl ⇧ E | ⌘ ⇧ E |
|
||||
| Text align justify | Ctrl ⇧ J | Ctrl ⇧ J | ⌘ ⇧ J |
|
||||
| Text align left | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
|
||||
| Text align right | Ctrl ⇧ R | Ctrl ⇧ R | ⌘ ⇧ R |
|
||||
| Underline | Ctrl U | Ctrl U | ⌘ U |
|
||||
| Toggle highlight | Ctrl Alt H | Ctrl Alt H | ⌘ ⌥ H |
|
||||
|
||||
@@ -394,12 +394,6 @@ export const tiptapKeys = {
|
||||
description: "Toggle highlight",
|
||||
category: "Editor",
|
||||
type: "tiptap"
|
||||
},
|
||||
insertComment: {
|
||||
keys: "Mod-Alt-m",
|
||||
description: "Insert comment",
|
||||
category: "Editor",
|
||||
type: "tiptap"
|
||||
}
|
||||
} satisfies Record<string, TipTapKey>;
|
||||
|
||||
|
||||
@@ -604,7 +604,7 @@ export type User = {
|
||||
cancelURL: string | null;
|
||||
expiry: number;
|
||||
trialExpiry?: number;
|
||||
productId: string;
|
||||
productId: string | null;
|
||||
provider: SubscriptionProvider;
|
||||
start: number;
|
||||
plan: SubscriptionPlan;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
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 { Mark, mergeAttributes } from "@tiptap/core";
|
||||
|
||||
interface CommentOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
export type CommentAttributes = {
|
||||
id: string;
|
||||
text: string;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
comment: {
|
||||
/**
|
||||
* Set a comment mark
|
||||
*/
|
||||
setComment: (attributes: CommentAttributes) => ReturnType;
|
||||
/**
|
||||
* Unset a comment mark
|
||||
*/
|
||||
unsetComment: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const Comment = Mark.create<CommentOptions>({
|
||||
name: "comment",
|
||||
|
||||
priority: 1000,
|
||||
|
||||
keepOnSplit: false,
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {
|
||||
class: "comment-highlight"
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
id: {
|
||||
default: null,
|
||||
parseHTML: (element) => element.getAttribute("data-comment-id"),
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.id) return {};
|
||||
return { "data-comment-id": attributes.id };
|
||||
}
|
||||
},
|
||||
text: {
|
||||
default: null,
|
||||
parseHTML: (element) => element.getAttribute("data-comment-text"),
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.text) return {};
|
||||
return { "data-comment-text": attributes.text };
|
||||
}
|
||||
},
|
||||
timestamp: {
|
||||
default: null,
|
||||
parseHTML: (element) => {
|
||||
const ts = element.getAttribute("data-comment-timestamp");
|
||||
return ts ? parseInt(ts, 10) : null;
|
||||
},
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.timestamp) return {};
|
||||
return { "data-comment-timestamp": attributes.timestamp.toString() };
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "span[data-comment-id]" }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"span",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
0
|
||||
];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setComment:
|
||||
(attributes) =>
|
||||
({ commands }) => {
|
||||
return commands.setMark(this.name, attributes);
|
||||
},
|
||||
unsetComment:
|
||||
() =>
|
||||
({ commands }) => {
|
||||
return commands.unsetMark(this.name);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
export * from "./comment.js";
|
||||
@@ -23,7 +23,6 @@ import { CodeBlock } from "../code-block/index.js";
|
||||
import { showLinkPopup } from "../../toolbar/popups/link-popup.js";
|
||||
import { isListActive } from "../../utils/list.js";
|
||||
import { tiptapKeys } from "@notesnook/common";
|
||||
import { showCommentPopup } from "../../toolbar/popups/comment-popup.js";
|
||||
|
||||
export const KeyMap = Extension.create({
|
||||
name: "key-map",
|
||||
@@ -72,10 +71,6 @@ export const KeyMap = Extension.create({
|
||||
[tiptapKeys.insertLink.keys]: ({ editor }) => {
|
||||
showLinkPopup(editor);
|
||||
return true;
|
||||
},
|
||||
[tiptapKeys.insertComment.keys]: ({ editor }) => {
|
||||
showCommentPopup(editor);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import CharacterCount from "@tiptap/extension-character-count";
|
||||
import Color from "@tiptap/extension-color";
|
||||
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
||||
import { Link, LinkAttributes } from "./extensions/link/index.js";
|
||||
import { Comment } from "./extensions/comment/index.js";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import Subscript from "@tiptap/extension-subscript";
|
||||
import Superscript from "@tiptap/extension-superscript";
|
||||
@@ -283,7 +282,6 @@ const useTiptap = (
|
||||
autolink: false,
|
||||
linkOnPaste: true
|
||||
}),
|
||||
Comment.configure({}),
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
allowTableNodeSelection: true,
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
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 { ToolbarGroup } from "../../components/toolbar-group.js";
|
||||
import { HoverPopupProps } from "./index.js";
|
||||
import { useHoverPopupContext } from "./context.js";
|
||||
import { ToolbarGroupDefinition } from "../../types.js";
|
||||
|
||||
const COMMENT_TOOLS: ToolbarGroupDefinition = ["viewComment"];
|
||||
|
||||
function CommentHoverPopup(props: HoverPopupProps) {
|
||||
const { editor } = props;
|
||||
const { selectedNode } = useHoverPopupContext();
|
||||
const { node } = selectedNode || {};
|
||||
|
||||
if (
|
||||
!node?.isText ||
|
||||
node.marks.length <= 0 ||
|
||||
!node.marks.some((mark) => mark.type.name === "comment")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarGroup
|
||||
force
|
||||
tools={COMMENT_TOOLS}
|
||||
groupId={"commentHoverTools"}
|
||||
editor={editor}
|
||||
sx={{
|
||||
bg: "background",
|
||||
boxShadow: "menu",
|
||||
borderRadius: "default",
|
||||
p: 1
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const CommentHoverPopupHandler = {
|
||||
isActive: (e: HTMLElement) => !!e.closest("[data-comment-id]"),
|
||||
popup: CommentHoverPopup,
|
||||
openOnClick: true
|
||||
};
|
||||
@@ -22,23 +22,13 @@ import { showPopup } from "../../../components/popup-presenter/index.js";
|
||||
import { Editor } from "../../../types.js";
|
||||
import { FloatingMenuProps } from "../types.js";
|
||||
import { LinkHoverPopupHandler } from "./link.js";
|
||||
import { CommentHoverPopupHandler } from "./comment.js";
|
||||
import { HoverPopupContextProvider } from "./context.js";
|
||||
|
||||
export type HoverPopupProps = {
|
||||
editor: Editor;
|
||||
};
|
||||
|
||||
type HoverPopupHandler = {
|
||||
isActive: (element: HTMLElement) => boolean;
|
||||
popup: (props: HoverPopupProps) => React.ReactNode;
|
||||
openOnClick?: boolean;
|
||||
};
|
||||
|
||||
const handlers: HoverPopupHandler[] = [
|
||||
LinkHoverPopupHandler,
|
||||
CommentHoverPopupHandler
|
||||
];
|
||||
const handlers = [LinkHoverPopupHandler];
|
||||
|
||||
const HOVER_TIMEOUT = 1000;
|
||||
|
||||
@@ -69,11 +59,29 @@ export function HoverPopupHandler(props: FloatingMenuProps) {
|
||||
|
||||
clearTimeout(hoverTimeoutId.current);
|
||||
}
|
||||
function onMouseOver(e: MouseEvent) {
|
||||
if (
|
||||
!e.target ||
|
||||
!(e.target instanceof HTMLElement) ||
|
||||
e.target.classList.contains("ProseMirror")
|
||||
)
|
||||
return;
|
||||
|
||||
const element = e.target;
|
||||
|
||||
if (activePopup.current) {
|
||||
const isOutsideEditor = !element.closest(".ProseMirror");
|
||||
const isInsidePopup = element.closest(".popup-presenter-portal");
|
||||
const isActiveElement = activePopup.current.element === element;
|
||||
if (isInsidePopup) return;
|
||||
|
||||
if (isOutsideEditor || !isActiveElement) {
|
||||
activePopup.current.hide();
|
||||
activePopup.current = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function showPopupForElement(
|
||||
element: HTMLElement,
|
||||
timeout: number = HOVER_TIMEOUT
|
||||
) {
|
||||
clearTimeout(hoverTimeoutId.current);
|
||||
|
||||
hoverTimeoutId.current = setTimeout(
|
||||
@@ -117,79 +125,20 @@ export function HoverPopupHandler(props: FloatingMenuProps) {
|
||||
align: "start",
|
||||
location: "top",
|
||||
isTargetAbsolute: true,
|
||||
yOffset: 0,
|
||||
yOffset: -10,
|
||||
xOffset: -30
|
||||
}
|
||||
});
|
||||
activePopup.current = { element, hide: hidePopup };
|
||||
},
|
||||
timeout,
|
||||
HOVER_TIMEOUT,
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
if (
|
||||
!e.target ||
|
||||
!(e.target instanceof HTMLElement) ||
|
||||
e.target.classList.contains("ProseMirror")
|
||||
)
|
||||
return;
|
||||
|
||||
const element = e.target;
|
||||
|
||||
const handler = handlers.find((h) => h.isActive(element));
|
||||
if (!handler?.openOnClick) return;
|
||||
|
||||
if (activePopup.current?.element === element) return;
|
||||
|
||||
if (activePopup.current) {
|
||||
activePopup.current.hide();
|
||||
activePopup.current = undefined;
|
||||
}
|
||||
|
||||
showPopupForElement(element, 0);
|
||||
}
|
||||
|
||||
function onMouseOver(e: MouseEvent) {
|
||||
if (
|
||||
!e.target ||
|
||||
!(e.target instanceof HTMLElement) ||
|
||||
e.target.classList.contains("ProseMirror")
|
||||
)
|
||||
return;
|
||||
|
||||
const element = e.target;
|
||||
|
||||
if (activePopup.current) {
|
||||
const isOutsideEditor = !element.closest(".ProseMirror");
|
||||
const isInsidePopup = element.closest(".popup-presenter-portal");
|
||||
const isActiveElement = activePopup.current.element === element;
|
||||
if (isInsidePopup) return;
|
||||
|
||||
if (isOutsideEditor) {
|
||||
activePopup.current.hide();
|
||||
activePopup.current = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isActiveElement) {
|
||||
activePopup.current.hide();
|
||||
activePopup.current = undefined;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showPopupForElement(element);
|
||||
}
|
||||
|
||||
window.addEventListener("mouseover", onMouseOver);
|
||||
window.addEventListener("click", onClick);
|
||||
window.addEventListener("contextmenu", onContextMenu);
|
||||
return () => {
|
||||
window.removeEventListener("mouseover", onMouseOver);
|
||||
window.removeEventListener("click", onClick);
|
||||
window.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
},
|
||||
|
||||
@@ -122,9 +122,7 @@ import {
|
||||
mdiCheckboxMultipleMarked,
|
||||
mdiMessageOutline,
|
||||
mdiVectorLink,
|
||||
mdiPinOutline,
|
||||
mdiCommentOutline,
|
||||
mdiCommentEditOutline
|
||||
mdiPinOutline
|
||||
} from "@mdi/js";
|
||||
|
||||
export const Icons = {
|
||||
@@ -256,8 +254,6 @@ export const Icons = {
|
||||
selectAllChecked: mdiCheckboxMultipleMarked,
|
||||
checkList: mdiCheckboxMarkedOutline,
|
||||
pin: mdiPinOutline,
|
||||
comment: mdiCommentOutline,
|
||||
commentEdit: mdiCommentEditOutline,
|
||||
none: ""
|
||||
};
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
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 { Textarea } from "@theme-ui/components";
|
||||
import { Flex } from "@theme-ui/components";
|
||||
import { useState } from "react";
|
||||
import { Popup } from "../components/popup.js";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { showPopup } from "../../components/popup-presenter/index.js";
|
||||
import { ResponsivePresenter } from "../../components/responsive/index.js";
|
||||
import { nanoid } from "nanoid";
|
||||
import { CommentAttributes } from "../../extensions/comment/comment.js";
|
||||
import { Editor } from "@tiptap/core";
|
||||
|
||||
export type CommentPopupProps = {
|
||||
onDone: (text: string) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function CommentPopup(props: CommentPopupProps) {
|
||||
const { onDone, onClose } = props;
|
||||
const [commentText, setCommentText] = useState("");
|
||||
|
||||
return (
|
||||
<Popup
|
||||
title={strings.addComment()}
|
||||
onClose={onClose}
|
||||
action={{
|
||||
title: strings.add(),
|
||||
disabled: !commentText.trim(),
|
||||
onClick: () => onDone(commentText)
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{ p: 1, flexDirection: "column", width: ["auto", 300] }}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter" && e.ctrlKey && commentText.trim()) {
|
||||
onDone(commentText);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Textarea
|
||||
autoFocus
|
||||
placeholder={strings.writeComment()}
|
||||
value={commentText}
|
||||
rows={2}
|
||||
onChange={(e) => setCommentText(e.target.value)}
|
||||
sx={{
|
||||
fontFamily: "body",
|
||||
fontSize: "body",
|
||||
resize: "vertical"
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export function showCommentPopup(editor: Editor) {
|
||||
if (!editor || editor.state.selection.empty || editor.isActive("comment")) {
|
||||
return;
|
||||
}
|
||||
|
||||
showPopup({
|
||||
popup: (hide) => (
|
||||
<ResponsivePresenter
|
||||
mobile="sheet"
|
||||
desktop="popup"
|
||||
position={{
|
||||
target:
|
||||
(document.querySelector("#tool-comment") as HTMLElement) ||
|
||||
undefined,
|
||||
isTargetAbsolute: true,
|
||||
location: "below",
|
||||
align: "center",
|
||||
yOffset: 5
|
||||
}}
|
||||
title={strings.addComment()}
|
||||
isOpen={true}
|
||||
items={[]}
|
||||
onClose={() => {
|
||||
hide();
|
||||
editor.commands.focus();
|
||||
}}
|
||||
focusOnRender={true}
|
||||
>
|
||||
<CommentPopup
|
||||
onClose={() => {
|
||||
hide();
|
||||
editor.commands.focus();
|
||||
}}
|
||||
onDone={(text: string) => {
|
||||
const commentId = `comment-${nanoid(8)}`;
|
||||
const attributes: CommentAttributes = {
|
||||
id: commentId,
|
||||
text,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
editor.commands.setComment(attributes);
|
||||
hide();
|
||||
editor.commands.focus();
|
||||
}}
|
||||
/>
|
||||
</ResponsivePresenter>
|
||||
),
|
||||
blocking: true,
|
||||
focusOnRender: true
|
||||
});
|
||||
}
|
||||
@@ -75,15 +75,6 @@ const tools: Record<ToolId, ToolDefinition> = {
|
||||
title: strings.linkSettings(),
|
||||
conditional: true
|
||||
},
|
||||
addComment: {
|
||||
icon: "comment",
|
||||
title: strings.addComment()
|
||||
},
|
||||
viewComment: {
|
||||
icon: "comment",
|
||||
title: strings.viewComment(),
|
||||
conditional: true
|
||||
},
|
||||
code: {
|
||||
icon: "code",
|
||||
title: strings.code()
|
||||
@@ -422,7 +413,7 @@ const defaultPresets: Record<"default" | "minimal", ToolbarDefinition> = {
|
||||
["fontSize"],
|
||||
["headings", "fontFamily"],
|
||||
["checkList", "numberedList", "bulletList"],
|
||||
["addLink", "addInternalLink", "addComment"],
|
||||
["addLink", "addInternalLink"],
|
||||
["alignment", "textDirection"],
|
||||
["clearformatting"]
|
||||
],
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
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 { ToolProps } from "../types.js";
|
||||
import { ToolButton } from "../components/tool-button.js";
|
||||
import { useRef, useState } from "react";
|
||||
import { ResponsivePresenter } from "../../components/responsive/index.js";
|
||||
import { CommentPopup } from "../popups/comment-popup.js";
|
||||
import { useRefValue } from "../../hooks/use-ref-value.js";
|
||||
import { findMark, selectionToOffset } from "../../utils/prosemirror.js";
|
||||
import { Box, Flex, Input, Text } from "@theme-ui/components";
|
||||
import { useHoverPopupContext } from "../floating-menus/hover-popup/context.js";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { CommentAttributes } from "../../extensions/comment/index.js";
|
||||
import { nanoid } from "nanoid";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
|
||||
export function AddComment(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const isActive = editor.isActive("comment");
|
||||
|
||||
if (isActive) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolButton
|
||||
{...props}
|
||||
buttonRef={buttonRef}
|
||||
onClick={() => setIsOpen(true)}
|
||||
toggled={isOpen}
|
||||
disabled={editor.state.selection.empty}
|
||||
/>
|
||||
<ResponsivePresenter
|
||||
mobile="sheet"
|
||||
desktop="popup"
|
||||
position={{
|
||||
target: buttonRef.current || undefined,
|
||||
isTargetAbsolute: true,
|
||||
location: "below",
|
||||
align: "center",
|
||||
yOffset: 5
|
||||
}}
|
||||
title={strings.addComment()}
|
||||
isOpen={isOpen}
|
||||
items={[]}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
editor.commands.focus();
|
||||
}}
|
||||
focusOnRender={false}
|
||||
>
|
||||
<CommentPopup
|
||||
onClose={() => setIsOpen(false)}
|
||||
onDone={(text: string) => {
|
||||
const commentId = `comment-${nanoid(8)}`;
|
||||
const attributes: CommentAttributes = {
|
||||
id: commentId,
|
||||
text,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
editor.commands.setComment(attributes);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
/>
|
||||
</ResponsivePresenter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ViewComment(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
const { selectedNode: _selectedNode, hide } = useHoverPopupContext();
|
||||
const selectedNode = useRefValue(
|
||||
_selectedNode || selectionToOffset(editor.state)
|
||||
);
|
||||
const { node } = selectedNode.current || {};
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const comment = node ? findMark(node, "comment") : null;
|
||||
const [commentAttributes, setCommentAttributes] = useState(
|
||||
comment ? (comment.attrs as CommentAttributes) : null
|
||||
);
|
||||
|
||||
if (!commentAttributes) return null;
|
||||
|
||||
function saveComment() {
|
||||
if (!commentAttributes) return null;
|
||||
|
||||
if (selectedNode.current) {
|
||||
editor.chain().focus().setTextSelection(selectedNode.current).run();
|
||||
}
|
||||
|
||||
const text = inputRef.current?.value || "";
|
||||
const attributes: CommentAttributes = {
|
||||
id: commentAttributes.id,
|
||||
text,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
editor.commands.setComment(attributes);
|
||||
setIsEditing(false);
|
||||
setCommentAttributes(attributes);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDirection: "column", gap: 1, px: 1 }}>
|
||||
<Flex sx={{ alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Text sx={{ fontSize: "subBody" }}>
|
||||
{getFormattedDate(commentAttributes.timestamp, "date-time")}
|
||||
</Text>
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<ToolButton
|
||||
icon={"delete"}
|
||||
title={strings.deleteComment()}
|
||||
iconSize={"small"}
|
||||
onClick={() => {
|
||||
if (_selectedNode)
|
||||
editor.commands.setTextSelection({
|
||||
from: _selectedNode.from,
|
||||
to: _selectedNode.to
|
||||
});
|
||||
editor.chain().focus().unsetComment().run();
|
||||
hide();
|
||||
}}
|
||||
/>
|
||||
{isEditing ? (
|
||||
<ToolButton
|
||||
icon={"save"}
|
||||
title={strings.saveComment()}
|
||||
iconSize={"small"}
|
||||
onClick={saveComment}
|
||||
/>
|
||||
) : (
|
||||
<ToolButton
|
||||
icon={"commentEdit"}
|
||||
title={strings.editComment()}
|
||||
iconSize={"small"}
|
||||
onClick={() => {
|
||||
if (editor.isEditable) {
|
||||
setIsEditing(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
sx={{
|
||||
minWidth: 200,
|
||||
maxWidth: 400,
|
||||
pb: 2
|
||||
}}
|
||||
>
|
||||
{isEditing ? (
|
||||
<Input
|
||||
autoFocus
|
||||
as={"textarea"}
|
||||
ref={inputRef}
|
||||
defaultValue={commentAttributes.text}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter" && e.ctrlKey) {
|
||||
saveComment();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setIsEditing(false);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
fontSize: "body",
|
||||
width: "100%",
|
||||
height: "fit-content"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word"
|
||||
}}
|
||||
>
|
||||
{commentAttributes.text}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -96,7 +96,6 @@ import {
|
||||
OpenLink,
|
||||
CopyLink
|
||||
} from "./link.js";
|
||||
import { AddComment, ViewComment } from "./comment.js";
|
||||
import {
|
||||
WebClipFullScreen,
|
||||
WebClipSettings,
|
||||
@@ -122,8 +121,6 @@ const tools = {
|
||||
copyLink: CopyLink,
|
||||
linkSettings: LinkSettings,
|
||||
openLink: OpenLink,
|
||||
addComment: AddComment,
|
||||
viewComment: ViewComment,
|
||||
insertBlock: InsertBlock,
|
||||
numberedList: NumberedList,
|
||||
bulletList: BulletList,
|
||||
|
||||
@@ -99,17 +99,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ProseMirror span.comment-highlight {
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dashed;
|
||||
text-decoration-color: var(--accent);
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
||||
.ProseMirror span.comment-highlight:hover {
|
||||
text-decoration-style: solid;
|
||||
}
|
||||
|
||||
.ProseMirror a[href^="nn://"]::before {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
@@ -718,6 +707,10 @@ p > *::selection {
|
||||
mask-size: cover;
|
||||
}
|
||||
|
||||
.simple-checklist > li.checked p {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Callout */
|
||||
.ProseMirror div.callout {
|
||||
padding: 15px;
|
||||
|
||||
@@ -699,10 +699,6 @@ msgstr "Add a tag"
|
||||
msgid "Add color"
|
||||
msgstr "Add color"
|
||||
|
||||
#: src/strings.ts:2609
|
||||
msgid "Add comment"
|
||||
msgstr "Add comment"
|
||||
|
||||
#: src/strings.ts:894
|
||||
msgid "Add notebook"
|
||||
msgstr "Add notebook"
|
||||
@@ -2105,10 +2101,6 @@ msgstr "Delete collapsed section"
|
||||
msgid "Delete column"
|
||||
msgstr "Delete column"
|
||||
|
||||
#: src/strings.ts:2612
|
||||
msgid "Delete comment"
|
||||
msgstr "Delete comment"
|
||||
|
||||
#: src/strings.ts:1313
|
||||
msgid "Delete group"
|
||||
msgstr "Delete group"
|
||||
@@ -2366,10 +2358,6 @@ msgstr "Easy access"
|
||||
msgid "Edit"
|
||||
msgstr "Edit"
|
||||
|
||||
#: src/strings.ts:2610
|
||||
msgid "Edit comment"
|
||||
msgstr "Edit comment"
|
||||
|
||||
#: src/strings.ts:526
|
||||
msgid "Edit internal link"
|
||||
msgstr "Edit internal link"
|
||||
@@ -5437,10 +5425,6 @@ msgstr "Save account recovery key"
|
||||
msgid "Save and continue"
|
||||
msgstr "Save and continue"
|
||||
|
||||
#: src/strings.ts:2614
|
||||
msgid "Save comment"
|
||||
msgstr "Save comment"
|
||||
|
||||
#: src/strings.ts:1047
|
||||
msgid "Save data recovery key"
|
||||
msgstr "Save data recovery key"
|
||||
@@ -7030,10 +7014,6 @@ msgstr "View all linked notebooks"
|
||||
msgid "View and share debug logs"
|
||||
msgstr "View and share debug logs"
|
||||
|
||||
#: src/strings.ts:2611
|
||||
msgid "View comment"
|
||||
msgstr "View comment"
|
||||
|
||||
#: src/strings.ts:1746
|
||||
msgid "View receipt"
|
||||
msgstr "View receipt"
|
||||
@@ -7167,10 +7147,6 @@ msgstr "Words"
|
||||
msgid "Work & Office"
|
||||
msgstr "Work & Office"
|
||||
|
||||
#: src/strings.ts:2613
|
||||
msgid "Write a comment..."
|
||||
msgstr "Write a comment..."
|
||||
|
||||
#: src/strings.ts:822
|
||||
msgid "Write notes with freedom, no spying, no tracking."
|
||||
msgstr "Write notes with freedom, no spying, no tracking."
|
||||
|
||||
@@ -699,10 +699,6 @@ msgstr ""
|
||||
msgid "Add color"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2609
|
||||
msgid "Add comment"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:894
|
||||
msgid "Add notebook"
|
||||
msgstr ""
|
||||
@@ -2094,10 +2090,6 @@ msgstr ""
|
||||
msgid "Delete column"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2612
|
||||
msgid "Delete comment"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:1313
|
||||
msgid "Delete group"
|
||||
msgstr ""
|
||||
@@ -2355,10 +2347,6 @@ msgstr ""
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2610
|
||||
msgid "Edit comment"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:526
|
||||
msgid "Edit internal link"
|
||||
msgstr ""
|
||||
@@ -5411,10 +5399,6 @@ msgstr ""
|
||||
msgid "Save and continue"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2614
|
||||
msgid "Save comment"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:1047
|
||||
msgid "Save data recovery key"
|
||||
msgstr ""
|
||||
@@ -6981,10 +6965,6 @@ msgstr ""
|
||||
msgid "View and share debug logs"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2611
|
||||
msgid "View comment"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:1746
|
||||
msgid "View receipt"
|
||||
msgstr ""
|
||||
@@ -7118,10 +7098,6 @@ msgstr ""
|
||||
msgid "Work & Office"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2613
|
||||
msgid "Write a comment..."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:822
|
||||
msgid "Write notes with freedom, no spying, no tracking."
|
||||
msgstr ""
|
||||
|
||||
@@ -2605,11 +2605,5 @@ Use this if changes from other devices are not appearing on this device. This wi
|
||||
clickToDirectlyClaimPromo: () =>
|
||||
t`Click here to directly claim the promotion.`,
|
||||
loginToUploadAttachments: () =>
|
||||
t`Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)`,
|
||||
addComment: () => t`Add comment`,
|
||||
editComment: () => t`Edit comment`,
|
||||
viewComment: () => t`View comment`,
|
||||
deleteComment: () => t`Delete comment`,
|
||||
writeComment: () => t`Write a comment...`,
|
||||
saveComment: () => t`Save comment`
|
||||
t`Login to upload attachments. [Read more](https://help.notesnook.com/faqs/login-to-upload-attachments)`
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user