feat: fully implement new note widget on android

This commit is contained in:
ammarahm-ed
2021-10-09 14:29:40 +05:00
parent 8a11961a6d
commit ac48c35710
6 changed files with 320 additions and 376 deletions

View File

@@ -6,6 +6,7 @@
<meta charset="utf-8" />
<title>Note Diff Preview</title>
<link rel="stylesheet" href="./index.css">
<link rel="stylesheet" href="./fonts.css">
<link id="dark_sheet" disabled rel="stylesheet" href="./dist/skins/notesnook-dark/content.min.css">
<link id="light_sheet" disabled rel="stylesheet" href="./dist/skins/notesnook/content.min.css">
<style>
@@ -80,17 +81,41 @@
padding: 12px !important;
overflow-x: hidden;
overflow-y: scroll;
min-height: 150px;
font-family: "Open Sans";
}
[contenteditable] {
outline: 0px solid transparent;
}
[contenteditable=true]:empty:before {
content: attr(placeholder);
pointer-events: none;
display: block;
color: gray;
/* For Firefox */
}
</style>
</head>
<body>
<div class="htmldiff_div">
<div placeholder="Write something..." contenteditable="true" class="htmldiff_div">
</div>
<script src="./listeners.js"></script>
<script src="./constants.js"></script>
<script>
function reactNativeEventHandler(type, value) {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(
JSON.stringify({
type: type,
value: value
})
);
}
}
attachMessageListener()
</script>
</body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -6,6 +6,7 @@
android:padding="@dimen/widget_margin"
android:theme="@style/ThemeOverlay.Notesnook.AppWidgetContainer">
<LinearLayout
android:layout_width="match_parent"
android:id="@+id/widget_button"
@@ -14,7 +15,7 @@
android:layout_centerVertical="true"
android:background="@drawable/layout_bg"
android:paddingHorizontal="10dp"
android:elevation="5dp"
android:orientation="horizontal">
<LinearLayout

View File

@@ -2,9 +2,9 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/note_widget"
android:initialLayout="@layout/note_widget"
android:minWidth="110dp"
android:minHeight="40dp"
android:previewImage="@drawable/example_appwidget_preview"
android:minWidth="300dp"
android:minHeight="50dp"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen"></appwidget-provider>

View File

@@ -6,6 +6,7 @@
<meta charset="utf-8" />
<title>Note Diff Preview</title>
<link rel="stylesheet" href="./index.css">
<link rel="stylesheet" href="./fonts.css">
<link id="dark_sheet" disabled rel="stylesheet" href="./dist/skins/notesnook-dark/content.min.css">
<link id="light_sheet" disabled rel="stylesheet" href="./dist/skins/notesnook/content.min.css">
<style>
@@ -80,17 +81,41 @@
padding: 12px !important;
overflow-x: hidden;
overflow-y: scroll;
min-height: 150px;
font-family: "Open Sans";
}
[contenteditable] {
outline: 0px solid transparent;
}
[contenteditable=true]:empty:before {
content: attr(placeholder);
pointer-events: none;
display: block;
color: gray;
/* For Firefox */
}
</style>
</head>
<body>
<div class="htmldiff_div">
<div placeholder="Write something..." contenteditable="true" class="htmldiff_div">
</div>
<script src="./listeners.js"></script>
<script src="./constants.js"></script>
<script>
function reactNativeEventHandler(type, value) {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(
JSON.stringify({
type: type,
value: value
})
);
}
}
attachMessageListener()
</script>
</body>

View File

@@ -1,6 +1,7 @@
import Clipboard from '@react-native-clipboard/clipboard';
import absolutify from 'absolutify';
import {getLinkPreview} from 'link-preview-js';
import React, {useEffect, useRef, useState} from 'react';
import { getLinkPreview } from 'link-preview-js';
import React, { useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Appearance,
@@ -9,24 +10,25 @@ import {
Platform,
SafeAreaView,
Text,
TextInput,
TouchableOpacity,
useWindowDimensions,
View
} from 'react-native';
import Animated, {Easing, timing, useValue} from 'react-native-reanimated';
import Animated, { Easing, timing, useValue } from 'react-native-reanimated';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import WebView from 'react-native-webview';
import ShareExtension from 'rn-extensions-share';
import sanitize from 'sanitize-html';
import validator from 'validator';
import {getElevation, showTooltip, TOOLTIP_POSITIONS} from '../src/utils';
import {COLOR_SCHEME_DARK, COLOR_SCHEME_LIGHT} from '../src/utils/Colors';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import Clipboard from '@react-native-clipboard/clipboard';
import {db} from '../src/utils/database';
import {SIZE} from '../src/utils/SizeUtils';
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from '../src/services/EventManager';
import { getElevation } from '../src/utils';
import { COLOR_SCHEME_DARK, COLOR_SCHEME_LIGHT } from '../src/utils/Colors';
import { db } from '../src/utils/database';
import Storage from '../src/utils/storage';
import {sleep} from '../src/utils/TimeUtils';
import { sleep } from '../src/utils/TimeUtils';
const AnimatedKAV = Animated.createAnimatedComponent(KeyboardAvoidingView);
const AnimatedSAV = Animated.createAnimatedComponent(SafeAreaView);
@@ -34,19 +36,10 @@ async function sanitizeHtml(site) {
try {
let html = await fetch(site);
html = await html.text();
let siteHtml = sanitize(html, {
allowedTags: sanitize.defaults.allowedTags.concat([
'img',
'style',
'head',
'link'
]),
allowedClasses: true,
allowVulnerableTags: true,
allowedAttributes: false,
allowProtocolRelative: true,
allowedSchemes: false
});
let siteHtml = html.replace(
/(?:<(script|button|input|textarea|style|link)(?:\s[^>]*)?>)\s*((?:(?!<\1)[\s\S])*)\s*(?:<\/\1>)/g,
''
);
return absolutify(siteHtml, site);
} catch (e) {
return '';
@@ -62,6 +55,30 @@ function makeHtmlFromPlainText(text) {
return `<p style="overflow-wrap:anywhere;white-space:pre-wrap" >${text}</p>`;
}
function getBaseUrl(site) {
var url = site.split('/').slice(0, 3).join('/');
return url;
}
async function absolutifyImgs(html, site) {
let parser = global.HTMLParser;
global.HTMLParser.body.innerHTML = html;
let images = parser.querySelectorAll('img');
for (var i = 0; i < images.length; i++) {
let img = images[i];
let url = getBaseUrl(site);
if (!img.src.startsWith('http')) {
if (img.src.startsWith('//')) {
img.src = img.src.replace('//', 'https://');
} else {
img.src = url + img.src;
}
}
}
return parser.body.innerHTML;
}
let defaultNote = {
title: null,
id: null,
@@ -71,7 +88,6 @@ let defaultNote = {
}
};
let editorContentValue = null;
const modes = {
1: {
@@ -101,13 +117,10 @@ const NotesnookShare = () => {
const [loadingIntent, setLoadingIntent] = useState(true);
const [loading, setLoading] = useState(false);
const [floating, setFloating] = useState(false);
const [quickNote, setQuickNote] = useState(false);
const [rawData, setRawData] = useState({
type: null,
value: null
});
const textInputRef = useRef();
const titleInputRef = useRef();
const {width, height} = useWindowDimensions();
const webviewRef = useRef();
const opacity = useValue(0);
@@ -117,7 +130,6 @@ const NotesnookShare = () => {
};
const prevAnimation = useRef(null);
const [mode, setMode] = useState(1);
const [keyboard,setKeyboard] = useState(false);
const animate = (opacityV, translateV) => {
prevAnimation.current = translateV;
@@ -134,86 +146,79 @@ const NotesnookShare = () => {
}).start();
};
const onKeyboardDidShow = (event) => {
const onKeyboardDidShow = event => {
let kHeight = event.endCoordinates.height;
console.log('called')
translate.setValue(-kHeight/1.8);
}
//translate.setValue(-150);
};
const onKeyboardDidHide = () => {
translate.setValue(0);
}
};
useEffect(() => {
let keyboardWillChangeFrame = Keyboard.addListener('keyboardWillChangeFrame', onKeyboardWillChangeFrame);
let keyboardDidShow = Keyboard.addListener("keyboardDidShow", onKeyboardDidShow);
let keyboardDidHide = Keyboard.addListener("keyboardDidHide", onKeyboardDidHide);
let keyboardWillChangeFrame = Keyboard.addListener(
'keyboardWillChangeFrame',
onKeyboardWillChangeFrame
);
let keyboardDidShow = Keyboard.addListener(
'keyboardDidShow',
onKeyboardDidShow
);
let keyboardDidHide = Keyboard.addListener(
'keyboardDidHide',
onKeyboardDidHide
);
return () => {
keyboardWillChangeFrame?.remove();
keyboardDidShow?.remove();
keyboardDidHide?.remove();
};
},[]);
}, []);
const onKeyboardWillChangeFrame = event => {
console.log('keyboad change frame',event)
setFloating(event.endCoordinates.width !== width);
};
const showLinkPreview = async link => {
let _note = {...defaultNote};
_note.title = 'Web link share';
_note.content.data = !note.content.data
? makeHtmlFromUrl(link)
: note.content.data + '\n' + makeHtmlFromUrl(link);
const showLinkPreview = async (note, link) => {
let _note = note;
_note.content.data = makeHtmlFromUrl(link);
try {
let preview = await getLinkPreview(link);
_note.title = preview.siteName || preview.title;
} catch (e) {
console.log(e);
}
setNote(_note);
return note;
};
const loadData = async () => {
try {
setNote(() => {
defaultNote.content.data = null;
return defaultNote;
});
const data = await ShareExtension.data();
console.log(data);
if (!data || data.length === 0) {
setRawData({
value: ''
});
setNote({...defaultNote});
setLoadingIntent(false);
setQuickNote(true);
return;
}
let note = defaultNote;
for (item of data) {
if (item.type === 'text') {
setRawData(item);
if (validator.isURL(item.value)) {
await showLinkPreview(item.value);
note = await showLinkPreview(note, item.value);
} else {
setNote(note => {
note.title = 'Note Share';
note.content.data = note.content.data
? note.content.data + '\n' + makeHtmlFromPlainText(item.value)
: makeHtmlFromPlainText(item.value);
return note;
});
note.content.data = makeHtmlFromPlainText(item.value);
}
}
}
setNote({...note});
} catch (e) {}
setLoadingIntent(false);
};
useEffect(() => {
setNote(defaultNote);
loadData();
sleep(50).then(() => {
animate(1, 0);
@@ -244,15 +249,20 @@ const NotesnookShare = () => {
}
const onPress = async () => {
titleInputRef.current?.blur();
textInputRef.current?.blur();
content = await getContent();
if (!content || content === '') {
return;
}
setLoading(true);
let add = async () => {
let _note = {...note};
_note.content.data =
_note.content.data + makeHtmlFromPlainText(editorContentValue);
await db.notes.add(note);
let _note = {
...note,
content: {
data: content,
type: 'tiny'
}
};
await db.notes.add(_note);
};
if (db && db.notes) {
await add();
@@ -276,6 +286,35 @@ const NotesnookShare = () => {
}
};
const getContent = () => {
return new Promise(resolve => {
let oncontent = value => {
eUnSubscribeEvent('share_content_event', oncontent);
resolve(value);
};
eSubscribeEvent('share_content_event', oncontent);
webviewRef.current?.injectJavaScript(`(function() {
let html = document.querySelector(".htmldiff_div").innerHTML;
if (!html) {
html = '';
}
reactNativeEventHandler('tiny', html);
})();`);
});
};
const onMessage = event => {
if (!event) return;
let data = JSON.parse(event.nativeEvent.data);
if (data.type === 'tiny') {
eSendEvent('share_content_event', data.value);
}
};
useEffect(() => {
onLoad();
}, [note]);
return (
<AnimatedSAV
style={{
@@ -307,7 +346,6 @@ const NotesnookShare = () => {
enabled={!floating && Platform.OS === 'ios'}
onLayout={event => {
if (prevAnimation.current === 0) return;
console.log('setting value here');
translate.setValue(event.nativeEvent.layout.height + 30);
}}
style={{
@@ -321,325 +359,180 @@ const NotesnookShare = () => {
]
}}
behavior="padding">
{quickNote ? null : (
<View
style={{
maxHeight: '100%',
paddingHorizontal: 12
}}>
<View
style={{
width: 50,
height: 6,
borderRadius: 100,
backgroundColor: colors.nav,
alignSelf: 'center',
position: 'absolute',
marginTop: 15
}}
/>
)}
{loadingIntent ? (
<View
style={{
height: 150,
width: '100%',
justifyContent: 'center',
alignItems: 'center'
width: '100%'
}}>
<ActivityIndicator color={colors.accent} />
<Text
<Button
color={colors.accent}
onPress={onPress}
loading={loading || loadingIntent}
icon="check"
iconSize={25}
type="action"
loading={loading}
style={{
color: colors.pri,
fontSize: SIZE.md,
marginTop: 5
}}>
Parsing Data...
</Text>
</View>
) : (
<>
position: 'absolute',
zIndex: 999,
...getElevation(10),
right: 24,
bottom: -35
}}
/>
<View
style={{
maxHeight: '100%'
marginTop: 10,
minHeight: 100,
borderRadius: 10,
...getElevation(5),
backgroundColor: colors.bg
}}>
{quickNote ? null : (
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.nav,
paddingHorizontal: 12
}}>
<TextInput
ref={titleInputRef}
style={{
fontSize: 25,
fontFamily:
Platform.OS === 'android' ? 'Roboto-Medium' : null,
fontWeight: Platform.OS === 'ios' ? '600' : null,
color: colors.pri,
flexGrow: 1,
maxWidth: '100%'
}}
placeholderTextColor={colors.icon}
defaultValue={note?.title}
onChangeText={v =>
setNote(_note => {
_note.title = v;
return _note;
})
}
onSubmitEditing={() => {
textInputRef.current?.focus();
}}
blurOnSubmit={false}
placeholder="Note title"
/>
</View>
)}
{quickNote ? null : (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
marginVertical: 10,
borderBottomWidth: 1,
borderBottomColor: colors.nav
}}>
{[
{
title: 'Plain text',
onPress: () => {
let html = validator.isURL(rawData.value)
? makeHtmlFromUrl(rawData.value)
: makeHtmlFromPlainText(rawData.value);
setNote(note => {
note.content.data = html;
return note;
});
onLoad();
}
},
...[
rawData?.value && validator.isURL(rawData.value)
? {
title: 'Clip webpage',
onPress: async () => {
let html = await sanitizeHtml(rawData.value);
setNote(note => {
note.content.data = html;
return note;
});
onLoad();
}
}
: null
]
].map(
(item, index) =>
item && (
<Button
title={item.title}
color={colors.nav}
textStyle={{
color: colors.icon,
fontWeight: 'normal',
fontSize: 14,
fontFamily: null
}}
onPress={item.onPress}
style={{
borderWidth: 0.5,
borderRadius: 100,
borderColor: colors.icon,
height: 30,
marginRight: 10,
marginLeft: index === 0 ? 12 : 0,
paddingHorizontal: 12
}}
/>
)
)}
</View>
)}
{quickNote ? null : (
<View
style={{
height: height * 0.25,
width: '100%'
}}>
<WebView
onLoad={onLoad}
ref={webviewRef}
style={{
width: '100%',
height: '100%',
backgroundColor: 'transparent'
}}
cacheMode="LOAD_DEFAULT"
domStorageEnabled={true}
scrollEnabled={true}
bounces={false}
allowFileAccess={true}
scalesPageToFit={true}
allowingReadAccessToURL={
Platform.OS === 'android' ? true : null
}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
originWhitelist={['*']}
javaScriptEnabled={true}
cacheEnabled={true}
source={
Platform.OS === 'ios'
? {uri: sourceUri}
: {
uri: 'file:///android_asset/plaineditor.html',
baseUrl: 'file:///android_asset/'
}
}
/>
</View>
)}
<View
style={{
width: '100%',
paddingHorizontal: 12
height: height * 0.25,
paddingBottom: 15
}}>
<WebView
onLoad={onLoad}
ref={webviewRef}
style={{
width: '100%',
height: '100%',
backgroundColor: 'transparent'
}}
cacheMode="LOAD_DEFAULT"
domStorageEnabled={true}
scrollEnabled={true}
bounces={false}
allowFileAccess={true}
scalesPageToFit={true}
allowingReadAccessToURL={
Platform.OS === 'android' ? true : null
}
onMessage={onMessage}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
originWhitelist={['*']}
javaScriptEnabled={true}
cacheEnabled={true}
source={
Platform.OS === 'ios'
? {uri: sourceUri}
: {
uri: 'file:///android_asset/plaineditor.html',
baseUrl: 'file:///android_asset/'
}
}
/>
</View>
<View
style={{
flexDirection: 'row',
paddingHorizontal: 12,
paddingRight: 80,
alignItems: 'center'
}}>
<Button
color={colors.accent}
color={colors.shade}
onPress={onPress}
loading={loading}
icon="check"
iconSize={25}
type="action"
icon={modes[mode].icon}
onPress={async () => {
let _mode = modes[mode];
if (
_mode.type === 'text' &&
validator.isURL(rawData.value)
) {
let html = await sanitizeHtml(rawData.value);
html = await absolutifyImgs(html, rawData.value);
setNote(note => {
note.content.data = html;
return {...note};
});
setMode(2);
return;
}
if (_mode.type === 'clip') {
let html = validator.isURL(rawData.value)
? makeHtmlFromUrl(rawData.value)
: makeHtmlFromPlainText(rawData.value);
setNote(note => {
note.content.data = html;
return {...note};
});
setMode(1);
return;
}
}}
title={modes[mode].title}
iconSize={18}
iconColor={colors.accent}
textStyle={{
fontSize: 12,
color: colors.accent,
marginLeft: 5
}}
style={{
position: 'absolute',
zIndex: 999,
...getElevation(10),
right: 24,
bottom: -35
marginRight: 10,
height: 30,
borderRadius: 100,
paddingHorizontal: 12,
marginTop: -2.5
}}
/>
<View
style={{
marginTop: 10,
minHeight: 100,
borderRadius: 10,
...getElevation(5),
backgroundColor: colors.bg
}}>
<TextInput
ref={textInputRef}
style={{
fontSize: 16,
color: colors.pri,
fontFamily: 'OpenSans-Regular',
padding: 12,
width: '100%'
{Clipboard.hasString() ? (
<Button
color={colors.nav}
onPress={onPress}
icon="clipboard"
onPress={async () => {
let text = await Clipboard.getString();
if (text) {
let content = await getContent();
setNote(note => {
note.content.data =
content + '\n' + makeHtmlFromPlainText(text);
return {...note};
});
}
}}
iconSize={18}
iconColor={colors.icon}
title="Paste"
textStyle={{
fontSize: 12,
color: colors.icon,
marginLeft: 5
}}
placeholderTextColor={colors.icon}
onChangeText={v => (editorContentValue = v)}
multiline={true}
numberOfLines={quickNote ? 5 : 3}
textAlignVertical="top"
value={editorContentValue}
blurOnSubmit={false}
placeholder={
quickNote
? 'Write something...'
: 'Add some additional notes here'
}
/>
<View
style={{
flexDirection: 'row',
paddingHorizontal: 12,
paddingRight: 80
}}>
<Button
color={colors.shade}
onPress={onPress}
loading={loading}
icon={modes[mode].icon}
onPress={async () => {
let _mode = modes[mode];
if (_mode.type === "text") {
setMode(2);
return;
}
if (_mode.type === "clip") {
setMode(1);
return
}
if (_mode.type == "link") {
setMode(3)
}
}}
title={modes[mode].title}
iconSize={18}
iconColor={colors.accent}
textStyle={{
fontSize: 12,
color: colors.accent,
marginLeft: 5
}}
style={{
marginRight: 10,
height: 30,
borderRadius: 100,
paddingHorizontal: 12,
marginTop: -2.5
}}
/>
{Clipboard.hasString() ? (
<Button
color={colors.nav}
onPress={onPress}
loading={loading}
icon="clipboard"
onPress={async () => {
let text = await Clipboard.getString();
if (text) {
textInputRef.current?.setNativeProps({
text: text
});
editorContentValue = text;
}
}}
iconSize={18}
iconColor={colors.icon}
title="Paste"
textStyle={{
fontSize: 12,
color: colors.icon,
marginLeft: 5
}}
style={{
marginRight: 15,
height: 30,
borderRadius: 100,
paddingHorizontal: 6,
marginTop: -2.5
}}
/>
) : null}
</View>
</View>
marginRight: 15,
height: 30,
borderRadius: 100,
paddingHorizontal: 6,
marginTop: -2.5
}}
/>
) : null}
</View>
<View
style={{
height: 40
}}
/>
</View>
</>
)}
</View>
<View
style={{
height: 40
}}
/>
</View>
</AnimatedKAV>
</AnimatedSAV>
);
@@ -699,7 +592,7 @@ const Button = ({
]}>
{loading && <ActivityIndicator color="white" />}
{icon && (
{icon && !loading && (
<Icon name={icon} size={iconSize} color={iconColor || 'white'} />
)}