diff --git a/ElectronClient/app/ElectronAppWrapper.js b/ElectronClient/app/ElectronAppWrapper.js index 0e884a520..0e2c1e1c1 100644 --- a/ElectronClient/app/ElectronAppWrapper.js +++ b/ElectronClient/app/ElectronAppWrapper.js @@ -2,6 +2,7 @@ const { _ } = require('lib/locale.js'); const { BrowserWindow } = require('electron'); const url = require('url') const path = require('path') +const urlUtils = require('lib/urlUtils.js'); class ElectronAppWrapper { @@ -27,20 +28,6 @@ class ElectronAppWrapper { return this.win_; } - // store() { - // return this.store_; - // } - - // dispatch(action) { - // return this.store().dispatch(action); - // } - - // windowContentSize() { - // if (!this.win_) return { width: 0, height: 0 }; - // const s = this.win_.getContentSize(); - // return { width: s[0], height: s[1] }; - // } - createWindow() { this.win_ = new BrowserWindow({width: 800, height: 600}) @@ -55,18 +42,6 @@ class ElectronAppWrapper { this.win_.on('closed', () => { this.win_ = null }) - - this.win_.on('resize', () => { - // this.dispatch({ - // type: 'WINDOW_CONTENT_SIZE_SET', - // size: this.windowContentSize(), - // }); - }); - - // this.dispatch({ - // type: 'WINDOW_CONTENT_SIZE_SET', - // size: this.windowContentSize(), - // }); } async waitForElectronAppReady() { diff --git a/ElectronClient/app/bridge.js b/ElectronClient/app/bridge.js index 1bdb4ed47..c29709950 100644 --- a/ElectronClient/app/bridge.js +++ b/ElectronClient/app/bridge.js @@ -1,3 +1,5 @@ +const { _ } = require('lib/locale.js'); + class Bridge { constructor(electronWrapper) { @@ -23,6 +25,30 @@ class Bridge { return dialog.showMessageBox(options); } + showErrorMessageBox(message) { + return this.showMessageBox({ + type: 'error', + message: message, + }); + } + + showConfirmMessageBox(message) { + const result = this.showMessageBox({ + type: 'question', + message: message, + buttons: [_('OK'), _('Cancel')], + }); + return result === 0; + } + + get Menu() { + return require('electron').Menu; + } + + get MenuItem() { + return require('electron').MenuItem; + } + } let bridge_ = null; diff --git a/ElectronClient/app/gui/Header.jsx b/ElectronClient/app/gui/Header.jsx index adf33f80f..a170a8000 100644 --- a/ElectronClient/app/gui/Header.jsx +++ b/ElectronClient/app/gui/Header.jsx @@ -11,7 +11,6 @@ class HeaderComponent extends React.Component { } makeButton(key, options) { - console.info(key, options); return {options.onClick()}}>{options.title} } @@ -42,7 +41,7 @@ class HeaderComponent extends React.Component { } const mapStateToProps = (state) => { - return { theme: state.theme }; + return { theme: state.settings.theme }; }; const Header = connect(mapStateToProps)(HeaderComponent); diff --git a/ElectronClient/app/gui/MainScreen.jsx b/ElectronClient/app/gui/MainScreen.jsx index 7d602500a..5e7e25ee2 100644 --- a/ElectronClient/app/gui/MainScreen.jsx +++ b/ElectronClient/app/gui/MainScreen.jsx @@ -4,11 +4,20 @@ const { Header } = require('./Header.min.js'); const { SideBar } = require('./SideBar.min.js'); const { NoteList } = require('./NoteList.min.js'); const { NoteText } = require('./NoteText.min.js'); +const { PromptDialog } = require('./PromptDialog.min.js'); +const { Setting } = require('lib/models/setting.js'); +const { Note } = require('lib/models/note.js'); const { themeStyle } = require('../theme.js'); +const { _ } = require('lib/locale.js'); const layoutUtils = require('lib/layout-utils.js'); +const { bridge } = require('electron').remote.require('./bridge'); class MainScreenComponent extends React.Component { + componentWillMount() { + this.setState({ newNotePromptVisible: false }); + } + render() { const style = this.props.style; const theme = themeStyle(this.props.theme); @@ -40,9 +49,40 @@ class MainScreenComponent extends React.Component { verticalAlign: 'top', }; + const promptStyle = { + width: style.width, + height: style.height, + }; + + const headerButtons = [ + { + title: _('New note'), + onClick: () => { + this.setState({ newNotePromptVisible: true }); + }, + }, + ]; + + const newNotePromptOnAccept = async (answer) => { + const folderId = Setting.value('activeFolderId'); + if (!folderId) return; + + const note = await Note.save({ + title: answer, + parent_id: folderId, + }); + Note.updateGeolocation(note.id); + + this.props.dispatch({ + type: 'NOTES_SELECT', + noteId: note.id, + }); + } + return (
-
+ newNotePromptOnAccept(answer)} message={_('Note title:')} visible={this.state.newNotePromptVisible}/> +
diff --git a/ElectronClient/app/gui/NoteList.jsx b/ElectronClient/app/gui/NoteList.jsx index d67aaaa82..8b836d988 100644 --- a/ElectronClient/app/gui/NoteList.jsx +++ b/ElectronClient/app/gui/NoteList.jsx @@ -1,10 +1,28 @@ const { ItemList } = require('./ItemList.min.js'); const React = require('react'); const { connect } = require('react-redux'); +const { themeStyle } = require('../theme.js'); +const { _ } = require('lib/locale.js'); +const { bridge } = require('electron').remote.require('./bridge'); +const Menu = bridge().Menu; +const MenuItem = bridge().MenuItem; class NoteListComponent extends React.Component { - itemRenderer(index, item) { + itemContextMenu(event) { + const noteId = event.target.getAttribute('data-id'); + if (!noteId) throw new Error('No data-id on element'); + + const menu = new Menu() + menu.append(new MenuItem({label: _('Delete'), async click() { + const ok = bridge().showConfirmMessageBox(_('Delete note?')); + if (!ok) return; + await Note.delete(noteId); + }})) + menu.popup(bridge().window()); + } + + itemRenderer(index, item, theme) { const onClick = (item) => { this.props.dispatch({ type: 'NOTES_SELECT', @@ -12,20 +30,27 @@ class NoteListComponent extends React.Component { }); } - let classes = ['item']; - classes.push(index % 2 === 0 ? 'even' : 'odd'); - if (this.props.selectedNoteId === item.id) classes.push('selected'); - return
{ onClick(item) }} className={classes.join(' ')} key={index}>{item.title + ' ' + item.id.substr(0,4)}
+ const style = { + height: this.props.itemHeight, + display: 'block', + cursor: 'pointer', + backgroundColor: index % 2 === 0 ? theme.backgroundColor : theme.oddBackgroundColor, + fontWeight: this.props.selectedNoteId === item.id ? 'bold' : 'normal', + }; + + return this.itemContextMenu(event)} href="#" style={style} onClick={() => { onClick(item) }} key={index}>{item.title} } render() { + const theme = themeStyle(this.props.theme); + return ( { return this.itemRenderer(index, item) } } + itemRenderer={ (index, item) => { return this.itemRenderer(index, item, theme) } } > ); } @@ -36,6 +61,7 @@ const mapStateToProps = (state) => { return { notes: state.notes, selectedNoteId: state.selectedNoteId, + theme: state.settings.theme, }; }; diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx index e036d22a0..958e2234c 100644 --- a/ElectronClient/app/gui/NoteText.jsx +++ b/ElectronClient/app/gui/NoteText.jsx @@ -6,6 +6,7 @@ const { reg } = require('lib/registry.js'); const MdToHtml = require('lib/MdToHtml'); const shared = require('lib/components/shared/note-screen-shared.js'); const { bridge } = require('electron').remote.require('./bridge'); +const { themeStyle } = require('../theme.js'); class NoteTextComponent extends React.Component { @@ -55,6 +56,10 @@ class NoteTextComponent extends React.Component { await shared.saveNoteButton_press(this); } + async saveOneProperty(name, value) { + await shared.saveOneProperty(this, name, value); + } + scheduleSave() { if (this.scheduleSaveTimeout_) clearTimeout(this.scheduleSaveTimeout_); this.scheduleSaveTimeout_ = setTimeout(() => { @@ -73,6 +78,7 @@ class NoteTextComponent extends React.Component { this.setState({ note: note, + lastSavedNote: Object.assign({}, note), mode: 'view', }); } @@ -164,7 +170,7 @@ class NoteTextComponent extends React.Component { webviewReady: true, }); - this.webview_.openDevTools(); + //this.webview_.openDevTools(); } webview_ref(element) { @@ -218,6 +224,7 @@ class NoteTextComponent extends React.Component { const style = this.props.style; const note = this.state.note; const body = note ? note.body : ''; + const theme = themeStyle(this.props.theme); const viewerStyle = { width: Math.floor(style.width / 2), @@ -227,12 +234,17 @@ class NoteTextComponent extends React.Component { verticalAlign: 'top', }; + const paddingTop = 14; + const editorStyle = { width: style.width - viewerStyle.width, - height: style.height, + height: style.height - paddingTop, overflowY: 'scroll', float: 'left', verticalAlign: 'top', + paddingTop: paddingTop + 'px', + lineHeight: theme.textAreaLineHeight + 'px', + fontSize: theme.fontSize + 'px', }; if (this.state.webviewReady) { @@ -242,7 +254,7 @@ class NoteTextComponent extends React.Component { }, postMessageSyntax: 'ipcRenderer.sendToHost', }; - const html = this.mdToHtml().render(body, {}, mdOptions); + const html = this.mdToHtml().render(body, theme, mdOptions); this.webview_.send('setHtml', html); } diff --git a/ElectronClient/app/gui/PromptDialog.jsx b/ElectronClient/app/gui/PromptDialog.jsx new file mode 100644 index 000000000..0612437bc --- /dev/null +++ b/ElectronClient/app/gui/PromptDialog.jsx @@ -0,0 +1,107 @@ +const React = require('react'); +const { connect } = require('react-redux'); +const { _ } = require('lib/locale.js'); +const { themeStyle } = require('../theme.js'); + +class PromptDialog extends React.Component { + + componentWillMount() { + this.setState({ + visible: false, + answer: '', + }); + this.focusInput_ = true; + } + + componentWillReceiveProps(newProps) { + if ('visible' in newProps) { + this.setState({ visible: newProps.visible }); + if (newProps.visible) this.focusInput_ = true; + } + } + + componentDidUpdate() { + if (this.focusInput_ && this.answerInput_) this.answerInput_.focus(); + this.focusInput_ = false; + } + + render() { + const style = this.props.style; + const theme = themeStyle(this.props.theme); + + const modalLayerStyle = { + zIndex: 9999, + position: 'absolute', + top: 0, + left: 0, + width: style.width, + height: style.height, + backgroundColor: 'rgba(0,0,0,0.6)', + display: this.state.visible ? 'flex' : 'none', + alignItems: 'center', + justifyContent: 'center', + }; + + const promptDialogStyle = { + backgroundColor: 'white', + padding: 10, + display: 'inline-block', + boxShadow: '6px 6px 20px rgba(0,0,0,0.5)', + }; + + const buttonStyle = { + minWidth: theme.buttonMinWidth, + minHeight: theme.buttonMinHeight, + marginLeft: 5, + }; + + const inputStyle = { + width: 0.5 * style.width, + maxWidth: 400, + }; + + const onAccept = () => { + if (this.props.onAccept) this.props.onAccept(this.state.answer); + this.setState({ visible: false, answer: '' }); + } + + const onReject = () => { + if (this.props.onReject) this.props.onReject(); + this.setState({ visible: false, answer: '' }); + } + + const onChange = (event) => { + this.setState({ answer: event.target.value }); + } + + const onKeyDown = (event) => { + if (event.key === 'Enter') { + onAccept(); + } else if (event.key === 'Escape') { + onReject(); + } + } + + return ( +
+
+ + this.answerInput_ = input} + value={this.state.answer} + type="text" + onChange={(event) => onChange(event)} + onKeyDown={(event) => onKeyDown(event)} /> +
+ + +
+
+
+ ); + } + +} + +module.exports = { PromptDialog }; \ No newline at end of file diff --git a/ElectronClient/app/gui/SideBar.jsx b/ElectronClient/app/gui/SideBar.jsx index 9f465b0e1..408c981c6 100644 --- a/ElectronClient/app/gui/SideBar.jsx +++ b/ElectronClient/app/gui/SideBar.jsx @@ -2,9 +2,27 @@ const React = require('react'); const { connect } = require('react-redux'); const shared = require('lib/components/shared/side-menu-shared.js'); const { Synchronizer } = require('lib/synchronizer.js'); +const { themeStyle } = require('../theme.js'); class SideBarComponent extends React.Component { + style() { + const theme = themeStyle(this.props.theme); + + const itemHeight = 20; + + let style = { + root: {}, + listItem: { + display: 'block', + cursor: 'pointer', + height: itemHeight, + }, + }; + + return style; + } + folderItem_click(folder) { this.props.dispatch({ type: 'FOLDERS_SELECT', @@ -24,15 +42,17 @@ class SideBarComponent extends React.Component { } folderItem(folder, selected) { - let classes = []; - if (selected) classes.push('selected'); - return
{this.folderItem_click(folder)}}>{folder.title}
+ const style = Object.assign({}, this.style().listItem, { + fontWeight: selected ? 'bold' : 'normal', + }); + return {this.folderItem_click(folder)}}>{folder.title} } tagItem(tag, selected) { - let classes = []; - if (selected) classes.push('selected'); - return
{this.tagItem_click(tag)}}>Tag: {tag.title}
+ const style = Object.assign({}, this.style().listItem, { + fontWeight: selected ? 'bold' : 'normal', + }); + return {this.tagItem_click(tag)}}>Tag: {tag.title} } makeDivider(key) { @@ -44,6 +64,9 @@ class SideBarComponent extends React.Component { } render() { + const theme = themeStyle(this.props.theme); + const style = Object.assign({}, this.style().root, this.props.style); + let items = []; if (this.props.folders.length) { @@ -69,7 +92,7 @@ class SideBarComponent extends React.Component { items.push(
{syncReportText}
); return ( -
+
{items}
); diff --git a/ElectronClient/app/index.html b/ElectronClient/app/index.html index 1db1dd121..2b3275949 100644 --- a/ElectronClient/app/index.html +++ b/ElectronClient/app/index.html @@ -2,12 +2,11 @@ - Hello World! + Joplin
- - + \ No newline at end of file diff --git a/ElectronClient/app/package-lock.json b/ElectronClient/app/package-lock.json index c09a6d414..9c0341fd4 100644 --- a/ElectronClient/app/package-lock.json +++ b/ElectronClient/app/package-lock.json @@ -1360,6 +1360,25 @@ "yargs": "10.0.3" } }, + "electron-context-menu": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-0.9.1.tgz", + "integrity": "sha1-7U3yDAgEkcPJlqv8s2MVmUajgFg=", + "requires": { + "electron-dl": "1.10.0", + "electron-is-dev": "0.1.2" + } + }, + "electron-dl": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-1.10.0.tgz", + "integrity": "sha1-+UQWBkBW/G8qhq5JhhTJNSaJCvk=", + "requires": { + "ext-name": "5.0.0", + "pupa": "1.0.0", + "unused-filename": "1.0.0" + } + }, "electron-download-tf": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/electron-download-tf/-/electron-download-tf-4.3.4.tgz", @@ -1377,6 +1396,11 @@ "sumchecker": "2.0.2" } }, + "electron-is-dev": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.1.2.tgz", + "integrity": "sha1-ihBD4ys6HaHD9VPc4oznZCRhZ+M=" + }, "electron-osx-sign": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.7.tgz", @@ -1617,6 +1641,23 @@ "integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==", "dev": true }, + "ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "requires": { + "mime-db": "1.30.0" + } + }, + "ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "requires": { + "ext-list": "2.2.2", + "sort-keys-length": "1.0.1" + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -2275,6 +2316,11 @@ "path-is-inside": "1.0.2" } }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", @@ -2719,8 +2765,7 @@ "mime-db": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", - "dev": true + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" }, "mime-types": { "version": "2.1.17", @@ -2769,6 +2814,11 @@ } } }, + "modify-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", + "integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" + }, "moment": { "version": "2.19.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz", @@ -3134,8 +3184,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -3317,6 +3366,11 @@ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, + "pupa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-1.0.0.tgz", + "integrity": "sha1-mpVopa9+ZXuEYqbp1TKHQ1YM7/Y=" + }, "q": { "version": "0.9.7", "resolved": "https://registry.npmjs.org/q/-/q-0.9.7.tgz", @@ -3771,6 +3825,22 @@ "hoek": "4.2.0" } }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "requires": { + "is-plain-obj": "1.1.0" + } + }, + "sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", + "requires": { + "sort-keys": "1.1.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4890,6 +4960,15 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" }, + "unused-filename": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-1.0.0.tgz", + "integrity": "sha1-00CID3GuIRXrqhMlvvBcxmhEacY=", + "requires": { + "modify-filename": "1.1.0", + "path-exists": "3.0.0" + } + }, "unzip-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", diff --git a/ElectronClient/app/package.json b/ElectronClient/app/package.json index 488bf9bf7..b9620718c 100644 --- a/ElectronClient/app/package.json +++ b/ElectronClient/app/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "app-module-path": "^2.2.0", + "electron-context-menu": "^0.9.1", "fs-extra": "^4.0.2", "html-entities": "^1.2.1", "lodash": "^4.17.4", diff --git a/ElectronClient/app/theme.js b/ElectronClient/app/theme.js index 976cbd0ff..c3cb45c9f 100644 --- a/ElectronClient/app/theme.js +++ b/ElectronClient/app/theme.js @@ -6,6 +6,7 @@ const globalStyle = { itemMarginTop: 10, itemMarginBottom: 10, backgroundColor: "#ffffff", + oddBackgroundColor: "#dddddd", color: "#555555", // For regular text colorError: "red", colorWarn: "#9A5B00", @@ -15,17 +16,21 @@ const globalStyle = { selectedColor: '#e5e5e5', disabledOpacity: 0.3, headerHeight: 20, + buttonMinWidth: 50, + buttonMinHeight: 30, + textAreaLineHeight: 17, raisedBackgroundColor: "#0080EF", raisedColor: "#003363", raisedHighlightedColor: "#ffffff", // For WebView - must correspond to the properties above - htmlFontSize: '20x', + htmlFontSize: '16px', htmlColor: 'black', // Note: CSS in WebView component only supports named colors or rgb() notation htmlBackgroundColor: 'white', htmlDividerColor: 'Gainsboro', htmlLinkColor: 'blue', + htmlLineHeight: '20px', }; globalStyle.marginRight = globalStyle.margin; diff --git a/ReactNativeClient/lib/MdToHtml.js b/ReactNativeClient/lib/MdToHtml.js index 4c3b48515..80546f454 100644 --- a/ReactNativeClient/lib/MdToHtml.js +++ b/ReactNativeClient/lib/MdToHtml.js @@ -203,6 +203,8 @@ class MdToHtml { if (!options) options = {}; if (!options.postMessageSyntax) options.postMessageSyntax = 'postMessage'; + console.info(style); + const cacheKey = this.makeContentKey(this.loadedResources_, body, style, options); if (this.cachedContentKey_ === cacheKey) return this.cachedContent_; @@ -255,8 +257,13 @@ class MdToHtml { body { font-size: ` + style.htmlFontSize + `; color: ` + style.htmlColor + `; - line-height: 1.5em; + line-height: ` + style.htmlLineHeight + `; background-color: ` + style.htmlBackgroundColor + `; + font-family: sans-serif; + } + p, h1, h2, h3, h4, ul { + margin-top: 14px; + margin-bottom: 14px; } h1 { font-size: 1.2em; diff --git a/ReactNativeClient/lib/components/global-style.js b/ReactNativeClient/lib/components/global-style.js index e7174a40f..8eeebb594 100644 --- a/ReactNativeClient/lib/components/global-style.js +++ b/ReactNativeClient/lib/components/global-style.js @@ -20,11 +20,12 @@ const globalStyle = { raisedHighlightedColor: "#ffffff", // For WebView - must correspond to the properties above - htmlFontSize: '20x', + htmlFontSize: '16px', htmlColor: 'black', // Note: CSS in WebView component only supports named colors or rgb() notation htmlBackgroundColor: 'white', htmlDividerColor: 'Gainsboro', htmlLinkColor: 'blue', + htmlLineHeight: '20px', }; globalStyle.marginRight = globalStyle.margin; diff --git a/ReactNativeClient/lib/urlUtils.js b/ReactNativeClient/lib/urlUtils.js new file mode 100644 index 000000000..5d754af14 --- /dev/null +++ b/ReactNativeClient/lib/urlUtils.js @@ -0,0 +1,9 @@ +const urlUtils = {}; + +urlUtils.hash = function(url) { + const s = url.split('#'); + if (s.length <= 1) return ''; + return s[s.length - 1]; +} + +module.exports = urlUtils; \ No newline at end of file