WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,7 @@ packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/useConvertToMarkdownBanner.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,7 @@ packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteEditor/WarningBanner/useConvertToMarkdownBanner.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
Expand Down
25 changes: 9 additions & 16 deletions packages/app-desktop/gui/NoteEditor/NoteEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
import getResourceBaseUrl from './utils/getResourceBaseUrl';
import useInitialCursorLocation from './utils/useInitialCursorLocation';
import NotePositionService, { EditorCursorLocations } from '@joplin/lib/services/NotePositionService';
import useConvertToMarkdownBanner from '@joplin/lib/components/shared/NoteEditor/WarningBanner/useConvertToMarkdownBanner';

const debounce = require('debounce');

Expand Down Expand Up @@ -496,16 +497,12 @@ function NoteEditorContent(props: NoteEditorProps) {
setShowRevisions(false);
}, []);

const onBannerConvertItToMarkdown = useCallback(async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
if (!props.selectedNoteIds || props.selectedNoteIds.length === 0) return;
await CommandService.instance().execute('convertNoteToMarkdown', props.selectedNoteIds[0]);
}, [props.selectedNoteIds]);

const onHideBannerConvertItToMarkdown = async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
Setting.setValue('editor.enableHtmlToMarkdownBanner', false);
};
const convertToMarkdownBannerData = useConvertToMarkdownBanner({
note: formNote,
readOnly: isReadOnly,
dismissed: !props.enableHtmlToMarkdownBanner,
});

const onBannerResourceClick = useCallback(async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
Expand Down Expand Up @@ -652,20 +649,16 @@ function NoteEditorContent(props: NoteEditorProps) {
const theme = themeStyle(props.themeId);

function renderConvertHtmlToMarkdown(): React.ReactNode {
if (!props.enableHtmlToMarkdownBanner) return null;

const note = props.notes.find(n => n.id === props.selectedNoteIds[0]);
if (!note) return null;
if (note.markup_language !== MarkupLanguage.Html) return null;
if (!convertToMarkdownBannerData.enabled) return null;

return (
<div style={styles.resourceWatchBanner}>
<p style={styles.resourceWatchBannerLine}>
{_('This note is in HTML format. Convert it to Markdown to edit it more easily.')}
&nbsp;
<a href="#" style={styles.resourceWatchBannerAction} onClick={onBannerConvertItToMarkdown}>{`${_('Convert it')}`}</a>
<button className='link-button' style={styles.resourceWatchBannerAction} onClick={convertToMarkdownBannerData.convert.onPress}>{convertToMarkdownBannerData.convert.label}</button>
{' / '}
<a href="#" style={styles.resourceWatchBannerAction} onClick={onHideBannerConvertItToMarkdown}>{_('Don\'t show this message again')}</a>
<button className='link-button' style={styles.resourceWatchBannerAction} onClick={convertToMarkdownBannerData.dismiss.onPress}>{convertToMarkdownBannerData.dismiss.label}</button>
Comment on lines -666 to +661
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to link-buttons allows using the onPress callbacks directly (without the .preventDefaults).

</p>
</div>
);
Expand Down
39 changes: 39 additions & 0 deletions packages/app-mobile/components/NoteEditor/NoteEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { Store } from 'redux';
import { AppState } from '../../utils/types';
import { MarkupLanguage } from '@joplin/renderer';
import { EditorType } from './types';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import shim from '@joplin/lib/shim';

let store: Store<AppState>;
let registeredRuntime: RegisteredRuntime;
Expand Down Expand Up @@ -56,6 +59,7 @@ describe('NoteEditor', () => {
store = createMockReduxStore();
setupGlobalStore(store);
registeredRuntime = mockCommandRuntimes(store);
shim.showMessageBox = jest.fn();
});

afterEach(() => {
Expand Down Expand Up @@ -132,4 +136,39 @@ describe('NoteEditor', () => {

wrappedNoteEditor.unmount();
});

it('should show a warning banner when opening an HTML-format note', async () => {
const parent = await Folder.save({ title: 'Test' });
const note = await Note.save({
parent_id: parent.id, title: 'Test', body: '<p><strong>Test</strong></p>', markup_language: MarkupLanguage.Html,
});
const wrappedNoteEditor = render(
<TestProviderStack store={store}>
<NoteEditor
ref={undefined}
{...defaultEditorProps}
noteId={note.id}
markupLanguage={note.markup_language}
mode={EditorType.Markdown}
/>
</TestProviderStack>,
);

const warningBannerQuery = /This note is in HTML format. Convert it to Markdown to edit it more easily.*/;
const warning = screen.getByText(warningBannerQuery);
expect(warning).toBeVisible();

// Should convert it to Markdown
const convertButton = screen.getByRole('button', { name: 'Convert it' });
fireEvent.press(convertButton);

await waitFor(async () => {
const newNotes = await Note.previews(parent.id, { fields: ['title', 'body', 'id'] });
expect(newNotes).toMatchObject([
{ title: 'Test', body: '**Test**' },
]);
});

wrappedNoteEditor.unmount();
});
});
7 changes: 6 additions & 1 deletion packages/app-mobile/components/NoteEditor/NoteEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,12 @@ function NoteEditor(props: Props) {
/>
</View>

<WarningBanner editorType={props.mode}/>
<WarningBanner
editorType={props.mode}
noteId={props.noteId}
markupLanguage={props.markupLanguage}
readOnly={props.readOnly}
/>

<SearchPanel
editorSettings={editorSettings}
Expand Down
68 changes: 53 additions & 15 deletions packages/app-mobile/components/NoteEditor/WarningBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,79 @@ import { AppState } from '../../utils/types';
import { EditorType } from './types';
import { Banner } from 'react-native-paper';
import { useMemo } from 'react';
import useConvertToMarkdownBanner from '@joplin/lib/components/shared/NoteEditor/WarningBanner/useConvertToMarkdownBanner';
import { MarkupLanguage } from '@joplin/renderer/types';

interface Props {
editorType: EditorType;
richTextBannerDismissed: boolean;
convertToMarkdownBannerDismissed: boolean;

markupLanguage: MarkupLanguage;
noteId: string;
readOnly: boolean;
}

const useBanner = ({ editorType, readOnly, richTextBannerDismissed, convertToMarkdownBannerDismissed, noteId, markupLanguage }: Props) => {
const convertToMarkdownBanner = useConvertToMarkdownBanner({
note: { markup_language: markupLanguage, id: noteId },
dismissed: convertToMarkdownBannerDismissed,
readOnly,
});

return useMemo(() => {
if (editorType === EditorType.RichText && !richTextBannerDismissed) {
return {
label: _('This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.'),
actions: [
{
label: _('Read more'),
onPress: onRichTextReadMoreLinkClick,
},
{
label: _('Dismiss'),
accessibilityHint: _('Hides warning'),
onPress: onRichTextDismissLinkClick,
},
],
};
}

if (convertToMarkdownBanner.enabled) {
return {
label: convertToMarkdownBanner.label,
actions: [
convertToMarkdownBanner.dismiss,
convertToMarkdownBanner.convert,
],
};
}

return null;
}, [editorType, richTextBannerDismissed, convertToMarkdownBanner]);
};

const WarningBanner: React.FC<Props> = props => {
const actions = useMemo(() => [
{
label: _('Read more'),
onPress: onRichTextReadMoreLinkClick,
},
{
label: _('Dismiss'),
accessibilityHint: _('Hides warning'),
onPress: onRichTextDismissLinkClick,
},
], []);

if (props.editorType !== EditorType.RichText || props.richTextBannerDismissed) return null;
const banner = useBanner(props);

if (!banner) return null;
return (
<Banner
icon='alert-outline'
actions={actions}
actions={banner.actions}
// Avoid hiding with react-native-paper's "visible" prop to avoid potential accessibility issues
// related to how react-native-paper hides the banner.
visible={true}
>
{_('This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.')}
{banner.label}
</Banner>
);
};

export default connect((state: AppState) => {
return {
richTextBannerDismissed: state.settings.richTextBannerDismissed,
convertToMarkdownBannerDismissed: !state.settings['editor.enableHtmlToMarkdownBanner'],
selectedNoteIds: state.selectedNoteIds,
};
})(WarningBanner);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { MarkupLanguage } from '@joplin/renderer';
import { _ } from '../../../../locale';
import Setting from '../../../../models/Setting';
import CommandService from '../../../../services/CommandService';
import shim from '../../../../shim';

type NoteSlice = {
id: string;
markup_language: MarkupLanguage;
};

interface Props {
note: NoteSlice;
readOnly: boolean;
dismissed: boolean;
}

const useConvertToMarkdownBanner = ({ note, readOnly, dismissed }: Props) => {
const React = shim.react();
const noteId = note.id;
const enabled = !readOnly && !dismissed && note?.markup_language === MarkupLanguage.Html;

return React.useMemo(() => {
return {
enabled,
label: _('This note is in HTML format. Convert it to Markdown to edit it more easily.'),
dismiss: {
label: _('Don\'t show this message again'),
onPress: () => {
Setting.setValue('editor.enableHtmlToMarkdownBanner', false);
},
},
convert: {
label: _('Convert it'),
onPress: async () => {
if (!noteId) return;
await CommandService.instance().execute('convertNoteToMarkdown', noteId);
},
},
};
}, [noteId, enabled]);
};

export default useConvertToMarkdownBanner;
3 changes: 2 additions & 1 deletion packages/lib/models/settings/builtInMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,8 +761,9 @@ const builtInMetadata = (Setting: typeof SettingType) => {
type: SettingItemType.Bool,
public: true,
section: 'note',
appTypes: [AppType.Desktop],
appTypes: [AppType.Desktop, AppType.Mobile],
label: () => _('Enable HTML-to-Markdown conversion banner'),
description: () => _('If enabled, opening an HTML note displays a prompt to convert the note to Markdown.'),
storage: SettingStorage.File,
isGlobal: true,
},
Expand Down