mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-02-03 04:19:06 +08:00
Merge branch 'master' of https://github.com/Miodec/monkeytype
This commit is contained in:
commit
f144a03336
18 changed files with 7838 additions and 283 deletions
42
frontend/package-lock.json
generated
42
frontend/package-lock.json
generated
|
@ -14,12 +14,14 @@
|
|||
"chartjs-plugin-annotation": "^0.5.7",
|
||||
"chartjs-plugin-trendline": "^0.2.2",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"damerau-levenshtein": "1.0.8",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"firebase": "^8.4.2",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"howler": "^2.2.1",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"node-object-hash": "2.3.10",
|
||||
"stemmer": "2.0.0",
|
||||
"tinycolor2": "^1.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -27,6 +29,7 @@
|
|||
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@types/damerau-levenshtein": "1.0.0",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
"@types/howler": "^2.2.5",
|
||||
"@types/jquery": "^3.5.13",
|
||||
|
@ -2236,6 +2239,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
|
||||
},
|
||||
"node_modules/@types/damerau-levenshtein": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/damerau-levenshtein/-/damerau-levenshtein-1.0.0.tgz",
|
||||
"integrity": "sha512-8XQ1jJHlOl6HjZ3/fU9Yrm/14jxM4gXVezPWiwkyiG0GnYROsI6wdh8DwKccAFGDNiNYBooTZkRXVe4du6plKA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||
|
@ -4058,6 +4067,11 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="
|
||||
},
|
||||
"node_modules/dart-sass": {
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/dart-sass/-/dart-sass-1.25.0.tgz",
|
||||
|
@ -10652,6 +10666,18 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stemmer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stemmer/-/stemmer-2.0.0.tgz",
|
||||
"integrity": "sha512-0YS2oMdTZ/wAWUHMMpf7AAJ8Gm6dHXyHddJ0zCu2DIfOfIbdwqAm1bbk4+Vti6gxNIcOrnm5jAP7vYTzQDvc5A==",
|
||||
"bin": {
|
||||
"stemmer": "cli.js"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-browserify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
|
||||
|
@ -13742,6 +13768,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
|
||||
},
|
||||
"@types/damerau-levenshtein": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/damerau-levenshtein/-/damerau-levenshtein-1.0.0.tgz",
|
||||
"integrity": "sha512-8XQ1jJHlOl6HjZ3/fU9Yrm/14jxM4gXVezPWiwkyiG0GnYROsI6wdh8DwKccAFGDNiNYBooTZkRXVe4du6plKA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||
|
@ -15235,6 +15267,11 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="
|
||||
},
|
||||
"dart-sass": {
|
||||
"version": "1.25.0",
|
||||
"resolved": "https://registry.npmjs.org/dart-sass/-/dart-sass-1.25.0.tgz",
|
||||
|
@ -20387,6 +20424,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"stemmer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stemmer/-/stemmer-2.0.0.tgz",
|
||||
"integrity": "sha512-0YS2oMdTZ/wAWUHMMpf7AAJ8Gm6dHXyHddJ0zCu2DIfOfIbdwqAm1bbk4+Vti6gxNIcOrnm5jAP7vYTzQDvc5A=="
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
|
||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@types/damerau-levenshtein": "1.0.0",
|
||||
"@types/grecaptcha": "^3.0.3",
|
||||
"@types/howler": "^2.2.5",
|
||||
"@types/jquery": "^3.5.13",
|
||||
|
@ -56,12 +57,14 @@
|
|||
"chartjs-plugin-annotation": "^0.5.7",
|
||||
"chartjs-plugin-trendline": "^0.2.2",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"damerau-levenshtein": "1.0.8",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"firebase": "^8.4.2",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"howler": "^2.2.1",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"node-object-hash": "2.3.10",
|
||||
"stemmer": "2.0.0",
|
||||
"tinycolor2": "^1.4.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import Config, * as UpdateConfig from "../config";
|
|||
import * as Misc from "../misc";
|
||||
import * as WordFilterPopup from "./word-filter-popup";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as SavedTextsPopup from "./saved-texts-popup";
|
||||
|
||||
const wrapper = "#customTextPopupWrapper";
|
||||
const popup = "#customTextPopup";
|
||||
|
@ -107,8 +108,16 @@ $(`${popup} .randomInputFields .time input`).keypress(() => {
|
|||
$(`${popup} .randomInputFields .wordcount input`).val("");
|
||||
});
|
||||
|
||||
$("#customTextPopup .apply").click(() => {
|
||||
let text = ($("#customTextPopup textarea").val() as string).normalize();
|
||||
$(`${popup} .buttonsTop .showSavedTexts`).on("click", () => {
|
||||
SavedTextsPopup.show();
|
||||
});
|
||||
|
||||
$(`${popup} .buttonsTop .saveCustomText`).on("click", () => {
|
||||
hide();
|
||||
});
|
||||
|
||||
function apply(): void {
|
||||
let text = ($(`${popup} textarea`).val() as string).normalize();
|
||||
text = text.trim();
|
||||
// text = text.replace(/[\r]/gm, " ");
|
||||
text = text.replace(/\\\\t/gm, "\t");
|
||||
|
@ -120,26 +129,20 @@ $("#customTextPopup .apply").click(() => {
|
|||
// text = text.replace(/(\n)+/g, "\n");
|
||||
// text = text.replace(/(\r)+/g, "\r");
|
||||
text = text.replace(/( *(\r\n|\r|\n) *)/g, "\n ");
|
||||
if ($("#customTextPopup .typographyCheck input").prop("checked")) {
|
||||
if ($(`${popup} .typographyCheck input`).prop("checked")) {
|
||||
text = Misc.cleanTypographySymbols(text);
|
||||
}
|
||||
// text = Misc.remove_non_ascii(text);
|
||||
text = text.replace(/[\u2060]/g, "");
|
||||
CustomText.setText(text.split(CustomText.delimiter));
|
||||
CustomText.setWord(
|
||||
parseInt($("#customTextPopup .wordcount input").val() as string)
|
||||
);
|
||||
CustomText.setTime(
|
||||
parseInt($("#customTextPopup .time input").val() as string)
|
||||
);
|
||||
CustomText.setWord(parseInt($(`${popup} .wordcount input`).val() as string));
|
||||
CustomText.setTime(parseInt($(`${popup} .time input`).val() as string));
|
||||
|
||||
CustomText.setIsWordRandom(
|
||||
$("#customTextPopup .checkbox input").prop("checked") &&
|
||||
!isNaN(CustomText.word)
|
||||
$(`${popup} .checkbox input`).prop("checked") && !isNaN(CustomText.word)
|
||||
);
|
||||
CustomText.setIsTimeRandom(
|
||||
$("#customTextPopup .checkbox input").prop("checked") &&
|
||||
!isNaN(CustomText.time)
|
||||
$(`${popup} .checkbox input`).prop("checked") && !isNaN(CustomText.time)
|
||||
);
|
||||
|
||||
if (
|
||||
|
@ -184,9 +187,13 @@ $("#customTextPopup .apply").click(() => {
|
|||
if (Config.mode !== "custom") UpdateConfig.setMode("custom");
|
||||
TestLogic.restart();
|
||||
hide();
|
||||
}
|
||||
|
||||
$(document).on("click", `${popup} .button.apply`, () => {
|
||||
apply();
|
||||
});
|
||||
|
||||
$("#customTextPopup .wordfilter").click(() => {
|
||||
$(document).on("click", `${popup} .wordfilter`, () => {
|
||||
WordFilterPopup.show();
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,12 @@ import * as QuoteSubmitPopup from "./quote-submit-popup";
|
|||
import * as QuoteApprovePopup from "./quote-approve-popup";
|
||||
import * as QuoteReportPopup from "./quote-report-popup";
|
||||
import * as Misc from "../misc";
|
||||
import {
|
||||
buildSearchService,
|
||||
SearchService,
|
||||
TextExtractor,
|
||||
} from "../utils/search-service";
|
||||
import { debounce } from "../utils/debounce";
|
||||
|
||||
export let selectedId = 1;
|
||||
|
||||
|
@ -14,39 +20,65 @@ export function setSelectedId(val: number): void {
|
|||
selectedId = val;
|
||||
}
|
||||
|
||||
const searchServiceCache: Record<string, SearchService<any>> = {};
|
||||
|
||||
function getSearchService<T>(
|
||||
language: string,
|
||||
data: T[],
|
||||
textExtractor: TextExtractor<T>
|
||||
): SearchService<T> {
|
||||
if (language in searchServiceCache) {
|
||||
return searchServiceCache[language];
|
||||
}
|
||||
|
||||
const newSearchService = buildSearchService<T>(data, textExtractor);
|
||||
searchServiceCache[language] = newSearchService;
|
||||
|
||||
return newSearchService;
|
||||
}
|
||||
|
||||
function highlightMatches(text: string, matchedText: string[]): string {
|
||||
if (matchedText.length === 0) {
|
||||
return text;
|
||||
}
|
||||
const words = text.split(
|
||||
/(?=[.,'"/#!$%^&*;:{}=\-_`~()\s])|(?<=[.,'"/#!$%^&*;:{}=\-_`~()\s])/g
|
||||
);
|
||||
|
||||
const normalizedWords = words.map((word) => {
|
||||
const shouldHighlight = matchedText.find((match) => {
|
||||
return word.startsWith(match);
|
||||
});
|
||||
return shouldHighlight ? `<span class="highlight">${word}</span>` : word;
|
||||
});
|
||||
|
||||
return normalizedWords.join("");
|
||||
}
|
||||
|
||||
async function updateResults(searchText: string): Promise<void> {
|
||||
const quotes = await Misc.getQuotes(Config.language);
|
||||
const reg = new RegExp(searchText, "i");
|
||||
const found: MonkeyTypes.Quote[] = [];
|
||||
quotes.quotes.forEach((quote) => {
|
||||
const quoteText = quote["text"].replace(/[.,'"/#!$%^&*;:{}=\-_`~()]/g, "");
|
||||
const test1 = reg.test(quoteText);
|
||||
if (test1) {
|
||||
found.push(quote);
|
||||
const { quotes } = await Misc.getQuotes(Config.language);
|
||||
|
||||
const quoteSearchService = getSearchService<MonkeyTypes.Quote>(
|
||||
Config.language,
|
||||
quotes,
|
||||
(quote: MonkeyTypes.Quote) => {
|
||||
return `${quote.text} ${quote.id} ${quote.source}`;
|
||||
}
|
||||
});
|
||||
quotes.quotes.forEach((quote) => {
|
||||
const quoteSource = quote["source"].replace(
|
||||
/[.,'"/#!$%^&*;:{}=\-_`~()]/g,
|
||||
""
|
||||
);
|
||||
const quoteId = quote["id"];
|
||||
const test2 = reg.test(quoteSource);
|
||||
const test3 = reg.test(quoteId.toString());
|
||||
if ((test2 || test3) && found.filter((q) => q.id == quote.id).length == 0) {
|
||||
found.push(quote);
|
||||
}
|
||||
});
|
||||
);
|
||||
const { results: matches, matchedQueryTerms } =
|
||||
quoteSearchService.query(searchText);
|
||||
|
||||
$("#quoteSearchResults").remove();
|
||||
$("#quoteSearchPopup").append(
|
||||
'<div class="quoteSearchResults" id="quoteSearchResults"></div>'
|
||||
);
|
||||
const resultsList = $("#quoteSearchResults");
|
||||
let resultListLength = 0;
|
||||
|
||||
const resultsList = $("#quoteSearchResults");
|
||||
const isNotAuthed = !firebase.auth().currentUser;
|
||||
|
||||
found.forEach(async (quote) => {
|
||||
const quotesToShow = searchText === "" ? quotes : matches;
|
||||
|
||||
quotesToShow.slice(0, 100).forEach((quote) => {
|
||||
let lengthDesc;
|
||||
if (quote.length < 101) {
|
||||
lengthDesc = "short";
|
||||
|
@ -57,15 +89,21 @@ async function updateResults(searchText: string): Promise<void> {
|
|||
} else {
|
||||
lengthDesc = "thicc";
|
||||
}
|
||||
if (resultListLength++ < 100) {
|
||||
resultsList.append(`
|
||||
resultsList.append(`
|
||||
<div class="searchResult" id="${quote.id}">
|
||||
<div class="text">${quote.text}</div>
|
||||
<div class="id"><div class="sub">id</div><span class="quote-id">${
|
||||
quote.id
|
||||
}</span></div>
|
||||
<div class="text">${highlightMatches(
|
||||
quote.text,
|
||||
matchedQueryTerms
|
||||
)}</div>
|
||||
<div class="id"><div class="sub">id</div><span class="quote-id">${highlightMatches(
|
||||
quote.id.toString(),
|
||||
matchedQueryTerms
|
||||
)}</span></div>
|
||||
<div class="length"><div class="sub">length</div>${lengthDesc}</div>
|
||||
<div class="source"><div class="sub">source</div>${quote.source}</div>
|
||||
<div class="source"><div class="sub">source</div>${highlightMatches(
|
||||
quote.source,
|
||||
matchedQueryTerms
|
||||
)}</div>
|
||||
<div class="icon-button report ${
|
||||
isNotAuthed && "hidden"
|
||||
}" aria-label="Report quote" data-balloon-pos="left">
|
||||
|
@ -73,15 +111,14 @@ async function updateResults(searchText: string): Promise<void> {
|
|||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
if (found.length > 100) {
|
||||
if (quotesToShow.length > 100) {
|
||||
$("#extraResults").html(
|
||||
found.length +
|
||||
quotesToShow.length +
|
||||
" results <span style='opacity: 0.5'>(only showing 100)</span>"
|
||||
);
|
||||
} else {
|
||||
$("#extraResults").html(found.length + " results");
|
||||
$("#extraResults").html(quotesToShow.length + " results");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,17 +195,14 @@ export function apply(val: number): boolean {
|
|||
return ret;
|
||||
}
|
||||
|
||||
$("#quoteSearchPopup .searchBox").keydown((e) => {
|
||||
if (e.code == "Escape") return;
|
||||
setTimeout(() => {
|
||||
let searchText = (<HTMLInputElement>document.getElementById("searchBox"))
|
||||
.value;
|
||||
searchText = searchText
|
||||
.replace(/[.,'"/#!$%^&*;:{}=\-_`~()]/g, "")
|
||||
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
const debouncedSearch = debounce(updateResults);
|
||||
|
||||
updateResults(searchText);
|
||||
}, 0.1); //arbitrarily v. small time as it's only to allow text to input before searching
|
||||
$("#quoteSearchPopup .searchBox").on("keyup", (e) => {
|
||||
if (e.code === "Escape") return;
|
||||
|
||||
const searchText = (<HTMLInputElement>document.getElementById("searchBox"))
|
||||
.value;
|
||||
debouncedSearch(searchText);
|
||||
});
|
||||
|
||||
$("#quoteSearchPopupWrapper").click((e) => {
|
||||
|
@ -218,17 +252,3 @@ $(document).keydown((event) => {
|
|||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// $("#quoteSearchPopup input").keypress((e) => {
|
||||
// if (e.keyCode == 13) {
|
||||
// if (!isNaN(document.getElementById("searchBox").value)) {
|
||||
// apply();
|
||||
// } else {
|
||||
// let results = document.getElementsByClassName("searchResult");
|
||||
// if (results.length > 0) {
|
||||
// selectedId = parseInt(results[0].getAttribute("id"));
|
||||
// apply(selectedId);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
|
56
frontend/src/scripts/popups/saved-texts-popup.ts
Normal file
56
frontend/src/scripts/popups/saved-texts-popup.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import * as CustomText from "../test/custom-text";
|
||||
|
||||
export async function show(): Promise<void> {
|
||||
const names = CustomText.getCustomTextNames();
|
||||
const listEl = $(`#savedTextsPopup .list`).empty();
|
||||
let list = "";
|
||||
if (names.length === 0) {
|
||||
list += "<div>No saved custom texts found</div>";
|
||||
} else {
|
||||
for (const name of names) {
|
||||
list += `<div class="savedText">
|
||||
<div class="button name">${name}</div>
|
||||
<div class="button delete">
|
||||
<i class="fas fa-fw fa-trash"></i>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
listEl.html(list);
|
||||
$("#savedTextsPopupWrapper").removeClass("hidden");
|
||||
$("#customTextPopupWrapper").addClass("hidden");
|
||||
}
|
||||
|
||||
function hide(full = false): void {
|
||||
$("#savedTextsPopupWrapper").addClass("hidden");
|
||||
if (!full) $("#customTextPopupWrapper").removeClass("hidden");
|
||||
}
|
||||
|
||||
function applySaved(name: string): void {
|
||||
const text = CustomText.getCustomText(name);
|
||||
$(`#customTextPopupWrapper textarea`).val(text.join(CustomText.delimiter));
|
||||
}
|
||||
|
||||
$(document).on(
|
||||
"click",
|
||||
`#savedTextsPopupWrapper .list .savedText .button.name`,
|
||||
(e) => {
|
||||
const name = $(e.target).text();
|
||||
applySaved(name);
|
||||
hide();
|
||||
}
|
||||
);
|
||||
|
||||
$(document).on(
|
||||
"click",
|
||||
`#savedTextsPopupWrapper .list .savedText .button.delete`,
|
||||
() => {
|
||||
hide(true);
|
||||
}
|
||||
);
|
||||
|
||||
$("#savedTextsPopupWrapper").mousedown((e) => {
|
||||
if ($(e.target).attr("id") === "savedTextsPopupWrapper") {
|
||||
hide();
|
||||
}
|
||||
});
|
|
@ -7,6 +7,9 @@ import * as Loader from "../elements/loader";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as ApeKeysPopup from "../popups/ape-keys-popup";
|
||||
import * as CustomText from "../test/custom-text";
|
||||
import * as CustomTextPopup from "../popups/custom-text-popup";
|
||||
import * as SavedTextsPopup from "./saved-texts-popup";
|
||||
|
||||
type Input = {
|
||||
placeholder: string;
|
||||
|
@ -885,6 +888,52 @@ list["editApeKey"] = new SimplePopup(
|
|||
}
|
||||
);
|
||||
|
||||
list["saveCustomText"] = new SimplePopup(
|
||||
"saveCustomText",
|
||||
"text",
|
||||
"Save custom text",
|
||||
[
|
||||
{
|
||||
placeholder: "Name",
|
||||
initVal: "",
|
||||
},
|
||||
],
|
||||
"",
|
||||
"Save",
|
||||
(_thisPopup, input) => {
|
||||
const text = ($(`#customTextPopup textarea`).val() as string).normalize();
|
||||
CustomText.setCustomText(input, text);
|
||||
Notifications.add("Custom text saved", 1);
|
||||
CustomTextPopup.show();
|
||||
},
|
||||
() => {
|
||||
//
|
||||
},
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
list["deleteCustomText"] = new SimplePopup(
|
||||
"deleteCustomText",
|
||||
"text",
|
||||
"Delete custom text",
|
||||
[],
|
||||
"Are you sure?",
|
||||
"Delete",
|
||||
(_thisPopup) => {
|
||||
CustomText.deleteCustomText(_thisPopup.parameters[0]);
|
||||
Notifications.add("Custom text deleted", 1);
|
||||
SavedTextsPopup.show();
|
||||
},
|
||||
(_thisPopup) => {
|
||||
_thisPopup.text = `Are you sure you want to delete custom text ${_thisPopup.parameters[0]}?`;
|
||||
},
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
$(".pageSettings .section.discordIntegration #unlinkDiscordButton").click(
|
||||
() => {
|
||||
list["unlinkDiscord"].show();
|
||||
|
@ -923,6 +972,19 @@ $("#apeKeysPopup .generateApeKey").on("click", () => {
|
|||
list["generateApeKey"].show();
|
||||
});
|
||||
|
||||
$(`#customTextPopup .buttonsTop .saveCustomText`).on("click", () => {
|
||||
list["saveCustomText"].show();
|
||||
});
|
||||
|
||||
$(document).on(
|
||||
"click",
|
||||
`#savedTextsPopupWrapper .list .savedText .button.delete`,
|
||||
(e) => {
|
||||
const name = $(e.target).siblings(".button.name").text();
|
||||
list["deleteCustomText"].show([name]);
|
||||
}
|
||||
);
|
||||
|
||||
$(document).on("click", "#apeKeysPopup table tbody tr .button.delete", (e) => {
|
||||
const keyId = $(e.target).closest("tr").attr("keyId") as string;
|
||||
list["deleteApeKey"].show([keyId]);
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
export let text = "The quick brown fox jumps over the lazy dog".split(" ");
|
||||
export let text = [
|
||||
"The",
|
||||
"quick",
|
||||
"brown",
|
||||
"fox",
|
||||
"jumps",
|
||||
"over",
|
||||
"the",
|
||||
"lazy",
|
||||
"dog",
|
||||
];
|
||||
export let isWordRandom = false;
|
||||
export let isTimeRandom = false;
|
||||
export let word: number;
|
||||
|
@ -28,3 +38,36 @@ export function setWord(val: number): void {
|
|||
export function setDelimiter(val: string): void {
|
||||
delimiter = val;
|
||||
}
|
||||
|
||||
type CustomTextObject = { [key: string]: string };
|
||||
|
||||
export function getCustomText(name: string): string[] {
|
||||
const customText = getCustomTextObject();
|
||||
|
||||
return customText[name].split(/ +/);
|
||||
}
|
||||
|
||||
export function setCustomText(name: string, text: string | string[]): void {
|
||||
const customText = getCustomTextObject();
|
||||
|
||||
if (typeof text === "string") customText[name] = text;
|
||||
else customText[name] = text.join(" ");
|
||||
|
||||
window.localStorage.setItem("customText", JSON.stringify(customText));
|
||||
}
|
||||
|
||||
export function deleteCustomText(name: string): void {
|
||||
const customText = getCustomTextObject();
|
||||
|
||||
if (customText[name]) delete customText[name];
|
||||
|
||||
window.localStorage.setItem("customText", JSON.stringify(customText));
|
||||
}
|
||||
|
||||
function getCustomTextObject(): CustomTextObject {
|
||||
return JSON.parse(window.localStorage.getItem("customText") ?? "{}");
|
||||
}
|
||||
|
||||
export function getCustomTextNames(): string[] {
|
||||
return Object.keys(getCustomTextObject());
|
||||
}
|
||||
|
|
8
frontend/src/scripts/utils/debounce.ts
Normal file
8
frontend/src/scripts/utils/debounce.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export function debounce(fn: any, ms = 250): any {
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
return function (this: any, ...args: any[]) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
||||
};
|
||||
}
|
155
frontend/src/scripts/utils/search-service.ts
Normal file
155
frontend/src/scripts/utils/search-service.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
import { stemmer } from "stemmer";
|
||||
import levenshtein from "damerau-levenshtein";
|
||||
|
||||
export interface SearchService<T> {
|
||||
query: (query: string) => SearchResult<T>;
|
||||
}
|
||||
|
||||
interface SearchServiceOptions {
|
||||
fuzzyMatchSensitivity: number;
|
||||
scoreForSimilarMatch: number;
|
||||
scoreForExactMatch: number;
|
||||
}
|
||||
|
||||
interface InternalDocument {
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface ReverseIndex {
|
||||
[key: string]: Set<InternalDocument>;
|
||||
}
|
||||
|
||||
interface TokenMap {
|
||||
[key: string]: Set<string>;
|
||||
}
|
||||
|
||||
interface SearchResult<T> {
|
||||
results: T[];
|
||||
matchedQueryTerms: string[];
|
||||
}
|
||||
|
||||
export type TextExtractor<T> = (document: T) => string;
|
||||
|
||||
const DEFAULT_OPTIONS: SearchServiceOptions = {
|
||||
fuzzyMatchSensitivity: 0.2, // Value between 0-1. Higher = more tolerant to spelling mistakes, too high and you get nonsense.
|
||||
scoreForSimilarMatch: 0.5, // When ranking results, the score a match gets for having a token that is similar to a search token.
|
||||
scoreForExactMatch: 1, // When ranking results, the score a match gets for having an exact match with a token in the search query.
|
||||
};
|
||||
|
||||
function inverseDocumentFrequency(
|
||||
numberOfDocuments: number,
|
||||
numberOfDocumentsWithTerm: number
|
||||
): number {
|
||||
if (numberOfDocumentsWithTerm === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.log10(numberOfDocuments / numberOfDocumentsWithTerm);
|
||||
}
|
||||
|
||||
function tokenize(text: string): string[] {
|
||||
return text.match(/[a-zA-Z0-9]+/g) || [];
|
||||
}
|
||||
|
||||
export const buildSearchService = <T>(
|
||||
documents: T[],
|
||||
getSearchableText: TextExtractor<T>,
|
||||
options: SearchServiceOptions = DEFAULT_OPTIONS
|
||||
): SearchService<T> => {
|
||||
const reverseIndex: ReverseIndex = {};
|
||||
const normalizedTokenToOriginal: TokenMap = {};
|
||||
|
||||
documents.forEach((document, documentIndex) => {
|
||||
const rawTokens = tokenize(getSearchableText(document));
|
||||
|
||||
const internalDocument: InternalDocument = {
|
||||
id: documentIndex,
|
||||
};
|
||||
|
||||
rawTokens.forEach((token) => {
|
||||
const stemmedToken = stemmer(token);
|
||||
|
||||
if (!(stemmedToken in normalizedTokenToOriginal)) {
|
||||
normalizedTokenToOriginal[stemmedToken] = new Set<string>();
|
||||
}
|
||||
normalizedTokenToOriginal[stemmedToken].add(token);
|
||||
|
||||
if (!(stemmedToken in reverseIndex)) {
|
||||
reverseIndex[stemmedToken] = new Set<InternalDocument>();
|
||||
}
|
||||
reverseIndex[stemmedToken].add(internalDocument);
|
||||
});
|
||||
});
|
||||
|
||||
const tokenSet = Object.keys(reverseIndex);
|
||||
|
||||
const query = (searchQuery: string): SearchResult<T> => {
|
||||
const searchResult: SearchResult<T> = {
|
||||
results: [],
|
||||
matchedQueryTerms: [],
|
||||
};
|
||||
|
||||
const normalizedSearchQuery = new Set<string>(
|
||||
tokenize(searchQuery).map((token) => stemmer(token))
|
||||
);
|
||||
if (normalizedSearchQuery.size === 0) {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
const results = new Map<number, number>();
|
||||
const matchedTokens = new Set<string>();
|
||||
|
||||
normalizedSearchQuery.forEach((searchToken) => {
|
||||
tokenSet.forEach((token) => {
|
||||
const { similarity } = levenshtein(searchToken, token);
|
||||
|
||||
const matchesSearchToken = token === searchToken;
|
||||
const isSimilar = similarity >= 1 - options.fuzzyMatchSensitivity;
|
||||
|
||||
if (matchesSearchToken || isSimilar) {
|
||||
const documentMatches = reverseIndex[token];
|
||||
|
||||
const idf = inverseDocumentFrequency(
|
||||
documents.length,
|
||||
documentMatches.size
|
||||
);
|
||||
|
||||
documentMatches.forEach((document) => {
|
||||
const currentScore = results.get(document.id) ?? 0;
|
||||
|
||||
const scoreForExactMatch = matchesSearchToken
|
||||
? options.scoreForExactMatch
|
||||
: 0;
|
||||
const scoreForSimilarity = isSimilar
|
||||
? options.scoreForSimilarMatch
|
||||
: 0;
|
||||
const score = scoreForExactMatch + scoreForSimilarity;
|
||||
|
||||
const scoreForToken = score * idf;
|
||||
|
||||
results.set(document.id, currentScore + scoreForToken);
|
||||
});
|
||||
|
||||
normalizedTokenToOriginal[token].forEach((originalToken) => {
|
||||
matchedTokens.add(originalToken);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const orderedResults = [...results]
|
||||
.sort((match1, match2) => {
|
||||
return match2[1] - match1[1];
|
||||
})
|
||||
.map((match) => documents[match[0]]);
|
||||
|
||||
searchResult.results = orderedResults;
|
||||
searchResult.matchedQueryTerms = [...matchedTokens];
|
||||
|
||||
return searchResult;
|
||||
};
|
||||
|
||||
return {
|
||||
query,
|
||||
};
|
||||
};
|
|
@ -20,9 +20,24 @@
|
|||
display: grid;
|
||||
gap: 1rem;
|
||||
width: 60vw;
|
||||
.wordfilter {
|
||||
width: 33%;
|
||||
justify-self: right;
|
||||
|
||||
.buttonsTop {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.savedTexts {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
.title {
|
||||
color: var(--sub-color);
|
||||
}
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
@ -61,6 +76,36 @@
|
|||
}
|
||||
}
|
||||
|
||||
#savedTextsPopupWrapper {
|
||||
#savedTextsPopup {
|
||||
color: var(--sub-color);
|
||||
background: var(--bg-color);
|
||||
border-radius: var(--roundness);
|
||||
padding: 2rem;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
width: 400px;
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
color: var(--sub-color);
|
||||
}
|
||||
|
||||
.list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
.savedText {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 1fr 3rem;
|
||||
.button .fas {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#wordFilterPopupWrapper {
|
||||
#wordFilterPopup {
|
||||
color: var(--sub-color);
|
||||
|
@ -367,6 +412,10 @@
|
|||
}
|
||||
|
||||
#quoteSearchPopupWrapper {
|
||||
.highlight {
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
#quoteSearchPopup {
|
||||
background: var(--bg-color);
|
||||
border-radius: var(--roundness);
|
||||
|
|
|
@ -372,9 +372,23 @@
|
|||
</div>
|
||||
<div id="customTextPopupWrapper" class="popupWrapper hidden">
|
||||
<div id="customTextPopup" action="">
|
||||
<div class="wordfilter button">
|
||||
<i class="fas fa-filter"></i>
|
||||
Words filter
|
||||
<div class="buttonsTop">
|
||||
<div class="button saveCustomText">
|
||||
<i class="fas fa-save"></i>
|
||||
Save
|
||||
</div>
|
||||
<div class="button showSavedTexts">
|
||||
<i class="fas fa-folder"></i>
|
||||
Show saved texts
|
||||
</div>
|
||||
<div class="button wordfilter">
|
||||
<i class="fas fa-filter"></i>
|
||||
Words filter
|
||||
</div>
|
||||
</div>
|
||||
<div class="savedTexts hidden" style="display: none">
|
||||
<div class="title">saved texts</div>
|
||||
<div class="buttons"></div>
|
||||
</div>
|
||||
<textarea class="textarea" placeholder="Custom text"></textarea>
|
||||
<div class="inputs">
|
||||
|
@ -419,6 +433,12 @@
|
|||
<div class="button apply">ok</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="savedTextsPopupWrapper" class="popupWrapper hidden">
|
||||
<div id="savedTextsPopup">
|
||||
<div class="title">Saved texts</div>
|
||||
<div class="list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wordFilterPopupWrapper" class="popupWrapper hidden">
|
||||
<div id="wordFilterPopup">
|
||||
<div class="group">
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
{
|
||||
"name": "nepali",
|
||||
"languages": ["nepali"]
|
||||
"languages": ["nepali", "nepali_1k"]
|
||||
},
|
||||
{
|
||||
"name": "french",
|
||||
|
@ -174,7 +174,7 @@
|
|||
},
|
||||
{
|
||||
"name": "persian",
|
||||
"languages": ["persian"]
|
||||
"languages": ["persian", "persian_1k", "persian_5k"]
|
||||
},
|
||||
{
|
||||
"name": "kazakh",
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
,"french_2k"
|
||||
,"french_10k"
|
||||
,"nepali"
|
||||
,"nepali_1k"
|
||||
,"arabic"
|
||||
,"arabic_10k"
|
||||
,"malagasy"
|
||||
|
@ -89,6 +90,8 @@
|
|||
,"welsh"
|
||||
,"welsh_1k"
|
||||
,"persian"
|
||||
,"persian_1k"
|
||||
,"persian_5k"
|
||||
,"kazakh"
|
||||
,"vietnamese"
|
||||
,"vietnamese_1k"
|
||||
|
|
|
@ -5,206 +5,205 @@
|
|||
"noLazyMode": true,
|
||||
"bcp47": "ne-NP",
|
||||
"words": [
|
||||
"बिहान",
|
||||
"साँझ",
|
||||
"जिम्मेवारी",
|
||||
"खल्ती",
|
||||
"रमाइलो",
|
||||
"कपाल",
|
||||
"पुस्तक",
|
||||
"ओछ्यान",
|
||||
"परेवा",
|
||||
"सौन्दर्य",
|
||||
"छ",
|
||||
"र",
|
||||
"न",
|
||||
"त",
|
||||
"म",
|
||||
"यी",
|
||||
"यो",
|
||||
"तर",
|
||||
"ती",
|
||||
"नै",
|
||||
"वन",
|
||||
"मन",
|
||||
"गए",
|
||||
"मह",
|
||||
"जो",
|
||||
"सय",
|
||||
"भए",
|
||||
"एक",
|
||||
"दश",
|
||||
"जरा",
|
||||
"अनि",
|
||||
"पछि",
|
||||
"गएर",
|
||||
"काम",
|
||||
"थान",
|
||||
"टोपी",
|
||||
"बेचन",
|
||||
"सफलता",
|
||||
"कम्पनी",
|
||||
"घटाउनुहोस्",
|
||||
"घटना",
|
||||
"विशेष",
|
||||
"सम्झौता",
|
||||
"पौडी",
|
||||
"अवधि",
|
||||
"विपरीत",
|
||||
"पत्नी",
|
||||
"जूता",
|
||||
"काँध",
|
||||
"फैलियो",
|
||||
"व्यवस्था",
|
||||
"शिविरमा",
|
||||
"आविष्कार",
|
||||
"कपास",
|
||||
"जन्म",
|
||||
"निर्धारित",
|
||||
"पनि",
|
||||
"याम",
|
||||
"अरू",
|
||||
"सबै",
|
||||
"मरे",
|
||||
"हुन",
|
||||
"साथ",
|
||||
"रूख",
|
||||
"लाख",
|
||||
"भयो",
|
||||
"दिन",
|
||||
"हैन",
|
||||
"किन",
|
||||
"गरे",
|
||||
"अघि",
|
||||
"समय",
|
||||
"कथा",
|
||||
"गति",
|
||||
"रकम",
|
||||
"बढी",
|
||||
"भनि",
|
||||
"लागि",
|
||||
"माटो",
|
||||
"जंगल",
|
||||
"भएको",
|
||||
"ठाउँ",
|
||||
"बन्न",
|
||||
"सानै",
|
||||
"जोगाउन",
|
||||
"थाल्यो",
|
||||
"आयोजना",
|
||||
"थुप्रै",
|
||||
"लगाउने",
|
||||
"हेरचाह",
|
||||
"छाडेका",
|
||||
"रोपेको",
|
||||
"मिल्ने",
|
||||
"ल्याएर",
|
||||
"छिट्टै",
|
||||
"काट्ने",
|
||||
"त्यसले",
|
||||
"सिकाउन",
|
||||
"पन्ध्र",
|
||||
"खासगरि",
|
||||
"प्रयोग",
|
||||
"प्रदान",
|
||||
"हाम्रो",
|
||||
"अमूल्य",
|
||||
"साधारण",
|
||||
"नमस्कार",
|
||||
"दाउराका",
|
||||
"नरहेपछि",
|
||||
"बर्खाको",
|
||||
"मरूभूमि",
|
||||
"सुधार्न",
|
||||
"जनतालाई",
|
||||
"भिजाउने",
|
||||
"एक्लै",
|
||||
"त्यसैले",
|
||||
"त्यसपछि",
|
||||
"विभागले",
|
||||
"वेहोरेर",
|
||||
"समूह",
|
||||
"आम्दानी",
|
||||
"नजिकैा",
|
||||
"पाउनुभयो",
|
||||
"प्रयास",
|
||||
"सम्माना",
|
||||
"हावाहुरी",
|
||||
"तालिम",
|
||||
"उत्साहका",
|
||||
"अवस्थाा",
|
||||
"अभियान",
|
||||
"प्रत्येक",
|
||||
"प्रस्ताव",
|
||||
"हुर्किने",
|
||||
"खानेकुरा",
|
||||
"बाहिर",
|
||||
"ल्याइए",
|
||||
"दिँदैनथे",
|
||||
"अभियान",
|
||||
"लगाउनु",
|
||||
"परिवर्तन",
|
||||
"विद्यालय",
|
||||
"छेउछाउ",
|
||||
"पुरस्कार",
|
||||
"पृथ्वीको",
|
||||
"खेतीयोग्य",
|
||||
"चिट्ठी",
|
||||
"स्याहार",
|
||||
"महिना",
|
||||
"फाइदा",
|
||||
"नर्सरी",
|
||||
"स्याहार",
|
||||
"गर्नुहोला",
|
||||
"व्यक्ति",
|
||||
"गर्नुभएको",
|
||||
"पुर्याउने",
|
||||
"सार्वजनिक",
|
||||
"गर्नुपर्ने",
|
||||
"महिला",
|
||||
"गर्दागर्दै",
|
||||
"गाईवस्तुका",
|
||||
"नमस्कार",
|
||||
"बिरूवा",
|
||||
"रोपेका",
|
||||
"सुख्खा",
|
||||
"दुईवटा",
|
||||
"सक्दैन",
|
||||
"समाज",
|
||||
"लक्ष्य",
|
||||
"वरिपरि",
|
||||
"सुरूमा",
|
||||
"रखेदेख",
|
||||
"उपलब्ध",
|
||||
"गराउने",
|
||||
"अन्तमा",
|
||||
"नसक्ने",
|
||||
"नर्सरी",
|
||||
"सक्छन्",
|
||||
"उपायले",
|
||||
"गर्यो",
|
||||
"बीजहरू",
|
||||
"कामलाई",
|
||||
"अभियान",
|
||||
"माटोको",
|
||||
"चिसोपन",
|
||||
"बढीको",
|
||||
"ख्याल",
|
||||
"असलपन",
|
||||
"रिस",
|
||||
"दाबी",
|
||||
"महादेशमा",
|
||||
"भाइ",
|
||||
"काठमाडौँ",
|
||||
"राष्ट्रिय",
|
||||
"पहिले",
|
||||
"किनेको",
|
||||
"नेतृत्व",
|
||||
"पिच",
|
||||
"कोट",
|
||||
"सामूहिक",
|
||||
"व्यवस्थापन",
|
||||
"अथवा",
|
||||
"अनुभव"
|
||||
"अघि",
|
||||
"अथवा",
|
||||
"अनि",
|
||||
"अनिरुद्र",
|
||||
"अनुभव",
|
||||
"अन्तमा",
|
||||
"अभियान",
|
||||
"अमूल्य",
|
||||
"अरू",
|
||||
"अवधि",
|
||||
"अवस्था",
|
||||
"अविनवन",
|
||||
"असलपन",
|
||||
"आम्दानी",
|
||||
"आयोजना",
|
||||
"आर्यब",
|
||||
"आविष्कार",
|
||||
"आशिश",
|
||||
"उत्साह",
|
||||
"उपलब्ध",
|
||||
"उपायले",
|
||||
"एक",
|
||||
"एक्लै",
|
||||
"ओछ्यान",
|
||||
"कथा",
|
||||
"कपाल",
|
||||
"कपास",
|
||||
"कम्पनी",
|
||||
"काँध",
|
||||
"काट्ने",
|
||||
"काठमाडौँ",
|
||||
"काम",
|
||||
"किन",
|
||||
"किनेको",
|
||||
"कोट",
|
||||
"खल्ती",
|
||||
"खानेकुरा",
|
||||
"खासगरि",
|
||||
"खेतीयोग्य",
|
||||
"ख्याल",
|
||||
"गए",
|
||||
"गएर",
|
||||
"गति",
|
||||
"गरे",
|
||||
"गर्नु",
|
||||
"गाईवस्तुका",
|
||||
"घटना",
|
||||
"घटाउनु",
|
||||
"चिट्ठी",
|
||||
"चिसोपन",
|
||||
"छ",
|
||||
"छाडेका",
|
||||
"छिट्टै",
|
||||
"छेउछाउ",
|
||||
"जंगल",
|
||||
"जनता",
|
||||
"जन्म",
|
||||
"जरा",
|
||||
"जिम्मेवारी",
|
||||
"जूता",
|
||||
"जो",
|
||||
"जोगाउन",
|
||||
"टोपी",
|
||||
"ठाउँ",
|
||||
"त",
|
||||
"तर",
|
||||
"तालिम",
|
||||
"ती",
|
||||
"त्यसपछि",
|
||||
"त्यसले",
|
||||
"त्यसैले",
|
||||
"थान",
|
||||
"थाल्यो",
|
||||
"थुप्रै",
|
||||
"दश",
|
||||
"दाउरा",
|
||||
"दाबी",
|
||||
"दिन",
|
||||
"दुईवटा",
|
||||
"न",
|
||||
"नजिकैा",
|
||||
"नमस्कार",
|
||||
"नरहेपछि",
|
||||
"नर्सरी",
|
||||
"नसक्ने",
|
||||
"निकेश",
|
||||
"निर्धारित",
|
||||
"नेतृत्व",
|
||||
"नेपाल",
|
||||
"नै",
|
||||
"पछि",
|
||||
"पत्नी",
|
||||
"पनि",
|
||||
"पन्ध्र",
|
||||
"परिवर्तन",
|
||||
"परेवा",
|
||||
"पहिले",
|
||||
"पाउनु",
|
||||
"पिच",
|
||||
"पुरस्कार",
|
||||
"पुस्तक",
|
||||
"पृथ्वी",
|
||||
"पौडी",
|
||||
"प्रत्येक",
|
||||
"प्रदान",
|
||||
"प्रयास",
|
||||
"प्रयोग",
|
||||
"प्रस्ताव",
|
||||
"फाइदा",
|
||||
"फैलियो",
|
||||
"बढी",
|
||||
"बढीको",
|
||||
"बन्न",
|
||||
"बर्खा",
|
||||
"बाहिर",
|
||||
"बिनिसा",
|
||||
"बिप्लप",
|
||||
"बिरूवा",
|
||||
"बिहान",
|
||||
"बीजहरू",
|
||||
"बेचन",
|
||||
"भए",
|
||||
"भएको",
|
||||
"भनि",
|
||||
"भयो",
|
||||
"भाग",
|
||||
"भाग्य",
|
||||
"भात",
|
||||
"भाइ",
|
||||
"भिजाउने",
|
||||
"म",
|
||||
"मन",
|
||||
"मरूभूमि",
|
||||
"मरे",
|
||||
"मन्दिर",
|
||||
"मह",
|
||||
"महादेशमा",
|
||||
"महिना",
|
||||
"महिला",
|
||||
"माटो",
|
||||
"माटोको",
|
||||
"मिल्ने",
|
||||
"याम",
|
||||
"यी",
|
||||
"यो",
|
||||
"र",
|
||||
"रकम",
|
||||
"रखेदेख",
|
||||
"रमाइलो",
|
||||
"राष्ट्रिय",
|
||||
"रिस",
|
||||
"रूख",
|
||||
"रोपेका",
|
||||
"रोपेको",
|
||||
"लक्ष्य",
|
||||
"लगाउने",
|
||||
"लाख",
|
||||
"लागि",
|
||||
"ल्याइए",
|
||||
"ल्याएर",
|
||||
"वन",
|
||||
"वरिपरि",
|
||||
"विद्यालय",
|
||||
"विपरीत",
|
||||
"विभाग",
|
||||
"विशेष",
|
||||
"वेहोरेर",
|
||||
"व्यक्ति",
|
||||
"व्यवस्था",
|
||||
"व्यवस्थापन",
|
||||
"शिविर",
|
||||
"शुभम",
|
||||
"सक्छन्",
|
||||
"सक्दैन",
|
||||
"सज्जा",
|
||||
"सफलता",
|
||||
"सबै",
|
||||
"समय",
|
||||
"समाज",
|
||||
"समूह",
|
||||
"सम्झौता",
|
||||
"सम्माना",
|
||||
"सय",
|
||||
"साँझ",
|
||||
"साथ",
|
||||
"साधारण",
|
||||
"सानै",
|
||||
"सामूहिक",
|
||||
"सार्वजनिक",
|
||||
"सुख्खा",
|
||||
"सुधार्न",
|
||||
"सुरूमा",
|
||||
"सौन्दर्य",
|
||||
"स्याहार",
|
||||
"हाम्रो",
|
||||
"हावाहुरी",
|
||||
"हुन",
|
||||
"हुर्किने",
|
||||
"हेरचाह",
|
||||
"हैन"
|
||||
]
|
||||
}
|
||||
|
|
1016
frontend/static/languages/nepali_1k.json
Normal file
1016
frontend/static/languages/nepali_1k.json
Normal file
File diff suppressed because it is too large
Load diff
1009
frontend/static/languages/persian_1k.json
Normal file
1009
frontend/static/languages/persian_1k.json
Normal file
File diff suppressed because it is too large
Load diff
5009
frontend/static/languages/persian_5k.json
Normal file
5009
frontend/static/languages/persian_5k.json
Normal file
File diff suppressed because it is too large
Load diff
54
frontend/static/quotes/nepali.json
Normal file
54
frontend/static/quotes/nepali.json
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"language": "nepali",
|
||||
"groups": [
|
||||
[0, 100],
|
||||
[101, 300],
|
||||
[301, 600],
|
||||
[601, 9999]
|
||||
],
|
||||
"quotes": [
|
||||
{
|
||||
"text": "फुलको आँखामा फुलै संसार\nकाँडाको आँखामा काँडै संसार\nझुल्किन्छ है छायाँ बस्तु अनुसार\nकाँडाको आँखामा काँडै संसार",
|
||||
"source": "फुलको आँखामा - Ani Choying Dolma",
|
||||
"length": 109,
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"text": "सिन्दुली गढी घुमेर हेर्दा\nसुन्तली माई\nकतिमा राम्रो दरवार\nमार्यो नी माया ले मार्यो",
|
||||
"source": "सिन्दुली गढी घुमेर हेर्दा",
|
||||
"length": 81,
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"text": "जीवनमा सबैभन्दा ठूलो महिमा भनेको कहिल्यै पनि नलडनु हो, र उठनु हो हरेक चोटि जब हामी लडछौं",
|
||||
"source": "नेलसन मण्डेला",
|
||||
"length": 88,
|
||||
"id": 3
|
||||
},
|
||||
{
|
||||
"text": "कुन मन्दिरमा जान्छौ यात्री,\nकुन मन्दिरमा जानेहो?\nकुन सामग्री पुजा गने\nसाथ कसोरी लानेहो ?\nमावनसहरूका कााँध चढी\nकुन देिपुरीमा जानेहो?",
|
||||
"source": "यात्री",
|
||||
"length": 131,
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"text": "बिकल्प त थुप्रै थिऐ\nनि:शब्द रहनु मेरै गल्ति थियो\nसारांश खोज्छु कथा पढनु आघि\nम त्यहि खोजिमा हराउनु मेरै गल्ति थियो,\nहर्ष को स्पर्श हुन्छ तब\nजब थकाई मर्छ पुरा दिनको\nनसोचेको पल पनि पल-पलाई रहन्छ, जब आभास हुन्छ तिम्रो",
|
||||
"source": "अविनवन भट्टराई",
|
||||
"length": 218,
|
||||
"id": 5
|
||||
},
|
||||
{
|
||||
"text": "एउटा अग्लो भिरमा प्रेमको मन्दिर बनाई, तिमीलाई देउता थाप्ने छू\nधोकाकी देवी नाम राखी, पुजारी म स्वयमं नै बन्ने छू\nतिम्रो बिछोडको अग्निले हवन गरी, बगाएका आशुले जल चढाउने छू\nचर्किएको मुटू समाती कटाईएका ति रातका निन्द्रा नैवद टक्र्याउने छू\nहाम्रा पिरतीका कुराकानीलाई भजन बनाई बिहान बेलुकी गाउने छू\nअनि तिमीलाई छोडी जानेको पीडा बुझाउने छु",
|
||||
"source": "अनिरुद्र",
|
||||
"length": 333,
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"text": "जीवनका भोगाइ सबै रङ्गमन्चको खेल न हो\nधनदौलत, यौवन, उर्लिने खहरेको भेल न हो\nकोही पर्दाबाहिर त कोही पर्दाभित्र जाल बुन्छन्\nकठपुतलीझैँ अर्कैले नचाउँछ जगत् एक झेल न हो",
|
||||
"source": "दुर्गाकिरण तिवारी",
|
||||
"length": 163,
|
||||
"id": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in a new issue