web: ui and some functuanality is added.

This commit is contained in:
alihamuh
2023-02-15 10:56:14 +05:00
committed by Abdullah Atta
parent 0c6180e78c
commit 9beb506bbf
5 changed files with 827 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
.react-datepicker-wrapper,
.react-datepicker__input-container,
.react-datepicker__input-container input {
display: block;
width: auto;
}

View File

@@ -0,0 +1,327 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
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, either version 3 of the License, or
(at your option) any later version.
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 { useEffect, forwardRef, useRef } from "react";
import { hexToRGB } from "../../utils/color";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "./datepicker.css";
import { filterSearchEngine } from "./search";
import { Text } from "@theme-ui/components";
import { mainSearchEngine } from "./search";
export function FilterInput(props) {
const {
filters,
focusInput,
index,
setFilters,
item,
getSuggestions,
setSuggestions,
onFocus,
onBlur,
inputRef,
searchDefinitions
} = props;
useEffect(() => {
focusInput(filters.length - 1);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters.length]);
console.log();
// useEffect(() => {
// (async () => {
// console.log(filterRef.current.target.innerText);
// //await checkErrors(props, inputRef.filter.target.innerText);
// //addDefinition(item.input, searchDefinitions);
// })();
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [filterRef.current.target.innerText]);
const setCalenderState = (state) => {
setFilters((filters) => {
let inputs = [...filters];
inputs[index].input.isCalenderOpen = state;
return inputs;
});
};
const setCalenderDate = () => {
document.getElementById(`inputId_${index}`).textContent =
filters[index].input.date.formatted;
};
return !item.input.isDateFilter ? (
<CustomInput
{...props}
id={`inputId_${index}`}
bg={item.input.state.error ? "errorBg" : hexToRGB("#9E9E9E", 0.1)}
onFocus={async (e) => {
await checkErrors(props, e.target.textContent);
setSuggestions(await getSuggestions(e.target.textContent, item.input));
onFocus(e);
}}
onBlur={onBlur}
onKeyDown={async (e) => {
(await onKeyPress(e, props))[e.key]();
}}
/>
) : (
<DatePicker
{...props}
customInput={<CustomInput />}
peekNextMonth
showMonthDropdown
showYearDropdown
dropdownMode="select"
id={`inputId_${index}`}
selected={filters[index].input.date.orignal}
onCalendarClose={() => {
setCalenderDate();
document.getElementById(`inputId_${index}`).focus();
}}
onCalendarOpen={() => {
setCalenderDate();
}}
onChange={(date) => {
let inputs = [...filters];
inputs[index].input.date.formatted = `${date.getDate()}/${
date.getMonth() + 1
}/${date.getFullYear()}`;
inputs[index].input.date.orignal = date;
setFilters(inputs);
}}
onBlur={(e) => {
setCalenderState(false);
onBlur(e);
}}
onFocus={async (e) => {
setCalenderState(true);
setSuggestions(
//shift to top
await getSuggestions(e.target.textContent, item.input)
);
onFocus(e);
}}
onKeyDown={async (e) => {
(await onKeyPress(e, props))[e.key]();
}}
/>
);
}
const CustomInput = forwardRef((props, refs) => (
<Text
ref={refs}
tabIndex={0}
as="span"
contentEditable="true"
type="text"
sx={{
width: "10%",
py: "2.5px",
px: "6px",
fontSize: "input",
flexShrink: 0,
flexGrow: 1,
boxShadow: "none",
outline: "none",
":focus": {
boxShadow: "none",
border: "none"
},
":hover:not(:focus)": {
boxShadow: "none"
}
}}
{...props}
/>
));
CustomInput.displayName = "CustomInput";
const getCursorPosition = (editableDiv) => {
//it is a general method, it should be somehwre else
var caretPos = 0,
sel,
range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
};
const addDefinition = (input, definitions) => {
//hard to understand// minimize if else
//for (let filter of filters) console.log(filter.input.state.result);
if (input.state.result) {
let isArrayEmpty = false;
for (let index = 0; index < definitions.length; index++) {
if (definitions[index].srNo === input.id) {
isArrayEmpty = true;
input.state.result.srNo = input.id;
definitions[index] = input.state.result;
}
}
if (!isArrayEmpty) {
input.state.result.srNo = input.id;
definitions.push(input.state.result);
}
}
};
const onClick = (props, query) => {
checkErrors(props, query);
};
const checkErrors = async (props, query) => {
const { setFilters, index, item } = props;
query = query.trim();
let input = item.input;
let result = await (await mainSearchEngine(input.type, query)).result;
setFilters((filters) => {
let _filters = [...filters];
_filters[index].input.state = filterInputState(input, result, query);
return _filters;
});
};
const filterInputState = (input, result, query) => {
console.log(
"filterInputState",
input.id,
query,
!input.hasSuggestions,
result.length > 0
);
if (!input.hasSuggestions)
return {
error: false,
message: "",
result: { type: input.type, value: query }
};
if (result.length > 0)
return { error: false, message: "", result: result[0] };
return {
error: true,
message: `This ${input.type.replace(
"s",
""
)} is not present in the database.`,
result: undefined
};
};
const deleteDefinition = (definitions, id) => {
let index = 0;
for (let definition of definitions) {
if (definition.srNo === id) {
definitions.splice(index, 1);
}
index++;
}
};
const deleteFilter = (advanceInputs, index) => {
advanceInputs.splice(index, 1);
return advanceInputs;
};
const onKeyPress = async (e, props) => {
const {
filters,
focusInput,
index,
setFilters,
item,
getSuggestions,
setSuggestions,
onSearch,
searchDefinitions,
setSelectionIndex,
suggestions,
moveSelection
} = props;
setSuggestions(await getSuggestions(e.target.textContent, item.input));
await checkErrors(props, e.target.textContent);
return {
Enter: async () => {
props.onKeyDown(e);
focusInput(index + 1);
let results = await filterSearchEngine(searchDefinitions);
onSearch(results);
setSuggestions([]);
//await checkErrors(props, e.target.innerText);
addDefinition(item.input, searchDefinitions, filters);
e.preventDefault();
},
Escape: () => {
setSuggestions([]);
},
ArrowDown: () => {
moveSelection(suggestions, setSelectionIndex).Down();
e.preventDefault();
},
ArrowUp: () => {
moveSelection(suggestions, setSelectionIndex).Up();
e.preventDefault();
},
ArrowLeft: () => {
if (getCursorPosition(document.getElementById(e.target.id)) == 0) {
addDefinition(item.input, searchDefinitions);
focusInput(index - 1);
}
},
ArrowRight: () => {
if (
getCursorPosition(document.getElementById(e.target.id)) ==
e.target.textContent.length
) {
addDefinition(item.input, searchDefinitions);
focusInput(index + 1);
}
},
Backspace: () => {
if (e.target.textContent === "") {
setSuggestions([]);
setFilters(deleteFilter(filters, index));
deleteDefinition(searchDefinitions, item.input.id);
focusInput(index - 1);
}
}
};
};

View File

@@ -0,0 +1,113 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
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, either version 3 of the License, or
(at your option) any later version.
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 { useEffect, useState } from "react";
import { db } from "../../common/db";
import { addToSearchHistory } from "./search";
import { Input } from "@theme-ui/components";
export function MainInput(props) {
const [searchHistory, setSearchHistory] = useState();
useEffect(async () => {
//correct all the useEffects
//history file to be added in core for search history
let list = await db.searchHistory.getHistory();
if (props.filters.length < 1) {
setSearchHistory(list);
} else {
setSearchHistory([]);
}
}, [db.searchHistory, props.filters.length]);
return (
<Input
{...props}
id="general_input"
key="general_input"
placeholder={"Type your query here"}
bg={"none"}
autoFocus
as="input"
name="search"
autoComplete="off"
type="text"
variant="clean"
sx={{
wordWrap: "break-word",
minWidth: 0,
width: 0,
flex: 1
}}
onChange={async (e) => {
props.refreshFilters(e.target.value, props.setFilters);
props.setSuggestions(
await props.getSuggestions(e.target.value, undefined, searchHistory)
);
}}
onFocus={async (e) => {
//shift to index
props.setSuggestions(
await props.getSuggestions(e.target.value, undefined, searchHistory)
);
props.onFocus(e);
}}
onKeyDown={async (e) => {
//keyActions(e);
props.onKeyDown(e);
await onKeyPress[e.key](e, props);
}}
/>
);
}
const onKeyPress = {
Enter: async (e, props) => {
props.onSearch(e.target.value);
await addToSearchHistory(e.target.value);
props.setSuggestions([]);
props.refreshFilters(e.target.value, props.setFilters);
e.preventDefault();
},
Escape: (e, props) => {
props.setSuggestions([]);
},
ArrowDown: (e, props) => {
props.moveSelection(props.suggestions, props.setSelectionIndex).Down();
e.preventDefault();
},
ArrowUp: (e, props) => {
props.moveSelection(props.suggestions, props.setSelectionIndex).Up();
e.preventDefault();
},
ArrowLeft: (e, props) => {
if (e.target.selectionStart == 0) {
props.focusInput(props.filters.length - 1);
}
},
ArrowRight: (e, props) => {
if (e.target.selectionStart == e.target.value.length) {
props.focusInput(0);
}
},
Backspace: (e, props) => {
if (e.target.selectionStart == 0) {
props.focusInput(props.filters.length - 1);
}
}
};

View File

@@ -0,0 +1,325 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
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, either version 3 of the License, or
(at your option) any later version.
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 { store } from "../../stores/note-store";
import { db } from "../../common/db";
import { filter, parse } from "liqe";
export const mainSearchEngine = async (searchType, value) => {
//name is cumbersome
// better name? fetchResults
const context = store.context;
const [lookupType, allData] = await filterItemsToType(searchType, context);
if (lookupType !== undefined && allData !== undefined) {
let result = await db.lookup[lookupType](allData, value);
return { result, allData };
} else {
return { result: [], allData: [] };
}
};
/*
there is mainly two types of search:
filter search
main search
filter search is again divided into two: Why not make it one?
how to search?
db.notes.all === all notes
notebooks:all the notes in the notebook
topics: all the notes in the topic
tags: all notes in the tag
dates:all notes between the dates
notes: all notes with the word
inTitle: all notes with the word in title
the search will condense. The scope will decrease with the number of filters
*/
export const filterSearchEngine = async (definitions) => {
//name is cumbersome
let notes = [];
let searchlabels = [];
const organizingNotesFilters = ["notebook", "topic", "tag"];
let isOrganizingNotesFilter = false;
await db.notes.init();
console.log("search", definitions);
for (let definition of definitions) {
if (organizingNotesFilters.includes(definition.type))
isOrganizingNotesFilter = true;
}
for (let definition of definitions) {
switch (definition.type) {
case "notebook":
//isOrganizingNotesFilter = true; //this boolean should be done seperately
for (let topic of definition.topics) {
for (let note of topic.notes) {
if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
}
}
break;
case "topic":
//isOrganizingNotesFilter = true;
for (let note of definition.notes) {
if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
}
break;
case "tag":
//isOrganizingNotesFilter = true;
for (let note of definition.noteIds) {
if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
}
break;
default:
searchlabels.push(definition);
}
}
// for (let definition of definitions) { //switch statements or consts
// if (definition.type === "notebook") {
// isHigherlabelPresent = true;
// for (let topic of definition.topics) {
// for (let note of topic.notes) {
// if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
// }
// }
// } else if (definition.type === "topic") {
// isHigherlabelPresent = true;
// for (let note of definition.notes) {
// if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
// }
// } else if (definition.type === "tag") {
// isHigherlabelPresent = true;
// for (let note of definition.noteIds) {
// if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
// }
// } else if (
// definition.type === "before" ||
// definition.type === "after" ||
// definition.type === "during" ||
// definition.type === "notes" ||
// definition.type === "intitle"
// ) {
// searchlabels.push(definition);
// //notes = await dateSearch(definition.value, definition.type);
// }
// }
let result = [];
if (searchlabels.length > 0) {
for (let label of searchlabels) {
if (notes.length > 0) {
if (label.type === "notes") {
result = await db.lookup["notes"](notes, label.value);
} else if (label.type === "intitle") {
result = db.lookup["_byTitle"](notes, label.value);
} else {
result = await dateSearchEngine(label.value, label.type, notes);
}
} else if (!isOrganizingNotesFilter) {
let allNotes = db.notes.all;
if (label.type === "notes") {
let search = await db.lookup["notes"](allNotes, label.value);
result.push(...search);
} else if (label.type === "intitle") {
let search = await db.lookup["_byTitle"](allNotes, label.value);
result.push(...search);
} else {
result = await dateSearchEngine(label.value, label.type, allNotes);
}
}
}
return result;
} else {
return notes;
}
};
//definitions will first be sorted in following order: nbks> topics>tags>times>notes>intitle,
// const someMethod = async (definition,notes) => {
// await db.notes.init();
// return {
// notebook: () => {
// for (let topic of definition.topics) { // noltebooks and topics will be sorted for search in one method
// for (let note of topic.notes) {
// if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
// }
// }
// },
// topic: () => {
// for (let note of definition.notes) {
// if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
// }
// },
// tag: () => { //all the notes from tag will be retrived and then mathched with already present notes to sort out relevent notes
// for (let note of definition.noteIds) {
// if (db.notes.note(note)) notes.push(db.notes.note(note)._note);
// }
// },
// before: () => {//all the notes within dates will be retrived but only those will be sorted out which match with already present notes
// let _notes = notes === [] ? db.notes.all : notes;
// for (let note of _notes) {
// if (note.dateCreated < unixTime) {
// result.push(note);
// }
// }
// },
// after: () => {},
// during: () => {}
// };
// };
export const dateSearchEngine = async (date, label, notes) => {
let unixTime = new Date(date).getTime();
let notesArc = notes ? notes : db.notes.all;
let result = [];
switch (label) {
case "before":
for (let note of notesArc) {
if (note.dateCreated < unixTime) {
result.push(note);
}
}
break;
case "after":
for (let note of notesArc) {
if (note.dateCreated > unixTime) {
result.push(note);
}
}
break;
case "during":
for (let note of notesArc) {
let dateCreated = new Date(note.dateCreated);
let noteDate = dateFormat(dateCreated).DDMMYY;
let selectedUnix = new Date(unixTime);
let selectedDate = dateFormat(selectedUnix).DDMMYY;
if (noteDate === selectedDate) {
result.push(note);
}
}
break;
}
return result;
// if (label === "before") {
// for (let note of notesArc) {
// if (note.dateCreated < unixTime) {
// result.push(note);
// }
// }
// } else if (label === "after") {
// for (let note of notesArc) {
// if (note.dateCreated > unixTime) {
// result.push(note);
// }
// }
// } else if (label === "during") {
// for (let note of notesArc) {
// let dateCreated = new Date(note.dateCreated);
// let noteDate =
// dateCreated.getDate() +
// "/" +
// dateCreated.getMonth() +
// "/" +
// dateCreated.getFullYear();
// let selectedUnix = new Date(unixTime);
// let selectedDate =
// selectedUnix.getDate() +
// "/" +
// selectedUnix.getMonth() +
// "/" +
// selectedUnix.getFullYear();
// if (noteDate === selectedDate) {
// result.push(note);
// }
// }
// }
};
const dateFormat = (date) => {
//this should be someplace else
return {
DDMMYY: date.getDate() + "/" + date.getMonth() + "/" + date.getFullYear()
};
};
export async function filterItemsToType(type, context) {
switch (type) {
case "notes":
await db.notes.init();
if (!context) return ["notes", db.notes.all];
const notes = context.notes;
return ["notes", notes];
case "notebooks":
return ["notebooks", db.notebooks.all];
case "topics":
const notebooks = db.notebooks.all;
if (!notebooks) return ["topics", []];
let topics = [];
for (let notebook of notebooks) {
let notebookTopics = db.notebooks.notebook(notebook.id).topics.all;
if (notebookTopics.length > 0) {
for (let notebookTopic of notebookTopics) {
topics.push(notebookTopic);
}
}
}
return ["topics", topics];
case "tags":
return ["tags", db.tags.all];
case "trash":
return ["trash", db.trash.all];
case "_byTitle":
await db.notes.init();
if (!context) return ["notes", db.notes.all];
let title = context.notes;
return ["_byTitle", title];
default:
return [];
}
}
export function filterItems(query, items) {
//naming is wrong
//this is fetch suggestions search
try {
return filter(
parse(`text:"${query.toLowerCase()}"`),
items.map((item) => {
return { item, text: item.query };
})
).map((v) => {
return v.item.query;
});
} catch {
return [];
}
}
export const addToSearchHistory = async (value) => {
// const history = await db.searchHistory.getHistory();
// value = value.trim();
// let isValueAlreadyPresent = false;
// history.map((item) => {
// if (item.query === value) {
// isValueAlreadyPresent = true;
// }
// });
// if (isValueAlreadyPresent) {
// return;
// }
// await db.searchHistory.add(value);
};

View File

@@ -0,0 +1,56 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
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, either version 3 of the License, or
(at your option) any later version.
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 { Button, Text } from "@theme-ui/components";
export function SuggestionRow(props) {
return (
<Button
id={`suggestionItem_${props.index}`}
variant="menuitem"
m={0}
sx={{
display: "flex",
":focus": { bg: "hover" },
width: "100%",
alignItems: "flex-start"
}}
bg="green"
px={2}
{...props}
>
<Text
variant="subtitle"
fontWeight={props.item.isFilterFocused ? "body" : "bold"}
ml={1}
sx={{ textOverflow: "ellipsis" }}
>
{props.item.col1}
</Text>
<Text
variant="subtitle"
fontWeight={"body"}
ml={1}
sx={{ textOverflow: "ellipsis" }}
>
{props.item.col2}
</Text>
</Button>
);
}