Skip to content

Commit

Permalink
feat: moving notes to other notebooks via drag and drop (#52)
Browse files Browse the repository at this point in the history
* cosmetic: show notes with children narrower.

* feat: moving notes by dragging them to a different notebook

* lock

* version: electron

* fix: case

* chore: add deps

Co-authored-by: Daniel Mason <danielbaker210@gmail.com>
  • Loading branch information
danobot and Daniel Mason authored Jul 14, 2020
1 parent 35c72ad commit f4183bc
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 65 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ jobs:
- name: "[Docker Build] Build the tagged Docker image"
if: matrix.os == 'ubuntu-18.04' && github.ref == 'refs/heads/master'
run: docker build . --file Dockerfile.prod --tag danobot/notorious:${{ steps.pkg.outputs.version }} && docker tag danobot/notorious:${{ steps.pkg.outputs.version }} danobot/notorious:latest
- name: "[Docker Build] Build the tagged Docker image"
if: matrix.os == 'ubuntu-18.04' && github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop'
run: docker build . --file Dockerfile.prod --tag danobot/notorious:${{ steps.pkg.outputs.version }} && docker tag danobot/notorious:${{ steps.pkg.outputs.version }} danobot/notorious:latest
- name: "[Docker Build] Push the tagged Docker image"
if: matrix.os == 'ubuntu-18.04' && github.ref == 'refs/heads/master'
run: docker push danobot/notorious:${{ steps.pkg.outputs.version }}
Expand Down
2 changes: 2 additions & 0 deletions app/components/MainMenu/MainMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useRef} from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useDrop } from 'react-dnd'
import {
faPlusCircle,
faFile,
Expand All @@ -21,6 +22,7 @@ import Finder from '../util/Finder';
import TreeMenuCont from '../../containers/TreeMenuCont/TreeMenuCont';
import { Spin } from 'antd';
import { GlobalHotKeys } from 'react-hotkeys';
import { DragItemTypes } from '../../utils/DragItemTypes';

export default function MainMenu({
notebooks,
Expand Down
33 changes: 31 additions & 2 deletions app/components/MainMenu/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React, {useState} from 'react';
import { MenuItemStyle, MenuItemIcon, MenuItemLabel, MenuItemNormal, MenuItemSelected } from './style';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

import { useDrop } from 'react-dnd'
import { faCaretSquareRight, faCaretSquareDown } from '@fortawesome/free-solid-svg-icons';
import { DragItemTypes } from '../../utils/DragItemTypes';

export default function MenuItem({
noteId,
onClickHandler = () => {},
cacheParentId = () => {},
icon,
label,
compKey,
right,
indent,
droppable=false,
collapsible=false,
visible=false,
skipIcon=false,
Expand All @@ -19,15 +23,40 @@ export default function MenuItem({
}) {
const MenuItemComponent = (selected) ? MenuItemSelected : MenuItemNormal;
const [v, setVis] = useState(visible )
const [id, setId] = useState( )
const handleClick = () => {
setVis(!v)
}
// console.log("MainItem "+noteId)
const [{ canDrop, isOver }, drop] = useDrop({
accept: DragItemTypes.NOTE,
drop: (data) => {
cacheParentId()
return { name: id}
},
collect: (monitor) => {
return {
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}
},
})
// const isActive = canDrop && isOver
// let style = {}
// if (isActive) {
// style.backgroundColor = 'darkgreen'
// } else if (canDrop && droppable) {
// style.backgroundColor = 'darkkhaki'
// }
return (
<MenuItemComponent>
<MenuItemComponent
ref={drop}
>
<MenuItemStyle
onClick={e => onClickHandler()}
key={compKey}
indent={indent}

>
{!collapsible && !skipIcon && <MenuItemIcon onClick={e => handleClick()}>{icon}</MenuItemIcon>}
{collapsible && <MenuItemIcon onClick={e => handleClick()}>{<FontAwesomeIcon style={{fontSize: '11pt'}}
Expand Down
3 changes: 3 additions & 0 deletions app/components/MiddleMenu/MiddleMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export default function MiddleMenu({
selectedNote,
softDeleteNote,
updateNote,
moveNote,
dropNoteCache,
savingNew,
searchNotes,
addButtonDisabled,
Expand Down Expand Up @@ -108,6 +110,7 @@ export default function MiddleMenu({
note={i}
selected={i._id === selectedNote}
handleClick={selectNoteAction}
handleDrag={(id) => moveNote(id, dropNoteCache)}
handlers={notecardContextHandlers}
/>
))
Expand Down
3 changes: 2 additions & 1 deletion app/components/MiddleMenu/NoteCard/NoteCard.style.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import styled from 'styled-components';
import { hasChildren } from '../../../utils/utils';

export const NoteCardStyle = styled.div`
// background-color: green;
background-color: ${props => props.selected ? props.theme.colors.background.selected : 'inherit'};
height: 100px;
height: ${props => hasChildren(props.note.children) ? '31px': '100px'};
padding: 5px;
overflow: hidden;
border-bottom: 1px solid ${props => props.theme.colors.background.lift};
Expand Down
26 changes: 21 additions & 5 deletions app/components/MiddleMenu/NoteCard/NoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Truncate from 'react-truncate'
import { Tag } from 'antd';
import Moment from 'react-moment';
import styled from 'styled-components';

import { useDrag } from 'react-dnd'
import {
ContextMenu,
ContextMenuTrigger,
Expand All @@ -19,6 +19,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import { faClock, faListAlt } from "@fortawesome/free-regular-svg-icons";
import { faFolderOpen, faInbox, faColumns, faTasks, faFile, faThumbtack, faTh, faStream, faTimesCircle, faExclamationTriangle , faStar} from "@fortawesome/free-solid-svg-icons";
import { hasChildren } from '../../../utils/utils';
import { DragItemTypes } from '../../../utils/DragItemTypes';

const removeMd = require('remove-markdown');
export const InlineItem = styled.div`
Expand All @@ -41,6 +43,19 @@ export default function NoteCard(props) {
cmSwitchEditorHandler
} = props.handlers;
const { title, content, tags, _id, createdAt, updatedAt,children, kind, pinned, showInMenu,starred, deleted} = props.note;
const [{ isDragging }, drag] = useDrag({
item: { name: _id, type: DragItemTypes.NOTE },
end: (item, monitor) => {
const dropResult = monitor.getDropResult()
if (item) {
props.handleDrag(item.name)
}
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
})
// console.log(children)
return (
<>
<ContextMenuTrigger id={`${_id}cm`}>
Expand All @@ -49,10 +64,11 @@ export default function NoteCard(props) {
key={`${_id}style`}
onClick={e => props.handleClick(props.note._id)}
{...props}
ref={drag}
>
<div className="noteListTitle">{title || 'Untitled Note'}</div>
<div className="noteCardMeta">
{children && children.length > 0 && <InlineItem><FontAwesomeIcon icon={faFolderOpen} title={`Contains ${children.length} subnotes`} />{children.length}</InlineItem>}
{hasChildren(children) && <InlineItem><FontAwesomeIcon icon={faFolderOpen} title={`Contains ${children.length} subnotes`} />{children.length}</InlineItem>}
<InlineItem title={new Date(createdAt)}><Moment fromNow>{createdAt}</Moment></InlineItem>

<RightFloaty>
Expand All @@ -63,7 +79,7 @@ export default function NoteCard(props) {
{kind === 'index' && <InlineItem><FontAwesomeIcon title="Note Type: index" icon={faListAlt} /></InlineItem>}
{kind === 'columns' && <InlineItem><FontAwesomeIcon title="Note Type: columns" icon={faColumns} /></InlineItem>}
{starred && <InlineItem><FontAwesomeIcon title="Favourited" icon={faStar} /></InlineItem>}
{deleted && children.length > 0 && <InlineItem><FontAwesomeIcon title="This note cannot be deleted until all its subnotes have been removed." icon={faExclamationTriangle} /></InlineItem>}
{deleted && hasChildren(children) && <InlineItem><FontAwesomeIcon title="This note cannot be deleted until all its subnotes have been removed." icon={faExclamationTriangle} /></InlineItem>}

</RightFloaty>
</div>
Expand All @@ -72,11 +88,11 @@ export default function NoteCard(props) {
{tags &&
tags.length > 0 &&<InlineItem >{ tags.map(t => <span key={`tag-${_id}-${t}`} style={{marginLeft: '3px', fontStyle: 'italic'}}>{t}</span>) }</InlineItem>}
</div>
<div className="notePreview">
{false && <div className="notePreview">
<Truncate lines={2} ellipsis={<span>...</span>}>
{removeMd(content)}
</Truncate>
</div>
</div>}
</NoteCardStyle>
</ContextMenuTrigger>

Expand Down
6 changes: 5 additions & 1 deletion app/containers/MainMenu/reducers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createReducer } from '../../utils/utils'
import { configStorage } from '../../utils/localStorage';
import { SEARCH_NOTES_RESULTS } from '../../reducers/noteActions';
import { SEARCH_NOTES_RESULTS, CACHE_NOTE_PARENT_ID, MOVE_NOTE } from '../../reducers/noteActions';
import { SELECT_NOTEBOOK } from './actions';


Expand All @@ -10,6 +10,10 @@ const initialState = {
}

const mainMenuReducer = createReducer(initialState, {
[CACHE_NOTE_PARENT_ID]: (state, action) => {
return {...state, moveNoteDropTargetId: action.id}
},

[SELECT_NOTEBOOK]: (state, action) => {
return {...state, filter: action.filter}
},
Expand Down
1 change: 1 addition & 0 deletions app/containers/MiddleMenu/MiddleMenuCont.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ function mapStateToProps(state) {
addButtonDisabled: state.mainMenu.filter === "TRASH",
selectedNote: state.contentArea.selectedNote,
headerLabel: notebookLabelMaker(state.mainMenu.filter, state.notes, state.middleMenu),
dropNoteCache: state.mainMenu.moveNoteDropTargetId,
sorter: state.middleMenu.sorter
};
}
Expand Down
7 changes: 5 additions & 2 deletions app/containers/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { ThemeProvider } from 'styled-components';
import { Store } from '../reducers/types';
import Routes from '../Routes';
import darkTheme from '../utils/dark_theme.json';

import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
type Props = {
store: Store;
history: History;
Expand All @@ -17,7 +18,9 @@ const Root = ({ store, history }: Props) => (
<Provider store={store}>
<ThemeProvider theme={darkTheme}>
<ConnectedRouter history={history}>
<Routes />
<DndProvider backend={HTML5Backend}>
<Routes />
</DndProvider>
</ConnectedRouter>
</ThemeProvider>
</Provider>
Expand Down
19 changes: 13 additions & 6 deletions app/containers/TreeMenuCont/TreeMenuCont.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class TreeMenuCont extends React.Component {


render() {
const {selectedNotebook, selectNotebook, note, subNotes, handlers, children} = this.props
const {selectedNotebook, selectNotebook, note, subNotes, handlers, children, cacheParentId} = this.props
const {
cmCreateNotebookInside,
cmShowInMenuHandler,
Expand All @@ -36,7 +36,7 @@ class TreeMenuCont extends React.Component {
const {_id, title} = note
// console.log(selectedNotebook)
const icon = this.state.open ? <FontAwesomeIcon onClick={e=> this.setState({open: false})} icon={faChevronDown} /> : <FontAwesomeIcon onClick={e=> this.setState({open: true})} icon={faChevronRight} />

// console.log("TreeMenuCont Note id " + _id)
// we dont want to close a notebook menu when it is first selected. We want to close it on click (given its already selected) and open it given its closed.
// const singleClickHandler = (selectedNotebook !== _id) ? () => {selectNotebook(note._id); this.setState({open: true})} : () => {selectNotebook(note._id); this.setState({open: !this.state.open})}
const singleClickHandler = () => {selectNotebook(note._id); this.setState({open: !this.state.open})}
Expand All @@ -45,20 +45,27 @@ class TreeMenuCont extends React.Component {
{subNotes && subNotes.length ===0 && <ContextMenuTrigger id={`main-menu-context-${_id}`} key={`main-menu-context-trigger-a-${_id}`}>

<MenuItem
key={`menu-item-${_id}`}
droppable
noteId={this.props.note._id}
indent={this.props.level*16}
label={<DotLine>{note.title}</DotLine>}
icon={<FontAwesomeIcon icon={faFolder} />}
key={note._id} right={<MenuItemRightFloat>{children.length}</MenuItemRightFloat>}
right={<MenuItemRightFloat>{children.length}</MenuItemRightFloat>}
selected={selectedNotebook === _id}
onClickHandler={e=>selectNotebook(note._id)} key={"menucokponent"+_id}
onClickHandler={e=>selectNotebook(note._id)}
cacheParentId={e=>cacheParentId(_id)}
/>
</ContextMenuTrigger>
}
{subNotes && subNotes.length > 0 && <> <ContextMenuTrigger id={`main-menu-context-${_id}`} key={`main-menu-context-trigger-b-${_id}`}>
<MenuItem
key={"menucokponent"+_id} onClickHandler={e=> {singleClickHandler() }}
key={`menu-item-${_id}`}
droppable
noteId={this.props.note._id}
onClickHandler={e=> {singleClickHandler() }}
cacheParentId={e=>cacheParentId(_id)}
indent={this.props.level*16}
key={"MenuItem"+_id}
label={
<TreeHeading>
<DotLine>{note.title }</DotLine>
Expand Down
21 changes: 21 additions & 0 deletions app/reducers/noteActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {notesDB} from '../PouchInit'
import FlexSearch from 'flexsearch';
import MiddleMenu from '../components/MiddleMenu/MiddleMenu';
export const UPDATE_NOTE = 'UPDATE_NOTE';
export const MOVE_NOTE = 'MOVE_NOTE';
export const CREATE_NOTE = 'CREATE_NOTE';
export const DELETE_NOTE = 'DELETE_NOTE';
export const CACHE_NOTE_PARENT_ID = 'CACHE_NOTE_PARENT_ID'; // required beacuse react-dnd always receives undefined from Drop Target
export const SAVE_ATTACHMENT = 'ADD_ATTACHMENT';
export const SAVE_ATTACHMENT_SUCCESS = 'SAVE_ATTACHMENT_SUCCESS';
export const SAVE_ATTACHMENT_ERROR = 'SAVE_ATTACHMENT_ERROR';
Expand Down Expand Up @@ -41,6 +43,25 @@ export function emptyTrash() {
};
}

export function moveNote(id: string, parent: string) {
return dispatch => {
dispatch({
type: MOVE_NOTE,
id,
parent
});
};
}

export function cacheParentId(id: string) {
return dispatch => {
dispatch({
type: CACHE_NOTE_PARENT_ID,
id
});
};
}

export function createNote(parent: string, attributes: object) {
const noteId = uuid()
return dispatch => {
Expand Down
Loading

0 comments on commit f4183bc

Please sign in to comment.