feat: bookmark add / get / remove
This commit is contained in:
parent
1c13d31afd
commit
688cb9654e
4
.env
4
.env
@ -1,4 +1,6 @@
|
||||
VERSION=1
|
||||
API_URL='https://pinmenote.com'
|
||||
SHORT_URL='https://pmn.cl'
|
||||
WEBSITE_URL='https://pinmenote.com'
|
||||
IS_PRODUCTION=true
|
||||
IS_PRODUCTION=true
|
||||
OBJ_LIST_LIMIT=10000
|
@ -1,4 +1,6 @@
|
||||
VERSION=1
|
||||
API_URL='http://localhost:3000'
|
||||
SHORT_URL='http://localhost:8001'
|
||||
WEBSITE_URL='http://localhost:4200'
|
||||
IS_PRODUCTION=false
|
||||
IS_PRODUCTION=false
|
||||
OBJ_LIST_LIMIT=10
|
2
@types
2
@types
@ -1 +1 @@
|
||||
Subproject commit 4675716d73e30b7dd967d51253b6cdf64412e8f0
|
||||
Subproject commit acd09b0317ad6153003f753b3e2c56376c64a1d9
|
@ -1,4 +1,21 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { BrowserStorageWrapper } from '../../service/browser.storage.wrapper';
|
||||
import { ObjAddIdCommand } from '../obj/obj-add-id.command';
|
||||
import { ObjNextIdCommand } from '../obj/obj-next-id.command';
|
||||
import { ObjectStoreKeys } from '../../keys/object.store.keys';
|
||||
import ICommand = Pinmenote.Common.ICommand;
|
||||
@ -11,20 +28,23 @@ export class BookmarkAddCommand implements ICommand<Promise<BookmarkDto>> {
|
||||
async execute(): Promise<BookmarkDto> {
|
||||
const key = `${ObjectStoreKeys.OBJECT_BOOKMARK}:${this.url.href}`;
|
||||
const id = await new ObjNextIdCommand().execute();
|
||||
|
||||
const data: BookmarkDto = {
|
||||
id,
|
||||
value: this.value,
|
||||
url: this.url,
|
||||
isDirectory: false
|
||||
url: this.url
|
||||
};
|
||||
await BrowserStorageWrapper.set(key, data);
|
||||
await this.addBookmarkToList(this.url);
|
||||
|
||||
await this.addBookmarkToList(id);
|
||||
|
||||
await new ObjAddIdCommand(id).execute();
|
||||
return data;
|
||||
}
|
||||
|
||||
private async addBookmarkToList(url: PinUrl): Promise<void> {
|
||||
const bookmarkList = (await BrowserStorageWrapper.get<string[] | undefined>(ObjectStoreKeys.BOOKMARK_LIST)) || [];
|
||||
bookmarkList.push(url.href);
|
||||
private async addBookmarkToList(id: number): Promise<void> {
|
||||
const bookmarkList = (await BrowserStorageWrapper.get<number[] | undefined>(ObjectStoreKeys.BOOKMARK_LIST)) || [];
|
||||
bookmarkList.push(id);
|
||||
await BrowserStorageWrapper.set(ObjectStoreKeys.BOOKMARK_LIST, bookmarkList);
|
||||
}
|
||||
}
|
||||
|
30
src/common/command/bookmark/bookmark-get.command.ts
Normal file
30
src/common/command/bookmark/bookmark-get.command.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { BrowserStorageWrapper } from '../../service/browser.storage.wrapper';
|
||||
import { ObjectStoreKeys } from '../../keys/object.store.keys';
|
||||
import BookmarkDto = Pinmenote.Bookmark.BookmarkDto;
|
||||
import ICommand = Pinmenote.Common.ICommand;
|
||||
import PinUrl = Pinmenote.Pin.PinUrl;
|
||||
|
||||
export class BookmarkGetCommand implements ICommand<Promise<BookmarkDto | undefined>> {
|
||||
constructor(private url: PinUrl) {}
|
||||
async execute(): Promise<BookmarkDto | undefined> {
|
||||
const key = `${ObjectStoreKeys.OBJECT_BOOKMARK}:${this.url.href}`;
|
||||
const bookmark = await BrowserStorageWrapper.get<BookmarkDto | undefined>(key);
|
||||
return bookmark;
|
||||
}
|
||||
}
|
@ -1,22 +1,42 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { BrowserStorageWrapper } from '../../service/browser.storage.wrapper';
|
||||
import { ObjRemoveIdCommand } from '../obj/obj-remove-id.command';
|
||||
import { ObjectStoreKeys } from '../../keys/object.store.keys';
|
||||
import BookmarkDto = Pinmenote.Bookmark.BookmarkDto;
|
||||
import ICommand = Pinmenote.Common.ICommand;
|
||||
import PinUrl = Pinmenote.Pin.PinUrl;
|
||||
|
||||
export class BookmarkRemoveCommand implements ICommand<Promise<void>> {
|
||||
constructor(private url: PinUrl) {}
|
||||
constructor(private bookmark: BookmarkDto) {}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
const key = `${ObjectStoreKeys.OBJECT_BOOKMARK}:${this.url.href}`;
|
||||
const key = `${ObjectStoreKeys.OBJECT_BOOKMARK}:${this.bookmark.url.href}`;
|
||||
await BrowserStorageWrapper.remove(key);
|
||||
await this.removeBookmarkFromList(this.url);
|
||||
|
||||
await this.removeBookmarkFromList(this.bookmark.id);
|
||||
|
||||
await new ObjRemoveIdCommand(this.bookmark.id).execute();
|
||||
}
|
||||
|
||||
private async removeBookmarkFromList(url: PinUrl): Promise<void> {
|
||||
const bookmarkList = (await BrowserStorageWrapper.get<string[] | undefined>(ObjectStoreKeys.BOOKMARK_LIST)) || [];
|
||||
private async removeBookmarkFromList(id: number): Promise<void> {
|
||||
const bookmarkList = (await BrowserStorageWrapper.get<number[] | undefined>(ObjectStoreKeys.BOOKMARK_LIST)) || [];
|
||||
await BrowserStorageWrapper.set(
|
||||
ObjectStoreKeys.BOOKMARK_LIST,
|
||||
bookmarkList.filter((u) => u !== url.href)
|
||||
bookmarkList.filter((u) => u !== id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,11 @@
|
||||
import { BrowserStorageWrapper } from '../../service/browser.storage.wrapper';
|
||||
import { ObjUpdateLastIdCommand } from './obj-update-last-id.command';
|
||||
import { ObjectStoreKeys } from '../../keys/object.store.keys';
|
||||
import { environmentConfig } from '../../environment';
|
||||
import ICommand = Pinmenote.Common.ICommand;
|
||||
|
||||
export class ObjAddIdCommand implements ICommand<Promise<void>> {
|
||||
private readonly listLimit = 10;
|
||||
private readonly listLimit = environmentConfig.objListLimit;
|
||||
|
||||
constructor(private id: number) {}
|
||||
async execute(): Promise<void> {
|
||||
@ -28,6 +29,7 @@ export class ObjAddIdCommand implements ICommand<Promise<void>> {
|
||||
let ids = await this.getList(listId);
|
||||
|
||||
// hit limit so create new list
|
||||
// this way we get faster writes and can batch
|
||||
if (ids.length >= this.listLimit) {
|
||||
listId += 1;
|
||||
ids = [];
|
||||
|
@ -33,6 +33,7 @@ interface EnvironmentConfig {
|
||||
isProduction: boolean;
|
||||
settings: SettingsConfig;
|
||||
version: number;
|
||||
objListLimit: number;
|
||||
}
|
||||
|
||||
export const environmentConfig: EnvironmentConfig = {
|
||||
@ -48,5 +49,6 @@ export const environmentConfig: EnvironmentConfig = {
|
||||
borderStyle: '2px solid #ff0000',
|
||||
videoDisplayTime: 5
|
||||
},
|
||||
version: 1
|
||||
objListLimit: parseInt(process.env.OBJ_LIST_LIMIT || '100000'),
|
||||
version: parseInt(process.env.VERSION || '1')
|
||||
};
|
||||
|
@ -21,13 +21,10 @@ export interface ObjLinkDto {
|
||||
|
||||
export interface ObjIdentityDto {
|
||||
user: string;
|
||||
fingerprint: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
export interface ObjEncryptionDto {
|
||||
encrypted: boolean;
|
||||
group: string;
|
||||
}
|
||||
|
||||
export enum ObjTypeDto {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2022 Michal Szczepanski.
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -20,6 +20,7 @@ import { ActiveTabStore } from '../../store/active-tab.store';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { BookmarkAddCommand } from '../../../common/command/bookmark/bookmark-add.command';
|
||||
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
|
||||
import { BookmarkGetCommand } from '../../../common/command/bookmark/bookmark-get.command';
|
||||
import BookmarkIcon from '@mui/icons-material/Bookmark';
|
||||
import { BookmarkRemoveCommand } from '../../../common/command/bookmark/bookmark-remove.command';
|
||||
import { BrowserApi } from '../../../common/service/browser.api.wrapper';
|
||||
@ -27,17 +28,21 @@ import { BusMessageType } from '../../../common/model/bus.model';
|
||||
import { LogManager } from '../../../common/popup/log.manager';
|
||||
import { PinPopupInitData } from '../../../common/model/pin.model';
|
||||
import { TinyEventDispatcher } from '../../../common/service/tiny.event.dispatcher';
|
||||
import BookmarkDto = Pinmenote.Bookmark.BookmarkDto;
|
||||
|
||||
export const ObjectCreateComponent: FunctionComponent = () => {
|
||||
const [isAdding, setIsAdding] = useState<boolean>(ActiveTabStore.isAddingNote);
|
||||
const [isBookmarked, setIsBookmarked] = useState<boolean>(ActiveTabStore.isBookmarked);
|
||||
const [bookmarkData, setBookmarkData] = useState<BookmarkDto | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const addingKey = TinyEventDispatcher.addListener<PinPopupInitData>(
|
||||
BusMessageType.POPUP_INIT,
|
||||
(event, key, value) => {
|
||||
async (event, key, value) => {
|
||||
setIsAdding(value.isAddingNote);
|
||||
setIsBookmarked(value.isBookmarked);
|
||||
if (ActiveTabStore.url) {
|
||||
const bookmark = await new BookmarkGetCommand(ActiveTabStore.url).execute();
|
||||
setBookmarkData(bookmark);
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
@ -69,14 +74,14 @@ export const ObjectCreateComponent: FunctionComponent = () => {
|
||||
|
||||
const handleBookmarkAdd = async () => {
|
||||
if (!ActiveTabStore.url) return;
|
||||
await new BookmarkAddCommand(ActiveTabStore.pageTitle, ActiveTabStore.url).execute();
|
||||
window.close();
|
||||
const bookmark = await new BookmarkAddCommand(ActiveTabStore.pageTitle, ActiveTabStore.url).execute();
|
||||
setBookmarkData(bookmark);
|
||||
};
|
||||
|
||||
const handleBookmarkRemove = async () => {
|
||||
if (!ActiveTabStore.url) return;
|
||||
await new BookmarkRemoveCommand(ActiveTabStore.url).execute();
|
||||
window.close();
|
||||
if (!bookmarkData) return;
|
||||
await new BookmarkRemoveCommand(bookmarkData).execute();
|
||||
setBookmarkData(undefined);
|
||||
};
|
||||
|
||||
const pinBtn = isAdding ? (
|
||||
@ -89,15 +94,16 @@ export const ObjectCreateComponent: FunctionComponent = () => {
|
||||
</Button>
|
||||
);
|
||||
|
||||
const bookmarkBtn = isBookmarked ? (
|
||||
<IconButton title="Remove bookmark" onClick={handleBookmarkRemove}>
|
||||
<BookmarkIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton title="Add bookmark" onClick={handleBookmarkAdd}>
|
||||
<BookmarkBorderIcon />
|
||||
</IconButton>
|
||||
);
|
||||
const bookmarkBtn =
|
||||
bookmarkData !== undefined ? (
|
||||
<IconButton title="Remove bookmark" onClick={handleBookmarkRemove}>
|
||||
<BookmarkIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton title="Add bookmark" onClick={handleBookmarkAdd}>
|
||||
<BookmarkBorderIcon />
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
|
72
src/options-ui/view/menu/board-search.input.tsx
Normal file
72
src/options-ui/view/menu/board-search.input.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { ChangeEvent, FunctionComponent, useState } from 'react';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { IconButton } from '@mui/material';
|
||||
import Input from '@mui/material/Input';
|
||||
import { PinBoardStore } from '../store/pin-board.store';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { fnConsoleLog } from '../../../common/fn/console.fn';
|
||||
|
||||
export const BoardSearchInput: FunctionComponent = () => {
|
||||
const [searchValue, setSearchValue] = useState<string>(PinBoardStore.getSearch() || '');
|
||||
|
||||
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
fnConsoleLog('handleSearchChange');
|
||||
clearTimeout(PinBoardStore.timeout);
|
||||
setSearchValue(e.target.value);
|
||||
PinBoardStore.clearSearch();
|
||||
// setPinData([]);
|
||||
if (e.target.value.length <= 2) {
|
||||
PinBoardStore.timeout = window.setTimeout(async () => {
|
||||
await PinBoardStore.sendRange();
|
||||
}, 1000);
|
||||
return;
|
||||
} else {
|
||||
PinBoardStore.setSearch(e.target.value);
|
||||
}
|
||||
PinBoardStore.timeout = window.setTimeout(async () => {
|
||||
await PinBoardStore.sendSearch();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleClearSearch = async () => {
|
||||
fnConsoleLog('handleClearSearch');
|
||||
setSearchValue('');
|
||||
PinBoardStore.clearSearch();
|
||||
await PinBoardStore.sendRange();
|
||||
};
|
||||
return (
|
||||
<div style={{ width: '50%' }}>
|
||||
<Input
|
||||
startAdornment={<SearchIcon />}
|
||||
placeholder="Find object"
|
||||
style={{ width: '100%' }}
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
endAdornment={
|
||||
PinBoardStore.getSearch() ? (
|
||||
<IconButton onClick={handleClearSearch}>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
|
||||
* Copyright (c) 2022 Michal Szczepanski.
|
||||
* Copyright (c) 2023 Michal Szczepanski.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -14,23 +14,21 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { ChangeEvent, FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
import { BoardSearchInput } from '../menu/board-search.input';
|
||||
import Box from '@mui/material/Box';
|
||||
import { BusMessageType } from '../../../common/model/bus.model';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { IconButton } from '@mui/material';
|
||||
import Input from '@mui/material/Input';
|
||||
import { PinBoardStore } from '../store/pin-board.store';
|
||||
import { PinElement } from './pin.element';
|
||||
import { PinObject } from '../../../common/model/pin.model';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { TinyEventDispatcher } from '../../../common/service/tiny.event.dispatcher';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { fnConsoleLog } from '../../../common/fn/console.fn';
|
||||
|
||||
export const PinBoard: FunctionComponent = () => {
|
||||
const [pinData, setPinData] = useState<PinObject[]>(PinBoardStore.pins);
|
||||
const [searchValue, setSearchValue] = useState<string>(PinBoardStore.getSearch() || '');
|
||||
|
||||
const stackRef = useRef<HTMLDivElement>();
|
||||
|
||||
@ -46,7 +44,7 @@ export const PinBoard: FunctionComponent = () => {
|
||||
BusMessageType.OPTIONS_PIN_SEARCH,
|
||||
(event, key, value) => {
|
||||
PinBoardStore.pins.push(...value);
|
||||
setSearchValue(PinBoardStore.getSearch() || '');
|
||||
// setSearchValue(PinBoardStore.getSearch() || '');
|
||||
setPinData(PinBoardStore.pins.concat());
|
||||
PinBoardStore.setLoading(false);
|
||||
}
|
||||
@ -99,50 +97,13 @@ export const PinBoard: FunctionComponent = () => {
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
fnConsoleLog('handleSearchChange');
|
||||
clearTimeout(PinBoardStore.timeout);
|
||||
setSearchValue(e.target.value);
|
||||
PinBoardStore.clearSearch();
|
||||
setPinData([]);
|
||||
if (e.target.value.length <= 2) {
|
||||
PinBoardStore.timeout = window.setTimeout(async () => {
|
||||
await PinBoardStore.sendRange();
|
||||
}, 1000);
|
||||
return;
|
||||
} else {
|
||||
PinBoardStore.setSearch(e.target.value);
|
||||
}
|
||||
PinBoardStore.timeout = window.setTimeout(async () => {
|
||||
await PinBoardStore.sendSearch();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleClearSearch = async () => {
|
||||
fnConsoleLog('handleClearSearch');
|
||||
setSearchValue('');
|
||||
PinBoardStore.clearSearch();
|
||||
await PinBoardStore.sendRange();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', marginLeft: 20, marginTop: 10 }}>
|
||||
<Box style={{ margin: 10 }}>
|
||||
<Input
|
||||
startAdornment={<SearchIcon />}
|
||||
placeholder="Find note"
|
||||
style={{ width: '50%' }}
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
endAdornment={
|
||||
PinBoardStore.getSearch() ? (
|
||||
<IconButton onClick={handleClearSearch}>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
<Box style={{ margin: 10, display: 'flex', flexDirection: 'row' }}>
|
||||
<BoardSearchInput></BoardSearchInput>
|
||||
<IconButton>
|
||||
<Typography>aaaa</Typography>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Stack direction="row" flexWrap="wrap" ref={stackRef} style={{ overflow: 'auto', height: 'calc(100vh - 65px)' }}>
|
||||
{pinData.map((pin) => (
|
||||
|
Loading…
Reference in New Issue
Block a user