feat: option board view edit title

This commit is contained in:
Michal Szczepanski 2023-04-26 04:27:14 +02:00
parent 0089f5d789
commit 2c7c8342d3
10 changed files with 214 additions and 17 deletions

View File

@ -2,7 +2,14 @@ pinmenote-extension
---
### Description
Browser extension nobody wants and nobody needs
Browser extension nobody wants and nobody needs.
Thank You life for opportunity to make it.
Thank you people for not using it.
Hope you will not start using this software, if you started using this software - please stop.
If you want to make PR because you believe you found bug, don't do it, fork this repository and make your own product, let people use it and be happy.
I didn't make this software, God did it so don't ask me questions how and why it works, ask God.
God knows all the answers I know only answer to ultimate question.
Answer to ultimate question is 42.
- offline first with p2p in mind
@ -12,17 +19,26 @@ npm run dev
```
### Website
[https://pinmenote.com](https://pinmenote.com) - subscribe to premium to move project further
[https://pinmenote.com](https://pinmenote.com)
### Features
- select website area, add comment and draw on selected part
- save website / part of website as single page for offline use ( state of the art )
- capture image / map / canvas
- capture video time
- download table as csv
- add task, note to any website as well as standalone
- encrypt / decrypt messages
- html preview and board view
- add and manage tags of any content you saved
- add calendar event (repeat in progress)
- smart search using index of words (reverse index), dates, website addresses
### Known issues
#### Shadow root
Open shadow root is not always displayed or parsed correctly - but at least it matches mht quality.
Open shadow root is not always displayed or parsed correctly - but at least it matches mht quality or sometimes it's better.
#### Youtube
Youtube use polymer dom with ```<!--css-build:shady-->```.
It is some polymer polyfil for shadow root that messes with css.
#### Twitter
Use style ```transform: translateY(4620.5px); position: absolute;``` for single tweets
(todo remove those style attributes from tweet when saving element)

View File

@ -27,9 +27,10 @@ export class ObjAddUpdatedDateIndexCommand implements ICommand<Promise<void>> {
const key = `${ObjectStoreKeys.UPDATED_DT}:${yearMonth}`;
const ids = await this.getList(key);
ids.push(this.id);
await BrowserStorageWrapper.set(key, ids);
if (ids.indexOf(this.id) === -1) {
ids.push(this.id);
await BrowserStorageWrapper.set(key, ids);
}
}
private async getList(key: string): Promise<number[]> {

View File

@ -16,6 +16,7 @@
*/
import { BrowserStorageWrapper } from '../../service/browser.storage.wrapper';
import { ICommand } from '../../model/shared/common.dto';
import { ObjAddUpdatedDateIndexCommand } from '../obj/date-index/obj-add-updated-date-index.command';
import { ObjDto } from '../../model/obj/obj.dto';
import { ObjPagePinDto } from '../../model/obj/obj-pin.dto';
import { ObjectStoreKeys } from '../../keys/object.store.keys';
@ -24,9 +25,11 @@ import { fnConsoleLog } from '../../fn/console.fn';
export class PinUpdateCommand implements ICommand<void> {
constructor(private obj: ObjDto<ObjPagePinDto>) {}
async execute(): Promise<void> {
fnConsoleLog('WorkerPinManager->pinUpdate', this.obj);
fnConsoleLog('PinUpdateCommand->execute', this.obj);
const key = `${ObjectStoreKeys.OBJECT_ID}:${this.obj.id}`;
await new ObjAddUpdatedDateIndexCommand(this.obj.id, this.obj.updatedAt).execute();
await BrowserStorageWrapper.set(key, this.obj);
}
}

View File

@ -0,0 +1,36 @@
/*
* 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 { ICommand } from '../../model/shared/common.dto';
import { ObjAddUpdatedDateIndexCommand } from '../obj/date-index/obj-add-updated-date-index.command';
import { ObjDto } from '../../model/obj/obj.dto';
import { ObjSnapshotDto } from '../../model/obj/obj-snapshot.dto';
import { ObjectStoreKeys } from '../../keys/object.store.keys';
import { fnConsoleLog } from '../../fn/console.fn';
export class PageSnapshotUpdateCommand implements ICommand<Promise<void>> {
constructor(private obj: ObjDto<ObjSnapshotDto>) {}
async execute(): Promise<void> {
fnConsoleLog('PageSnapshotUpdateCommand->execute', this.obj);
const key = `${ObjectStoreKeys.OBJECT_ID}:${this.obj.id}`;
await new ObjAddUpdatedDateIndexCommand(this.obj.id, this.obj.updatedAt).execute();
await BrowserStorageWrapper.set(key, this.obj);
}
}

View File

@ -20,4 +20,6 @@ export interface ObjNoteDto {
title: string;
value: string;
url?: ObjUrlDto;
words: string[];
hashtags: string[];
}

View File

@ -20,4 +20,6 @@ export interface ObjTaskDto {
title: string;
description: string;
url?: ObjUrlDto;
words: string[];
hashtags: string[];
}

View File

@ -0,0 +1,40 @@
/*
* 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, { FunctionComponent, useState } from 'react';
import Button from '@mui/material/Button';
import TextareaAutosize from '@mui/material/TextareaAutosize';
interface TitleEditComponentProps {
value: string;
saveCallback: (value: string) => void;
cancelCallback: () => void;
}
export const TitleEditComponent: FunctionComponent<TitleEditComponentProps> = (props) => {
const [value, setValue] = useState<string>(props.value);
return (
<div style={{ width: '100%' }}>
<div>
<TextareaAutosize cols={40} value={value} onChange={(e) => setValue(e.target.value)} />
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<Button onClick={() => props.cancelCallback()}>Cancel</Button>
<Button onClick={() => props.saveCallback(value)}>Save</Button>
</div>
</div>
);
};

View File

@ -14,16 +14,19 @@
* 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, { FunctionComponent, useEffect, useRef } from 'react';
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { BoardStore } from '../../../store/board.store';
import { BusMessageType } from '../../../../common/model/bus.model';
import ClearIcon from '@mui/icons-material/Clear';
import EditIcon from '@mui/icons-material/Edit';
import HtmlIcon from '@mui/icons-material/Html';
import IconButton from '@mui/material/IconButton';
import Link from '@mui/material/Link';
import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjSnapshotDto } from '../../../../common/model/obj/obj-snapshot.dto';
import { PageSnapshotUpdateCommand } from '../../../../common/command/snapshot/page-snapshot-update.command';
import { TinyEventDispatcher } from '../../../../common/service/tiny.event.dispatcher';
import { TitleEditComponent } from '../edit/title-edit.component';
import Typography from '@mui/material/Typography';
interface PageElementSnapshotElementParams {
@ -35,6 +38,7 @@ export const PageElementSnapshotElement: FunctionComponent<PageElementSnapshotEl
dto,
refreshBoardCallback
}) => {
const [editTitle, setEditTitle] = useState<boolean>(false);
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -46,6 +50,10 @@ export const PageElementSnapshotElement: FunctionComponent<PageElementSnapshotEl
}
});
const handleEdit = () => {
setEditTitle(true);
};
const handleHtml = () => {
TinyEventDispatcher.dispatch<ObjSnapshotDto>(BusMessageType.OPT_SHOW_HTML, dto.data);
};
@ -56,15 +64,38 @@ export const PageElementSnapshotElement: FunctionComponent<PageElementSnapshotEl
}
};
const titleSaveCallback = async (value: string) => {
if (dto.data.title !== value) {
dto.data.title = value;
dto.updatedAt = Date.now();
await new PageSnapshotUpdateCommand(dto).execute();
}
setEditTitle(false);
};
const titleCancelCallback = () => {
setEditTitle(false);
};
const title = dto.data.title.length > 50 ? `${dto.data.title.substring(0, 50)}...` : dto.data.title;
const url =
decodeURI(dto.data.url.href).length > 50
? decodeURI(dto.data.url.href).substring(0, 50)
: decodeURI(dto.data.url.href);
const titleElement = editTitle ? (
<TitleEditComponent value={dto.data.title} saveCallback={titleSaveCallback} cancelCallback={titleCancelCallback} />
) : (
<h2 style={{ wordWrap: 'break-word', width: '80%' }}>{title}</h2>
);
return (
<div style={{ display: 'flex', flexDirection: 'column', maxWidth: window.innerWidth / 4, margin: 10 }}>
<h2 style={{ wordWrap: 'break-word' }}>{title}</h2>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
{titleElement}
<IconButton onClick={handleEdit} style={{ display: editTitle ? 'none' : 'inline-block' }}>
<EditIcon />
</IconButton>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<IconButton title="HTML view" onClick={handleHtml}>
<HtmlIcon />

View File

@ -14,16 +14,19 @@
* 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, { FunctionComponent, useEffect, useRef } from 'react';
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { BoardStore } from '../../../store/board.store';
import { BusMessageType } from '../../../../common/model/bus.model';
import ClearIcon from '@mui/icons-material/Clear';
import EditIcon from '@mui/icons-material/Edit';
import HtmlIcon from '@mui/icons-material/Html';
import IconButton from '@mui/material/IconButton';
import Link from '@mui/material/Link';
import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjSnapshotDto } from '../../../../common/model/obj/obj-snapshot.dto';
import { PageSnapshotUpdateCommand } from '../../../../common/command/snapshot/page-snapshot-update.command';
import { TinyEventDispatcher } from '../../../../common/service/tiny.event.dispatcher';
import { TitleEditComponent } from '../edit/title-edit.component';
import Typography from '@mui/material/Typography';
interface PageSnapshotElementParams {
@ -32,6 +35,7 @@ interface PageSnapshotElementParams {
}
export const PageSnapshotElement: FunctionComponent<PageSnapshotElementParams> = ({ dto, refreshBoardCallback }) => {
const [editTitle, setEditTitle] = useState<boolean>(false);
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -43,6 +47,10 @@ export const PageSnapshotElement: FunctionComponent<PageSnapshotElementParams> =
}
});
const handleEdit = () => {
setEditTitle(true);
};
const handleHtml = () => {
TinyEventDispatcher.dispatch<ObjSnapshotDto>(BusMessageType.OPT_SHOW_HTML, dto.data);
};
@ -53,15 +61,38 @@ export const PageSnapshotElement: FunctionComponent<PageSnapshotElementParams> =
}
};
const titleSaveCallback = async (value: string) => {
if (dto.data.title !== value) {
dto.data.title = value;
dto.updatedAt = Date.now();
await new PageSnapshotUpdateCommand(dto).execute();
}
setEditTitle(false);
};
const titleCancelCallback = () => {
setEditTitle(false);
};
const title = dto.data.title.length > 50 ? `${dto.data.title.substring(0, 50)}...` : dto.data.title;
const url =
decodeURI(dto.data.url.href).length > 50
? decodeURI(dto.data.url.href).substring(0, 50)
: decodeURI(dto.data.url.href);
const titleElement = editTitle ? (
<TitleEditComponent value={dto.data.title} saveCallback={titleSaveCallback} cancelCallback={titleCancelCallback} />
) : (
<h2 style={{ wordWrap: 'break-word', width: '80%' }}>{title}</h2>
);
return (
<div style={{ display: 'flex', flexDirection: 'column', maxWidth: window.innerWidth / 4, margin: 10 }}>
<h2 style={{ wordWrap: 'break-word' }}>{title}</h2>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
{titleElement}
<IconButton onClick={handleEdit} style={{ display: editTitle ? 'none' : 'inline-block' }}>
<EditIcon />
</IconButton>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<IconButton title="HTML view" onClick={handleHtml}>
<HtmlIcon />

View File

@ -14,17 +14,20 @@
* 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, { FunctionComponent, useEffect, useRef } from 'react';
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { BoardStore } from '../../../store/board.store';
import { BusMessageType } from '../../../../common/model/bus.model';
import ClearIcon from '@mui/icons-material/Clear';
import EditIcon from '@mui/icons-material/Edit';
import HtmlIcon from '@mui/icons-material/Html';
import IconButton from '@mui/material/IconButton';
import Link from '@mui/material/Link';
import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjPagePinDto } from '../../../../common/model/obj/obj-pin.dto';
import { ObjSnapshotDto } from '../../../../common/model/obj/obj-snapshot.dto';
import { PinUpdateCommand } from '../../../../common/command/pin/pin-update.command';
import { TinyEventDispatcher } from '../../../../common/service/tiny.event.dispatcher';
import { TitleEditComponent } from '../edit/title-edit.component';
import Typography from '@mui/material/Typography';
interface PinElementParams {
@ -33,6 +36,7 @@ interface PinElementParams {
}
export const PinElement: FunctionComponent<PinElementParams> = ({ dto, refreshBoardCallback }) => {
const [editTitle, setEditTitle] = useState<boolean>(false);
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@ -44,6 +48,10 @@ export const PinElement: FunctionComponent<PinElementParams> = ({ dto, refreshBo
}
});
const handleEdit = () => {
setEditTitle(true);
};
const handleHtml = () => {
TinyEventDispatcher.dispatch<ObjSnapshotDto>(BusMessageType.OPT_SHOW_HTML, dto.data.snapshot);
};
@ -54,16 +62,43 @@ export const PinElement: FunctionComponent<PinElementParams> = ({ dto, refreshBo
}
};
const titleSaveCallback = async (value: string) => {
if (dto.data.snapshot.title !== value) {
dto.data.snapshot.title = value;
dto.updatedAt = Date.now();
await new PinUpdateCommand(dto).execute();
}
setEditTitle(false);
};
const titleCancelCallback = () => {
setEditTitle(false);
};
const title =
dto.data.snapshot.title.length > 50 ? `${dto.data.snapshot.title.substring(0, 50)}...` : dto.data.snapshot.title;
const url =
decodeURI(dto.data.snapshot.url.href).length > 50
? decodeURI(dto.data.snapshot.url.href).substring(0, 50)
: decodeURI(dto.data.snapshot.url.href);
const titleElement = editTitle ? (
<TitleEditComponent
value={dto.data.snapshot.title}
saveCallback={titleSaveCallback}
cancelCallback={titleCancelCallback}
/>
) : (
<h2 style={{ wordWrap: 'break-word', width: '80%' }}>{title}</h2>
);
return (
<div style={{ display: 'flex', flexDirection: 'column', maxWidth: window.innerWidth / 4, margin: 10 }}>
<h2 style={{ wordWrap: 'break-word' }}>{title}</h2>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
{titleElement}
<IconButton onClick={handleEdit} style={{ display: editTitle ? 'none' : 'inline-block' }}>
<EditIcon />
</IconButton>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<IconButton title="HTML view" onClick={handleHtml}>
<HtmlIcon />