diff --git a/packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.ts b/packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.ts index 73cb2e7b21..df824dcb14 100644 --- a/packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.ts +++ b/packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.ts @@ -18,6 +18,6 @@ export const runtime = (comp: any): CommandRuntime => { }, }); }, - enabledCondition: 'joplinServerConnected && oneNoteSelected', + enabledCondition: 'joplinServerConnected && someNotesSelected', }; }; diff --git a/packages/app-desktop/gui/NoteListItem.tsx b/packages/app-desktop/gui/NoteListItem.tsx index 4517ea207e..ce432a9364 100644 --- a/packages/app-desktop/gui/NoteListItem.tsx +++ b/packages/app-desktop/gui/NoteListItem.tsx @@ -110,6 +110,7 @@ function NoteListItem(props: NoteListItemProps, ref: any) { let listItemTitleStyle = Object.assign({}, props.style.listItemTitle); listItemTitleStyle.paddingLeft = !item.is_todo ? hPadding : 4; + if (item.is_shared) listItemTitleStyle.color = theme.colorWarn; if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, props.style.listItemTitleCompleted); const displayTitle = Note.displayTitle(item); diff --git a/packages/app-desktop/gui/ShareNoteDialog.tsx b/packages/app-desktop/gui/ShareNoteDialog.tsx index c3a5e894e7..f5dffc743e 100644 --- a/packages/app-desktop/gui/ShareNoteDialog.tsx +++ b/packages/app-desktop/gui/ShareNoteDialog.tsx @@ -10,19 +10,21 @@ import { reg } from '@joplin/lib/registry'; import Dialog from './Dialog'; import DialogTitle from './DialogTitle'; import ShareService from '@joplin/lib/services/share/ShareService'; +import { StateShare } from '@joplin/lib/services/share/reducer'; +import { NoteEntity } from '@joplin/lib/services/database/types'; +import Button from './Button/Button'; +import { connect } from 'react-redux'; +import { AppState } from '../app'; const { clipboard } = require('electron'); -interface ShareNoteDialogProps { +interface Props { themeId: number; noteIds: Array; onClose: Function; + shares: StateShare[]; } -interface SharesMap { - [key: string]: any; -} - -function styles_(props: ShareNoteDialogProps) { +function styles_(props: Props) { return buildStyle('ShareNoteDialog', props.themeId, (theme: any) => { return { noteList: { @@ -60,17 +62,21 @@ function styles_(props: ShareNoteDialogProps) { }); } -export default function ShareNoteDialog(props: ShareNoteDialogProps) { +export function ShareNoteDialog(props: Props) { console.info('Render ShareNoteDialog'); - const [notes, setNotes] = useState([]); + const [notes, setNotes] = useState([]); const [sharesState, setSharesState] = useState('unknown'); - const [shares, setShares] = useState({}); + // const [shares, setShares] = useState({}); const noteCount = notes.length; const theme = themeStyle(props.themeId); const styles = styles_(props); + useEffect(() => { + void ShareService.instance().refreshShares(); + }, []); + useEffect(() => { async function fetchNotes() { const result = []; @@ -87,9 +93,9 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) { props.onClose(); }; - const copyLinksToClipboard = (shares: SharesMap) => { + const copyLinksToClipboard = (shares: StateShare[]) => { const links = []; - for (const n in shares) links.push(ShareService.instance().shareUrl(shares[n])); + for (const share of shares) links.push(ShareService.instance().shareUrl(share)); clipboard.writeText(links.join('\n')); }; @@ -109,15 +115,13 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) { setSharesState('creating'); - const newShares = Object.assign({}, shares); + const newShares: StateShare[] = []; for (const note of notes) { const share = await service.shareNote(note.id); - newShares[note.id] = share; + newShares.push(share); } - setShares(newShares); - setSharesState('synchronizing'); await reg.waitForSyncFinishedThenSync(); setSharesState('creating'); @@ -125,6 +129,8 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) { copyLinksToClipboard(newShares); setSharesState('created'); + + await ShareService.instance().refreshShares(); } catch (error) { if (error.code === 404 && !hasSynced) { reg.logger().info('ShareNoteDialog: Note does not exist on server - trying to sync it.', error); @@ -142,34 +148,53 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) { } }; - const removeNoteButton_click = (event: any) => { - const newNotes = []; - for (let i = 0; i < notes.length; i++) { - const n = notes[i]; - if (n.id === event.noteId) continue; - newNotes.push(n); - } - setNotes(newNotes); + // const removeNoteButton_click = (event: any) => { + // const newNotes = []; + // for (let i = 0; i < notes.length; i++) { + // const n = notes[i]; + // if (n.id === event.noteId) continue; + // newNotes.push(n); + // } + // setNotes(newNotes); + // }; + + const unshareNoteButton_click = async (event: any) => { + await ShareService.instance().unshareNote(event.noteId); + await ShareService.instance().refreshShares(); }; - const renderNote = (note: any) => { - const removeButton = notes.length <= 1 ? null : ( - + const renderNote = (note: NoteEntity) => { + const unshareButton = !props.shares.find(s => s.note_id === note.id) ? null : ( + + // ); + + // const removeButton = notes.length <= 1 ? null : ( + // + // ); + return (
- {note.title}{removeButton} + {note.title}{unshareButton}
); }; const renderNoteList = (notes: any) => { const noteComps = []; - for (const noteId of Object.keys(notes)) { - noteComps.push(renderNote(notes[noteId])); + for (const note of notes) { + noteComps.push(renderNote(note)); } return
{noteComps}
; }; @@ -194,7 +219,12 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
{statusMessage(sharesState)}
{renderEncryptionWarningMessage()} - + ); } @@ -203,3 +233,11 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) { ); } + +const mapStateToProps = (state: AppState) => { + return { + shares: state.shareService.shares.filter(s => !!s.note_id), + }; +}; + +export default connect(mapStateToProps)(ShareNoteDialog as any); diff --git a/packages/app-desktop/gui/Sidebar/styles/index.ts b/packages/app-desktop/gui/Sidebar/styles/index.ts index 380e27ad14..dc5231759e 100644 --- a/packages/app-desktop/gui/Sidebar/styles/index.ts +++ b/packages/app-desktop/gui/Sidebar/styles/index.ts @@ -69,7 +69,6 @@ function listItemTextColor(props: any) { export const StyledListItemAnchor = styled.a` font-size: ${(props: any) => Math.round(props.theme.fontSize * 1.0833333)}px; - // font-weight: 500; text-decoration: none; color: ${(props: any) => listItemTextColor(props)}; cursor: default; diff --git a/packages/app-desktop/runForSharing.sh b/packages/app-desktop/runForSharing.sh index 1dc528d6d5..352c0cdc81 100755 --- a/packages/app-desktop/runForSharing.sh +++ b/packages/app-desktop/runForSharing.sh @@ -35,6 +35,7 @@ if [ "$1" == "1" ]; then echo 'mkbook "other"' >> "$CMD_FILE" echo 'use "shared"' >> "$CMD_FILE" echo 'mknote "note 1"' >> "$CMD_FILE" + echo 'mknote "note 2"' >> "$CMD_FILE" fi cd "$ROOT_DIR/packages/app-cli" diff --git a/packages/lib/models/Folder.ts b/packages/lib/models/Folder.ts index 63a4e72402..ac400da2ec 100644 --- a/packages/lib/models/Folder.ts +++ b/packages/lib/models/Folder.ts @@ -347,7 +347,8 @@ export default class Folder extends BaseItem { public static async updateResourceShareIds() { // Find all resources where share_id is different from parent note // share_id. Then update share_id on all these resources. Essentially it - // makes it match the resource share_id to the note share_id. + // makes it match the resource share_id to the note share_id. At the + // same time we also process the is_shared property. const rows = await this.db().selectAll(` SELECT r.id, n.share_id, n.is_shared FROM note_resources nr diff --git a/packages/lib/models/Note.ts b/packages/lib/models/Note.ts index f8b885bbc6..217ec903f0 100644 --- a/packages/lib/models/Note.ts +++ b/packages/lib/models/Note.ts @@ -307,7 +307,7 @@ export default class Note extends BaseItem { includeTimestamps: true, }, options); - const output = ['id', 'title', 'is_todo', 'todo_completed', 'todo_due', 'parent_id', 'encryption_applied', 'order', 'markup_language', 'is_conflict']; + const output = ['id', 'title', 'is_todo', 'todo_completed', 'todo_due', 'parent_id', 'encryption_applied', 'order', 'markup_language', 'is_conflict', 'is_shared']; if (options.includeTimestamps) { output.push('updated_time'); diff --git a/packages/lib/services/share/ShareService.ts b/packages/lib/services/share/ShareService.ts index 93ae1f25dd..0d4fe2a1a1 100644 --- a/packages/lib/services/share/ShareService.ts +++ b/packages/lib/services/share/ShareService.ts @@ -104,7 +104,7 @@ export default class ShareService { await Folder.updateAllShareIds(); } - public async shareNote(noteId: string) { + public async shareNote(noteId: string): Promise { const note = await Note.load(noteId); if (!note) throw new Error(`No such note: ${noteId}`); @@ -115,6 +115,24 @@ export default class ShareService { return share; } + public async unshareNote(noteId: string) { + const note = await Note.load(noteId); + if (!note) throw new Error(`No such note: ${noteId}`); + + const shares = await this.refreshShares(); + const noteShares = shares.filter(s => s.note_id === noteId); + + const promises: Promise[] = []; + + for (const share of noteShares) { + promises.push(this.deleteShare(share.id)); + } + + await Promise.all(promises); + + await Note.save({ id: note.id, is_shared: 0 }); + } + public shareUrl(share: StateShare): string { return `${this.api().baseUrl()}/shares/${share.id}`; } @@ -170,13 +188,15 @@ export default class ShareService { }); } - public async refreshShares() { + public async refreshShares(): Promise { const result = await this.loadShares(); this.store.dispatch({ type: 'SHARE_SET', shares: result.items, }); + + return result.items; } public async refreshShareUsers(shareId: string) { diff --git a/packages/lib/themes/light.ts b/packages/lib/themes/light.ts index fe6fc155e0..44fdabbf4f 100644 --- a/packages/lib/themes/light.ts +++ b/packages/lib/themes/light.ts @@ -11,7 +11,7 @@ const theme: Theme = { oddBackgroundColor: '#eeeeee', color: '#32373F', // For regular text colorError: 'red', - colorWarn: '#9A5B00', + colorWarn: 'rgb(228 86 0)', colorFaded: '#7C8B9E', // For less important text colorBright: '#000000', // For important text dividerColor: '#dddddd',