Compare commits
336 Commits
Jul-01-201
...
master
Author | SHA1 | Date |
---|---|---|
Robbie Antenesse | be67f965eb | |
Robbie Antenesse | ed7f06d061 | |
Robbie Antenesse | bfb74a0000 | |
Robbie Antenesse | 4c8f896462 | |
Robbie Antenesse | be44c4c7a5 | |
Robbie Antenesse | 4de0a7bf8b | |
Robbie Antenesse | 459edda009 | |
Robbie Antenesse | 9fcbb08468 | |
Robbie Antenesse | 2f0aa6e14c | |
Robbie Antenesse | 76e3f77b0b | |
dependabot[bot] | f1b99960f3 | |
Robbie Antenesse | d64890ec12 | |
Robbie Antenesse | acdc5cbdaa | |
Robbie Antenesse | efa623af85 | |
Robbie Antenesse | e1222a43f6 | |
Robbie Antenesse | 3dd370a375 | |
Robbie Antenesse | 34459b40a4 | |
Robbie Antenesse | 4318f90263 | |
Robbie Antenesse | d98cf9a584 | |
Robbie Antenesse | f388620d3e | |
Robbie Antenesse | e3c4ce7666 | |
Robbie Antenesse | 4228ac8063 | |
Robbie Antenesse | b61f128745 | |
Robbie Antenesse | 5a728dbd7c | |
Robbie Antenesse | b87f4e7cad | |
Robbie Antenesse | 5e803d988e | |
Robbie Antenesse | 15ad17e1d7 | |
Robbie Antenesse | a73b74ff71 | |
Robbie Antenesse | ec37fc53a5 | |
Robbie Antenesse | b3507128a6 | |
Robbie Antenesse | 54cc74b468 | |
Robbie Antenesse | f6446eedc9 | |
Robbie Antenesse | ce06308c1d | |
Robbie Antenesse | abacf7a56a | |
Robbie Antenesse | fb42b0ef2b | |
Robbie Antenesse | 6d7cdf68a9 | |
Robbie Antenesse | c2d02ceccb | |
Robbie Antenesse | bd8a3f475a | |
dependabot[bot] | b6811a07e4 | |
Robbie Antenesse | 90e0553f4e | |
Robbie Antenesse | adb95fbae3 | |
Robbie Antenesse | 8cc3b469f9 | |
Robbie Antenesse | e103420245 | |
Robbie Antenesse | d5e2ddec93 | |
Robbie Antenesse | 8f6dad8a84 | |
Robbie Antenesse | a30bdf5d63 | |
Robbie Antenesse | 4d63aa9034 | |
Robbie Antenesse | d1bcfc5b97 | |
Robbie Antenesse | e2d3164136 | |
Robbie Antenesse | 69c0539370 | |
Robbie Antenesse | 4d3132e151 | |
Robbie Antenesse | 40feae7194 | |
Robbie Antenesse | 65cf421cbe | |
Robbie Antenesse | 98cdd763f8 | |
Robbie Antenesse | 006f020424 | |
Robbie Antenesse | 5f15ec6353 | |
Robbie Antenesse | 4fee98c8fe | |
Robbie Antenesse | 4c4ec4536f | |
Robbie Antenesse | d4e7df495d | |
Robbie Antenesse | 7bc01c79b7 | |
Robbie Antenesse | 3a36817686 | |
Robbie Antenesse | 36898ff670 | |
dependabot[bot] | 46b1b5a01f | |
Robbie Antenesse | 143bde30f0 | |
Robbie Antenesse | 1d143e1d98 | |
Robbie Antenesse | 18f460f2e4 | |
Robbie Antenesse | 689a5d3a59 | |
Robbie Antenesse | 8d07a26d10 | |
Robbie Antenesse | 1ab93d69ae | |
Robbie Antenesse | ed9eba7137 | |
Robbie Antenesse | 5fa06b31c2 | |
Robbie Antenesse | e118cb8d14 | |
Robbie Antenesse | a9ae7f938c | |
Robbie Antenesse | 6fad67da93 | |
Robbie Antenesse | 7a80be9730 | |
Robbie Antenesse | 9965dba80e | |
Robbie Antenesse | c25114b9fd | |
Robbie Antenesse | 1b9a6fcda6 | |
Robbie Antenesse | abf0bc08a0 | |
dependabot[bot] | d3559d5465 | |
Robbie Antenesse | 484426d98b | |
Robbie Antenesse | 331d285899 | |
Robbie Antenesse | 03974b9d45 | |
Robbie Antenesse | b20c6703ef | |
Robbie Antenesse | 981490473f | |
Robbie Antenesse | d03abfa566 | |
Robbie Antenesse | 1bb8ace20a | |
Robbie Antenesse | 39ccddab18 | |
Robbie Antenesse | 9d08bbb337 | |
Robbie Antenesse | 122282d1a7 | |
Robbie Antenesse | f39dd886b1 | |
Robbie Antenesse | 725f47782a | |
Robbie Antenesse | abb8511999 | |
Robbie Antenesse | 5fbd06a035 | |
Robbie Antenesse | 466e5e84c6 | |
Robbie Antenesse | b2db35982f | |
Robbie Antenesse | 5ba84abd0e | |
Robbie Antenesse | f4ad87f73b | |
Robbie Antenesse | 10d204d695 | |
Robbie Antenesse | cb38d57053 | |
Robbie Antenesse | a8c1dbe52b | |
Robbie Antenesse | 54703649a3 | |
Robbie Antenesse | 8c92e56312 | |
Robbie Antenesse | 959355bf18 | |
Robbie Antenesse | 8c9ae4b100 | |
Robbie Antenesse | aefd7747d3 | |
Robbie Antenesse | 068edd7a94 | |
Robbie Antenesse | 6ee83c7773 | |
Robbie Antenesse | 26e4475fc2 | |
Robbie Antenesse | f8b20e7ba2 | |
Robbie Antenesse | 8f864fda67 | |
Robbie Antenesse | 6fed4dd4fb | |
Robbie Antenesse | 59bf9c48e0 | |
Robbie Antenesse | e490ef08d3 | |
Robbie Antenesse | 706a7042cc | |
Robbie Antenesse | d6bf2bc297 | |
Robbie Antenesse | 3d94a7c5ec | |
Robbie Antenesse | a533ccbeba | |
Robbie Antenesse | 07028f28a8 | |
Robbie Antenesse | 2a00d3d5b9 | |
Robbie Antenesse | ad8ca6addc | |
Robbie Antenesse | 105331bf68 | |
Robbie Antenesse | 377e71daf8 | |
Robbie Antenesse | bd895b28f8 | |
Robbie Antenesse | fd10920b61 | |
Robbie Antenesse | 13ed184390 | |
Robbie Antenesse | ee0e97c9c5 | |
Robbie Antenesse | 97b9f97dfe | |
Robbie Antenesse | e228af66e4 | |
Robbie Antenesse | cf5fb3cafd | |
Robbie Antenesse | c3bfe1531a | |
Robbie Antenesse | feb6c3e88a | |
Robbie Antenesse | 8731119dc2 | |
Robbie Antenesse | 740cdaa4fd | |
Robbie Antenesse | 386b2e626e | |
Robbie Antenesse | e2b5f1cd83 | |
Robbie Antenesse | 435547cb4d | |
Robbie Antenesse | eba7307328 | |
Robbie Antenesse | 84a308693f | |
Robbie Antenesse | 828608ee0b | |
Robbie Antenesse | 118b5c2d1a | |
Robbie Antenesse | 102877aefa | |
Robbie Antenesse | b476b69bb4 | |
Robbie Antenesse | e6b973165a | |
Robbie Antenesse | 769c6f0f15 | |
Robbie Antenesse | 74642dcc0a | |
Robbie Antenesse | 3553530e6e | |
Robbie Antenesse | e755026267 | |
Robbie Antenesse | 90464fd15c | |
Robbie Antenesse | 1b6247f457 | |
Robbie Antenesse | 8b6a3b05a4 | |
Robbie Antenesse | 9d793c6c7d | |
Robbie Antenesse | 9743e934d3 | |
Robbie Antenesse | 87b7bbff89 | |
Robbie Antenesse | 46b579d73f | |
Robbie Antenesse | 9ade94562c | |
Robbie Antenesse | 9df29b8d20 | |
Robbie Antenesse | 8b63808d2c | |
Robbie Antenesse | 6f320b39b1 | |
Robbie Antenesse | 239c66b616 | |
Robbie Antenesse | 8a80412cd6 | |
Robbie Antenesse | 11db09b44d | |
Robbie Antenesse | c0fe25007d | |
Robbie Antenesse | 457a6c6798 | |
Robbie Antenesse | 171abe17c6 | |
Robbie Antenesse | f8a0c00c2b | |
Robbie Antenesse | e717480d18 | |
Robbie Antenesse | ab02aea88f | |
Robbie Antenesse | 874d2ff3c2 | |
Robbie Antenesse | a47f38caa0 | |
Robbie Antenesse | 3ed27534ad | |
Robbie Antenesse | f3f4d18c48 | |
Robbie Antenesse | 0f23454473 | |
Robbie Antenesse | 8d2ee80c26 | |
Robbie Antenesse | 601cfc64bd | |
Robbie Antenesse | 5c5309a9a5 | |
Robbie Antenesse | d96817b485 | |
Robbie Antenesse | ac2d1b63c8 | |
Robbie Antenesse | 3c5d19f0c6 | |
Robbie Antenesse | 861bff60b4 | |
Robbie Antenesse | e1ba94c3c7 | |
Robbie Antenesse | acdde8da81 | |
Robbie Antenesse | 737dd169a1 | |
Robbie Antenesse | 09914440be | |
Robbie Antenesse | 1528503c52 | |
Robbie Antenesse | 3117637c59 | |
Robbie Antenesse | 3e1465b905 | |
Robbie Antenesse | 32ad87d661 | |
Robbie Antenesse | 1b0c6da3e4 | |
Robbie Antenesse | ba5baf8dd9 | |
Robbie Antenesse | 251ad12407 | |
Robbie Antenesse | dcee87e978 | |
Robbie Antenesse | 35ccf7bf2a | |
Robbie Antenesse | 2b4fd70b85 | |
Robbie Antenesse | db021647bd | |
Robbie Antenesse | 596efff70d | |
Robbie Antenesse | 04f70e4bfa | |
Robbie Antenesse | 4de8b572c3 | |
Robbie Antenesse | 601841c754 | |
Robbie Antenesse | b9cfce5a05 | |
Robbie Antenesse | b7bdd8d7e0 | |
Robbie Antenesse | 6292a73717 | |
Robbie Antenesse | 5ee6e9078e | |
Robbie Antenesse | 41a880068e | |
Robbie Antenesse | 240d226d4e | |
Robbie Antenesse | 92628cfa40 | |
Robbie Antenesse | 5587056f3b | |
Robbie Antenesse | e0b3a2b90e | |
Robbie Antenesse | 2eb5b4db84 | |
Robbie Antenesse | 92949673bb | |
Robbie Antenesse | d575e8f8d9 | |
Robbie Antenesse | 389b4d9015 | |
Robbie Antenesse | 0811dbde08 | |
Robbie Antenesse | c07ae23f8a | |
Robbie Antenesse | 1cf547dd2e | |
Robbie Antenesse | 91a5967727 | |
Robbie Antenesse | 9ca6ef15d7 | |
Robbie Antenesse | f9fa6a178d | |
Robbie Antenesse | a2542fe7a1 | |
Robbie Antenesse | a1ea105f66 | |
Robbie Antenesse | 10d2159262 | |
Robbie Antenesse | 56ce6c30a6 | |
Robbie Antenesse | aa956e9820 | |
Robbie Antenesse | 2bf0f15f67 | |
Robbie Antenesse | 4e1ee35c5b | |
Robbie Antenesse | ee30fe53b2 | |
Robbie Antenesse | 460506012e | |
Robbie Antenesse | 45e9e5230c | |
Robbie Antenesse | 079288a0a8 | |
Robbie Antenesse | 850b042d6b | |
Robbie Antenesse | d36eec52fa | |
Robbie Antenesse | f153e0c3ec | |
Robbie Antenesse | 4c5dafd6f0 | |
Robbie Antenesse | 1491fd1196 | |
Robbie Antenesse | 945e2a3c76 | |
Robbie Antenesse | 4f9f4a97ad | |
Robbie Antenesse | 1c2570684d | |
Robbie Antenesse | 686a7fa542 | |
Robbie Antenesse | 2ddd513098 | |
Robbie Antenesse | bc672a564a | |
Robbie Antenesse | d8111fbe1d | |
Robbie Antenesse | 88d7e4fd8c | |
Robbie Antenesse | 9e7e113cc2 | |
Robbie Antenesse | 21d91a9920 | |
Robbie Antenesse | 0eb8be330b | |
Robbie Antenesse | 2760efef80 | |
Robbie Antenesse | 2f96084b4a | |
Robbie Antenesse | 54c3054b25 | |
Robbie Antenesse | 4fc8cec5e3 | |
Robbie Antenesse | 48239809a5 | |
Robbie Antenesse | 85ab5e2b96 | |
Robbie Antenesse | 29eeb06ce4 | |
Robbie Antenesse | d57a4a2a48 | |
Robbie Antenesse | a012baeec2 | |
Robbie Antenesse | 6e24fca1bd | |
Robbie Antenesse | 3f690383f8 | |
Robbie Antenesse | a3b9a0a934 | |
Robbie Antenesse | acb94637c5 | |
Robbie Antenesse | b54e35be92 | |
Robbie Antenesse | ca7d219a96 | |
Robbie Antenesse | 352e6d3fc0 | |
Robbie Antenesse | 3a61d4ccc6 | |
Robbie Antenesse | 8d132f1919 | |
Robbie Antenesse | 66791d8dad | |
Robbie Antenesse | cbd22c5ee0 | |
Robbie Antenesse | ea61ba24bd | |
Robbie Antenesse | 0eaf289abc | |
Robbie Antenesse | 2c4c281850 | |
Robbie Antenesse | ec42aa3778 | |
Robbie Antenesse | 546c82996e | |
Robbie Antenesse | d9f8390672 | |
Robbie Antenesse | 5e4ded844e | |
Robbie Antenesse | 12d46c5eef | |
Robbie Antenesse | 39a5c11a11 | |
Robbie Antenesse | 7c5c33c54f | |
Robbie Antenesse | 41c6322477 | |
Robbie Antenesse | 9fe0da58db | |
Robbie Antenesse | b8ca61d3fe | |
Robbie Antenesse | 57ca675f7a | |
Robbie Antenesse | c0909343f6 | |
Robbie Antenesse | cbcc7ecfd4 | |
Robbie Antenesse | 7cdf43f2e5 | |
Robbie Antenesse | 64903fc544 | |
Robbie Antenesse | 881cd9cb2e | |
Robbie Antenesse | fa559afb8b | |
Robbie Antenesse | 8c39d05c13 | |
Robbie Antenesse | 9e993be503 | |
Robbie Antenesse | ffdd008344 | |
Robbie Antenesse | c44d4eee3e | |
Robbie Antenesse | 559d6a698f | |
Robbie Antenesse | c6ae72ec1b | |
Robbie Antenesse | 3c75fae3a8 | |
Robbie Antenesse | 5d7d0e319e | |
Robbie Antenesse | 3795eceed7 | |
Robbie Antenesse | 8ed216cb7a | |
Robbie Antenesse | 15d9201ef3 | |
Robbie Antenesse | 3d3a69c65f | |
Robbie Antenesse | 3340c96507 | |
Robbie Antenesse | 5dcccaba59 | |
Robbie Antenesse | 278b0b61d3 | |
Robbie Antenesse | 37689fdab1 | |
Robbie Antenesse | f2105465df | |
Robbie Antenesse | b0646209eb | |
Robbie Antenesse | 07694cd250 | |
Robbie Antenesse | af2b7edcfb | |
Robbie Antenesse | b034bb770b | |
Robbie Antenesse | 8a1589aaac | |
Robbie Antenesse | 038e47613e | |
Robbie Antenesse | 22a94e78f5 | |
Robbie Antenesse | 6227be15d0 | |
Robbie Antenesse | d33a2359b1 | |
Robbie Antenesse | 535020053e | |
Robbie Antenesse | f7eecf9796 | |
Robbie Antenesse | 5461a3f1ed | |
Robbie Antenesse | 86a37f0280 | |
Robbie Antenesse | 227d7f59f9 | |
Robbie Antenesse | 06bac52c36 | |
Robbie Antenesse | 188779131e | |
Robbie Antenesse | bd58301a9a | |
Robbie Antenesse | 918dad1ad8 | |
Robbie Antenesse | a1d8059068 | |
Robbie Antenesse | ce143be3f5 | |
Robbie Antenesse | e9cf9653be | |
Robbie Antenesse | eb0dd669bb | |
Robbie Antenesse | 1872fffba8 | |
Robbie Antenesse | 19e41958a4 | |
Robbie Antenesse | dafecd9582 | |
Robbie Antenesse | 63b1f20d58 | |
Robbie Antenesse | 08cadfb121 | |
Robbie Antenesse | 4a1dd7aae4 | |
Robbie Antenesse | b59c4702b2 | |
Robbie Antenesse | 520ede111b | |
Robbie Antenesse | eadc13e04b | |
Robbie Antenesse | 4bdeff3296 | |
Robbie Antenesse | 50b0941223 | |
Robbie Antenesse | 910e025997 |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"plugins": {
|
||||
"autoprefixer": {
|
||||
"browsers": [
|
||||
"overrideBrowserslist": [
|
||||
">1%",
|
||||
"last 4 versions",
|
||||
"Firefox ESR",
|
||||
|
|
|
@ -4,6 +4,9 @@ This is the light-as-possible rewrite of Lexiconga.
|
|||
|
||||
## Installation
|
||||
|
||||
0. Some dev tools require build tools.
|
||||
- On Windows, install them with `npm install --global windows-build-tools`.
|
||||
- Alternatively, you can just install the newest Python and Visual Studio (with Desktop C++ devkit).
|
||||
1. Clone and run `yarn` and `composer install` to install dependencies.
|
||||
1. Import `src/structure.sql` into a database called 'lexiconga' on your MariaDB server to get the database structure.
|
||||
1. Copy `src/php/api/config.php.changeme` to `src/php/api/config.php` and update the values within to enable connections to your lexiconga database.
|
||||
|
@ -22,6 +25,8 @@ This is the light-as-possible rewrite of Lexiconga.
|
|||
|
||||
It's less useful, but `npm run serve-frontend-only` will bundle and serve _only_ the front end stuff from `localhost:1234`. The bundled files all still get bundled into `dist`.
|
||||
|
||||
Parcel Bundler version < 2 doesn't use PostCSS 8+, so upgrading autoprefixer will not work. Keep autoprefixer at the highest version within the 9 major version until you're able to figure out how parcel-bundler 2+ works.
|
||||
|
||||
## Production
|
||||
|
||||
`npm run bundle` bundles and minifies the frontend stuff and also copies the backend stuff to `dist`. Be sure to run `npm run clear` to delete the contents of `dist` and `.cache` before using `npm run bundle` to make sure you don't get old dev versions of the bundled code included in your upload.
|
||||
|
@ -40,4 +45,3 @@ Be sure you set up email senders/receivers for at least these 3 email addresses:
|
|||
|
||||
- help (can be forwarder)
|
||||
- donotreply (must be sender)
|
||||
- ads (can be forwarder)
|
||||
|
|
56
ads.json
56
ads.json
|
@ -1,56 +0,0 @@
|
|||
[
|
||||
{
|
||||
"header": "Do You Like Lexiconga?",
|
||||
"body": "You can contribute a small amount each month toward keeping Lexiconga online and help us keep working on new features. We appreciate whatever you can offer!",
|
||||
"cta": "Support Lexiconga!",
|
||||
"link": "https://liberapay.com/robbieantenesse/donate",
|
||||
"start": "June 1, 2019",
|
||||
"end": "August 1, 2099",
|
||||
"isPriority": false
|
||||
},
|
||||
{
|
||||
"header": "Support Lexiconga's Developer",
|
||||
"body": "If you enjoy Lexiconga, you can make a one-time donation to the developer toward keeping it online and adding new features. Your donations are much appreciated!",
|
||||
"cta": "Buy Me a Coffee!",
|
||||
"link": "https://buymeacoffee.com/robbieantenesse",
|
||||
"start": "June 1, 2019",
|
||||
"end": "August 1, 2099",
|
||||
"isPriority": false
|
||||
},
|
||||
{
|
||||
"header": "A New Way to Role-Play",
|
||||
"body": "The GUTS+ System is a tabletop role-playing game system that aims to be approachable enough for newcomers and flexible enough for any setting.",
|
||||
"cta": "Learn to Play for Free!",
|
||||
"link": "https://guts.plus",
|
||||
"start": "June 1, 2019",
|
||||
"end": "March 1, 2020",
|
||||
"isPriority": false
|
||||
},
|
||||
{
|
||||
"header": "Protect Your Readers",
|
||||
"body": "RedFlag puts a wall between your readers or followers and content that you want to share. Choose up to 3 different warnings to help them be aware of what they're about to view!",
|
||||
"cta": "Use RedFlag for Free",
|
||||
"link": "https://redflag.ga",
|
||||
"start": "June 1, 2019",
|
||||
"end": "January 1, 2020",
|
||||
"isPriority": false
|
||||
},
|
||||
{
|
||||
"header": "Get Theonite: Planet Adyn free!",
|
||||
"body": "\"This book is the superhero fantasy you didn't know you needed. Magic, Afrofuturism, and heart-pumping suspense - this book has it all!\" - WeReadFantasy.com",
|
||||
"cta": "Read Free Now",
|
||||
"link": "https://mailchi.mp/ce525124aec3/theonite-planet-adyn-free-copy",
|
||||
"start": "June 1, 2019",
|
||||
"end": "January 1, 2020",
|
||||
"isPriority": false
|
||||
},
|
||||
{
|
||||
"header": "The Sword of Kaigen: A Theonite War Story",
|
||||
"body": "\"Fantasy and martial arts aficionados alike should enjoy this emotionally supercharged novel... which pierces readers' hearts with the precision of a samurai's razor-sharp blade.\" - Blue Ink Review",
|
||||
"cta": "Buy Now",
|
||||
"link": "https://www.amazon.com/Sword-Kaigen-Theonite-War-Story/dp/172019386X/",
|
||||
"start": "June 1, 2019",
|
||||
"end": "January 1, 2020",
|
||||
"isPriority": false
|
||||
}
|
||||
]
|
132
advertising.html
132
advertising.html
|
@ -1,132 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Advertising on Lexiconga</title>
|
||||
<meta property="description" content="Buy advertisement space on Lexiconga">
|
||||
<meta name="keywords" content="Lexiconga, advertising, ads, conlanging, dictionary, dictionaries, lexicon, conlangs, constructed languages, glossopoeia">
|
||||
|
||||
<meta property="og:site_name" content="Lexiconga">
|
||||
<meta property="og:url" content="https://lexicon.ga/advertising.html">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:title" content="Advertising on Lexiconga">
|
||||
<meta property="og:description" content="Buy advertisement space on Lexiconga">
|
||||
<meta property="og:image" content="processedImages/logo.png">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image:alt" content="Lexiconga logo">
|
||||
|
||||
<link rel="icon" href="processedImages/favicon.png" type="image/x-icon">
|
||||
<link rel="stylesheet" href="src/main.scss">
|
||||
</head>
|
||||
<body id="defaultTheme">
|
||||
<header id="top">
|
||||
<a href="/" title="Lexiconga"><svg id="title" alt="Lexiconga Logo" viewBox="0 0 249.78 55.087">
|
||||
<g transform="translate(-107.53 -155.84)">
|
||||
<g id="lexi">
|
||||
<path d="m144.03 159.39-11.339 22.409h-21.62l11.339-22.409z" />
|
||||
<path d="m132.69 183.97 11.339 22.409h-21.62l-11.339-22.409z" />
|
||||
<path d="m160.1 186.76v7.0042h-6.1056q-1.2442 0-1.7741 0.36864-0.50688 0.36864-0.50688 1.2442 0 0.99072 0.52992 1.3133 0.52992 0.29952 2.1658 0.29952h6.2899v8.9856h-7.3498q-5.184 0-7.9258-2.4653-2.7418-2.4883-2.7418-7.1655v-12.326q0-4.5159 2.5805-7.0042 2.6035-2.4883 7.3037-2.4883h8.1332v9.0317h-5.9674q-2.4192 0-2.4192 1.5667 0 0.92161 0.576 1.2902 0.57601 0.3456 2.0506 0.3456z" />
|
||||
<path d="m187.75 174.53-8.1792 15.299 8.4557 16.151h-9.8381l-3.8707-7.6263-3.2717 7.6263h-9.6768l8.64-15.898-8.1792-15.552h9.746l3.6634 7.1424 2.9491-7.1424z" />
|
||||
<path d="m198.95 174.53v31.45h-9.3543v-31.45z" />
|
||||
</g>
|
||||
<g id="conga">
|
||||
<path d="m218.5 205.38h-3.2347q-7.6994 0-10.114-3.918-1.8223-2.9613-1.8223-11.481 0-7.5172 1.8223-10.478 2.4146-3.918 9.294-3.918h4.7381q6.5604 0 8.7928 1.8679 2.5513 2.1413 2.5513 8.4739-0.77449-0.13667-1.4123-0.13667-0.68338 0-2.0501 0.13667-1.3668 0.13668-2.0957 0.13668l-1.7768-0.13668q0-2.5057-1.5946-3.3713-1.0934-0.59227-3.6902-0.59227h-1.8679q-3.3258 0-4.5559 1.8679-0.95673 1.4579-0.95673 4.647 0 1.1845 0.0456 3.098 0.0911 1.9135 0.0911 1.959 0 4.0092 1.7768 4.9203 1.0478 0.5467 5.6493 0.5467 2.1868 0 3.0069-0.22779 2.0957-0.59226 2.0957-3.6447 0.45558-0.13668 1.139-0.27335 0.72894-0.13668 1.1845-0.13668 0.68338 0 2.0501 0.2278 1.3668 0.22779 2.0957 0.22779 0.5467 0 0.86561-0.0456 0 6.606-2.7791 8.6106-2.2324 1.6401-9.2484 1.6401zm3.0069-31.208q3.8269 0.68338 7.0616 1.549 0.63782 1.0023 1.0934 2.1412 0.50114 1.139 0.50114 2.0046l-1.5946-3.0524-3.5991-1.4123q-1.4579-0.18224-2.1868-0.18224-0.68338 0-1.6401 0h-3.8725q1.0478-0.13667 2.1868-0.36447 1.6401-0.36447 2.0501-0.68338zm10.797 13.303q-0.50115 0-2.1868-0.45559-0.82006-0.22779-1.3668-0.22779-0.27335 0-0.63782 0.0911-0.31891 0.0456-0.82006 0.2278-1.7312-0.18224-2.7335-0.31891-1.0023-0.13668-1.0934-0.31891l4.5559-0.13668q0.7745 0 1.5034 0 0.72894 0 1.5946 0.13668l-0.0911-8.0183q0.22779 2.1413 0.59226 4.3281 0.36447 2.1412 0.95673 4.6925h-0.27335zm-0.59226 13.531-1.2301-0.22779 0.31891-0.59227q0.0456-0.0911 0.0456-0.91117v-1.3668q0-0.91117 0.2278-1.8679 0.22779-1.0023 0.50114-1.959l0.13668 5.1481v1.7768zm-23.28-25.604q0.13668-0.0456 0.63782-0.31891 0.54671-0.27336 0.72894-0.27336l0.13668 0.18224q0.22779 0 0.68338-0.22779 0.50114-0.27336 0.72893-0.36447 0.2278-0.0911 1.6857-0.13668 1.4579-0.0911 2.7791-0.0911l-3.7358 0.68338-3.6447 0.54671zm8.6561-0.59227-0.95673 0.36447-1.9135-0.13667q1.3212-0.41003 2.688-0.82006l2.369-0.13667q-0.5467 0.13667-1.0023 0.27335-0.41003 0.0911-1.1845 0.45558zm-5.786 14.032q0-0.5467 0.0911-1.0023 0.13668-0.50114 0.36447-1.139v0.50114q0 0.13668 0.31891 2.1413 0.0911 0.59226 0.0911 1.2756v5.5582l-0.86562-4.1458 0.18224-0.59226q0-0.0911-0.0911-1.139-0.0911-1.0478-0.0911-1.4579zm0.72894-5.4215 0.82006-0.63782q-0.36447 0.82005-0.36447 1.6401v0.31891l-0.68338 1.3668zm0.45559 13.759q0.18223 0.0455 1.0023 0.31891 1.0934 0.27335 1.6857 0.27335l3.3258-0.68338q0.27335-0.0456 1.0023-0.18224 0.7745 0 1.5946 0l1.4123-2.4602q-0.36447 1.0478-0.45559 1.4579-0.22779 0.86561-0.22779 1.4579l0.0911 0.22779 0.13667-0.0456q-4.237 0.41002-8.4739 0.77449l-1.0934-1.139zm19.18 4.9659q-0.68338 0.63782-1.6857 1.2756-0.95673 0.63782-1.7312 0.91118 0.41003-0.36447 1.2756-1.0934 0.50115-0.45559 1.139-1.1845l1.0023 0.0911zm-4.8292 3.4169-1.5034 0.0456 2.7335-1.0934q-0.31891 0.22779-0.63782 0.50115-0.31891 0.22779-0.59226 0.5467zm-24.921-21.64-0.13667-4.5103 1.5946-2.2779q1.0023-0.22779 1.6401-0.77449 0.95673-1.0478 1.959-2.1413 0.31891-0.31891 1.7768-0.45558 1.4579-0.13668 3.9636-0.2278-1.0023 0.2278-2.0501 0.50115-1.0478 0.22779-1.959 0.50114-1.7768 0.63782-3.7814 2.2324-2.4146 1.9135-2.5968 3.4625zm5.3759 22.233q-2.688-2.7335-3.9636-7.4716-1.0023-3.7814-1.0023-8.5195 0.22779 1.5946 0.68338 4.647 0.45559 3.0069 1.1845 5.0114 0.13668 0.41002 0.45559 1.2756 0.31891 0.86561 0.63782 1.3668l1.6401 2.1868zm-4.9659-16.811v-2.8702q0-0.41003 0.50115-2.7335 0.50114-2.3235 1.3212-5.6493 0.27335-0.18223 1.7768-1.1845 1.549-1.0478 1.5946-1.4123l0.95673 0.82006q-0.63782 0.0911-1.8679 0.86561-0.86561 0.54671-1.6857 1.2301-0.18223 0.95673-0.50114 1.9135-0.27335 0.95673-0.63782 1.6857 0 0.0456-0.31891 0.45559-0.13668 0.22779-0.18224 0.41003zm20.957 16.447q0.59226 0 0.86561 0.0911-2.1412 0.45558-4.6925 0.95673-2.5057 0.5467-3.6902 0.5467-1.3668 0-3.2802-0.27335-1.8679-0.27335-3.1891-0.63782l-0.72894-1.0478 5.9682 0.7745q0.27335 0.0456 0.91117-0.0456 0.68338-0.0911 0.82005-0.0911 0.0911 0 1.0478 0.0911 1.0023 0.0911 1.3668 0.0911 0.59226 0 2.9158-0.31891 1.0023-0.13668 1.6857-0.13668zm-21.003-9.8407q0.31891 0.54671 0.59226 2.5513 0.0911 0.86561 0.27336 1.0478l0.50114 0.63782-0.0456 0.18224 0.18224 0.36447q0.45558 0.63782 0.59226 1.7312 0.18223 1.0478 0.31891 2.0046l2.0501 2.2779-2.4146-1.8679q-0.72894-2.0046-1.3668-4.0092-0.77449-2.5057-1.8679-6.8794l1.1845 1.959zm26.06-1.2756q-0.68338 0-1.9135-0.0911-1.1845-0.13668-2.5513-0.27336 0.68338-0.36446 3.5991-0.50114 2.9158-0.18223 2.9613-0.18223l-0.63782 0.68337-1.4579 0.36447zm-9.1117-12.027 2.8702 1.0478 0.27335 1.0934q-2.1868-1.0023-4.4192-1.549-2.2324-0.59227-4.4647-0.59227z" />
|
||||
<path d="m262.61 195.81q0 0.77449-0.0911 2.6424-0.0911 1.8223-0.0911 1.959-1.2301 1.7768-2.9613 3.7358 0.91118-1.2756 1.4579-2.1868 1.0934-1.7312 1.0934-2.4146 0-2.4146-0.0911-4.4192-0.31891-6.9249-0.31891-7.426 0-0.59226-0.13668-1.4123-0.31891-1.959-0.31891-2.4146 0-0.72894 0.18224-1.5946 0.18223-0.86561 0.36447-1.7312 0.36446 3.5536 0.63782 7.7905 0.27335 4.1914 0.27335 7.4716zm-12.939 8.9295h-4.0547q-5.9682 0-8.7472-2.688-2.7791-2.688-2.7791-8.6561v-5.1026q0-5.6037 2.9158-8.2917 2.9158-2.688 8.565-2.688h3.6447q5.7859 0 8.7928 2.5513 3.1891 2.7335 3.1891 8.4283v5.1026q0 6.1504-2.688 8.7472-2.688 2.5968-8.8384 2.5968zm4.4192-16.264q0-3.098-1.0934-4.2825-1.0934-1.1845-4.1914-1.1845h-2.4146q-3.0069 0-4.1003 1.1845-1.0934 1.1845-1.0934 4.1914v4.7836q0 3.1435 1.0478 4.4647 1.0934 1.3212 4.1458 1.3212h2.4146q3.0524 0 4.1458-1.3212 1.139-1.3668 1.139-4.5103zm-1.2301-11.8q1.4123 0.45558 3.508 1.5034 1.7312 0.91117 3.4624 1.8224 0.41003 0.59226 0.68338 1.5034 0.0911 0.27335 0.22779 0.86561l0.27336-2.8702q-3.0524-0.63782-5.8315-1.2301-2.7335-0.59226-3.098-0.7745l-1.4123-1.1845q0.54671 0.0911 1.139 0.18223 0.63782 0.0456 1.0478 0.18224zm-14.67 2.0501q-1.8679 1.2756-2.4602 1.9135-0.5467 0.63782-0.82005 1.5946 1.5034-1.549 2.8702-2.8702 1.7312-1.6401 3.0524-2.5968 1.8679-0.22779 3.8269-0.50114t3.5991-0.27335q0.50114 0 1.139 0.13667 0.63782 0.13668 1.4579 0.31891-1.139 0.36447-2.2779 0.50115-1.139 0.0911-2.7791 0.0911-0.86561 0-2.4146-0.0456-1.5034-0.0456-2.1413-0.0456-0.41002 0-1.549 0.72894-0.77449 0.5467-1.5034 1.0478zm17.221 25.832q1.0934-0.50115 2.4146-1.2301 1.5946-0.86562 2.2779-1.2756l-6.3782 4.1458-2.369-1.3212q0.59227-0.13668 2.1868-0.18224 1.6401-0.0455 1.8679-0.13667zm-3.7358 1.6401q-0.68338-0.13668-1.4579-0.27335-0.72894-0.0911-1.139-0.0911l-0.27335 0.0911q-0.22779-0.0911-0.68338-0.27335-0.45558-0.13668-0.82005-0.36447zm-18.315-24.055q1.5946-1.139 3.1891-2.5057 1.9135-1.5946 3.0069-2.8246-0.82006 0.2278-1.549 0.45559-0.72893 0.22779-0.77449 0.27335-1.959 1.6857-2.8246 3.3713-0.86562 1.6401-0.86562 4.1003 0 0.45559 0.18224 1.4579 0.18223 1.0023 0.18223 1.5034l-0.13668 9.5673q-0.13667-1.2756-0.36446-2.9613-0.45559-3.3713-0.45559-5.0114 0-3.7358 0.41003-7.426zm4.4647 21.549q3.3713 1.7312 6.9705 2.0501 0.86561 0.0911 1.6401 0.18224 0.7745 0.0911 1.1845 0.27335-1.6857-0.68338-4.2825-1.2756-6.4693-1.4123-7.3805-1.7312-0.54671-0.63782-1.139-2.0957-0.59226-1.4579-1.2756-4.0547 0.72894 1.4123 2.0501 3.5991 1.7312 2.7791 2.2324 3.0524zm14.989-13.622 0.45558 0.86561-0.0911 6.1048-0.59226-1.5034q0.27335-2.0046 0.27335-3.9636l-0.0456-1.5034zm-0.31891-4.647q0.59226 0.45558 0.82005 1.4123 0.2278 0.95674 0.2278 2.0046l-0.13668 1.2301zm-10.023 0.77449q0 0.68338 0.13667 2.0501 0.13668 1.3668 0.13668 2.0957v2.1868q0 1.3212 0.22779 2.1412 0.41003 1.0934 0.68338 2.0046 0.18223-0.0456 0.82006 0.68338 0.63782 0.68338 0.82005 1.0023l-1.1845-0.36447q-1.5946-1.7768-2.0501-3.4624-0.41003-1.7312-0.41003-4.647v-1.4579q0-1.2756 0.27335-2.3235 0.31891-1.0934 1.1845-1.8224zm9.5673-2.3235q-0.36447 0.41002-1.2301 0.72893-0.82005 0.27336-1.5034 0.27336h-3.508l-1.5034-1.0934 7.745 0.0911zm-4.7836 13.986q0.59226-0.0911 1.2756-0.2278 0.68338-0.13667 1.0934-0.13667l2.4146 0.13667-3.3258 0.72894z" />
|
||||
<path d="m287.51 205.02q-0.82006 0-1.6401 0-0.68338-0.13668-1.0023-0.18224l-11.891-20.274v20.866h-7.1071v-29.431q3.0524-0.36447 5.7859-0.36447 2.4602 0 5.1481 0.36447 2.1412 5.057 4.8292 10.023 2.4146 4.4192 5.7404 9.5673 0-3.2802-0.0911-5.8771-0.31891-9.1573-0.31891-9.7951 0-2.7335 0.0455-3.1436 0.0456-0.45558 0.36447-2.0046h7.0616v29.75q-0.95673 0.13668-2.9158 0.31891-1.9135 0.18224-4.0092 0.18224zm-10.114-31.39q0.0911 0.22779 0.31891 0.63782 0.22779 0.41003 0.31891 0.7745l-0.0456-0.18224q0.2278 0.45559 0.72894 1.4579 0.45559 1.2301 0.91117 2.4146l-1.2301-0.45558q-0.0911-1.0934-0.36447-2.0957-0.22779-1.0023-0.63782-2.5513zm18.725 1.3668v1.959q0 2.6424-0.27335 4.647-0.27335 1.959-1.139 3.2347 0.31891-2.6424 0.45558-5.1026 0.18224-2.4602 0.18224-4.7381zm-0.95673 30.752 0.0456-19.772q0 3.2802 0.31891 9.8407t0.31891 9.8407l-2.2779 0.59226q-1.4123 0-3.1891-0.2278-1.7768-0.22779-3.4169-0.41002l8.2005 0.13667zm-8.4283-12.665q-2.0046-3.4624-4.0092-6.9705-3.0524-5.5126-3.0524-6.8338 2.4146 3.918 3.7358 6.1504 2.4602 4.1914 2.4602 6.3782 0 0.31891-0.0911 0.68338t-0.18223 0.72894l1.139-0.13668zm-14.533-18.406q-2.5057 0.27335-5.2848 0.7745l-1.3212-1.2301zm3.8269-0.22779 1.0023 0.5467-3.3713-0.27335zm5.1026 24.966q2.1868 3.4169 4.5103 6.3782l4.5103 0.27335-4.6014 0.72894q-0.18223-0.27335-0.7745-0.7745-0.5467-0.45558-0.77449-0.91117-1.549-2.8246-3.0524-5.6948-1.7312-3.1891-3.3258-5.7404-1.959-3.098-3.6902-5.1481l1.0934-0.22779q0.27336 2.4602 2.5057 5.9682 1.7768 2.5968 3.5991 5.1481zm-15.9 5.6493q-0.27336-1.7768-0.59227-3.8269-0.27335-2.0501-0.27335-3.4624 0-2.4602 0.63782-12.392 0.27335-4.237 0.27335-7.426 0-0.82006-0.0911-1.4123t-0.31891-2.0957q-0.13668 0.63782-0.2278 1.2301-0.0911 0.5467-0.0911 1.2301 0 2.3235 0.18223 8.2006 0.18224 5.8771 0.31891 10.342 0.18224 4.4647 0.18224 9.6129zm7.6083 0.59227q0.41003-2.6424 0.50115-4.3736 0.13667-1.7768 0.13667-4.8748-0.0456-2.3235-0.0911-4.647-0.0455-2.369-0.0455-4.6925 0.63782 3.6902 0.68337 4.1003 0.0911 0.36447 0.31891 2.2324 0.2278 0.2278 0.27336 1.3212 0.0455 1.0478 0.0455 1.9135l0.18224-0.18223q-0.18224 0.5467-0.36447 1.0934-0.45559 1.4123-1.0023 1.4123-0.41003 0-0.59226-0.31891-0.13668-0.36447-0.13668-0.77449v-0.2278h1.2756l0.0456 8.155-1.2301-0.13667zm-8.4283-28.747q0 1.2756-0.41003 12.848-0.13667 3.8725-0.13667 7.6994h0.45558q0-5.1481-0.22779-11.162-0.22779-6.0137-0.68338-10.57l1.2301-1.0934q-0.22779 1.2756-0.22779 2.2779zm0.68338 29.613 0.18223-0.95673h0.50115q1.7312 0 3.7814 0.22779 2.0957 0.22779 3.7814 0.59226zm29.294-33.076 1.6401 0.59226-0.59226 0.63782q-0.86562-0.0911-1.7768-0.18224-0.91117-0.0911-1.6857-0.0911h-2.2324l-2.7335 0.0911q1.0023-0.59226 2.0046-0.82005 1.0478-0.22779 2.2324-0.22779z" />
|
||||
<path d="m314.06 205.29h-3.1891q-6.4693 0-9.4306-4.2825-2.5513-3.6902-2.5513-10.478 0-7.5627 1.8223-10.752 2.4602-4.2825 9.294-4.2825h4.6014q4.8748 0 7.9728 2.2324 3.508 2.5057 3.508 7.1982h-7.426q-0.31891-1.9135-2.1868-2.5968-1.2756-0.45558-3.6902-0.45558h-1.1845q-3.2347 0-4.3281 1.7312-0.82006 1.3212-0.82006 4.7837v3.5536q0 4.5103 1.3212 5.7404 1.3212 1.1845 5.8771 1.1845 3.5991 0 4.4192-1.0478 0.68337-0.86562 0.68337-4.5559h-5.1937v-5.6948q1.4123 0 4.2825 0.18224 2.8702 0.13667 3.918 0.13667 2.2779 0 4.3281-0.31891v7.4716q0 5.6493-3.2802 8.1094-2.8702 2.1413-8.7472 2.1413zm10.114-29.203 2.0501 2.8246q0.27336 0.86561 0.54671 3.2802 0.13667 1.3212 0.36447 3.3713 0.0456-0.18223-0.45559-0.45558-0.50114-0.27335-0.50114-0.41003 0-0.0911 0.0911-0.59226 0.13668-0.50115 0.13668-0.7745 0-0.77449-0.86561-2.5968-0.86562-1.8679-1.4579-2.2324 0.22779-0.59226 0.59226-0.95673-0.18223-0.13667-0.63782-0.27335-0.41003-0.13668-0.59226-0.22779l0.72894-0.95673zm-3.098-0.31891-3.4169-0.82006q-0.95673 0.0456-1.9135 0.0911-0.95673 0.0456-1.9135 0.0456-0.31891 0-0.63782 0-0.27336 0-0.59227-0.0456l-7.016-0.63782 12.848 0.22779 2.6424 1.139zm2.2779 0.45558v0.27335l-1.7768-0.31891 1.7768 0.0456zm3.9636 14.305-0.13667 0.7745q-0.18224 0.91117-0.31891 2.5057-0.13668 1.549-0.27336 2.7791v-0.36446q0-1.1845 0.0911-3.1436 0.13668-2.0046 0.13668-2.4146 0-0.68338-0.0911-1.2301-0.0911-0.5467-0.13668-0.86561 0.13668 0.41003 0.31891 0.95673 0.2278 0.54671 0.41003 1.0023zm-16.037-7.6538h1.6401q1.549 0.63782 3.2802 1.3212 1.7312 0.63782 3.0069 0.86561l4.7836 0.18223 2.369 0.0911-1.0934 0.63782q-1.7768-0.13667-3.8269-0.27335-2.0501-0.18223-3.098-0.50114-0.18224-0.0456-1.8224-1.2301-1.6401-1.1845-1.9135-1.2756l-3.1435-0.27333zm-8.0639-6.9249q-0.68338 0.5467-2.0501 1.959-1.0023 1.0478-2.0046 2.0501 1.139-2.0957 2.2779-4.237l3.8269-0.68338zm8.9295 17.449q0-1.0934 0.0911-3.6902 0.0911-2.6424 0.0911-2.8246l0.72894 0.18224-0.0456 0.86561q0 0.86561 0.0456 2.5968t0.0456 2.6424l3.6447 1.2756 1.4579 2.688-0.27335-2.5513q-0.0456-0.45559-1.0023-0.59227-0.91117-0.18223-2.4146-0.18223-0.54671 0-1.0023 0.0456-0.41003 0-0.72894 0-0.63782 0-0.63782-0.45559zm11.207 11.162q0.63782 0 0.91118-0.59226 0.68337-1.5946 1.139-3.1891 0.45559-1.6401 0.68338-3.3258l1.139 0.45558-0.36446-0.86561q-0.13668 0.31891-0.36447 1.0023-0.18224 0.68338-0.41003 1.4579-0.86561 2.5968-1.0478 2.8702-0.41003 0.68338-2.369 2.2779-0.18223 0.18223-0.63782 0.27335-0.41002 0.0911-0.77449 0.0911l1.139-0.63783q0.54671 0.18224 0.95673 0.18224zm-15.718-10.433q0-0.22779-0.0911-3.4169-0.0911-3.1891-0.0911-4.5103v-0.5467q0-0.91117 0.18224-1.2301 0.22779-0.31891 0.5467-0.31891t0.59226 0.41003q0.0911 0.13667 0.0911 0.31891 0 1.0934-0.7745 4.4647-1.0934 4.8292-1.139 4.9659l1.6401 3.2802-0.0911 0.0456q-0.0911-0.31891-0.0911-1.139 0-0.82006-0.0911-1.0478-0.0456-0.22779-0.50115-0.86561-0.18223-0.2278-0.18223-0.41003zm1.0934 12.073q2.0501 0 6.1049-0.36447 4.0547-0.36447 6.0593-0.36447l-1.139 0.82006q-0.63782-0.36447-1.0478-0.45559-0.36447-0.0911-1.5946-0.27335-0.50115-0.0456-2.9613 0.50114-2.4602 0.54671-3.0069 0.82006zm-10.433-11.071q-0.31891 0.0911-0.50114 0.45559l1.0478 1.1845q-0.68338-1.4123-0.86562-2.5057-0.13667-1.139-0.13667-3.0069v-2.4146q0-0.59226 0.31891-3.0069 0.13667-1.0478 0.13667-1.7312 0-0.13668-0.27335-0.50115-0.27335-0.36446-0.27335-0.59226 0-0.50114 2.8246-4.6925 2.688-3.9636 2.9613-4.1914l1.8224-0.82006q-1.2756 1.0934-4.4192 5.6493-3.1435 4.5103-3.1435 5.467l-0.0456 3.9636q0 1.3212 0.13668 3.918 0.18223 2.5968 0.41002 2.8246zm-1.6857-5.9226q0.27335 2.3235 0.68338 4.4192 0.41002 2.0501 0.63782 2.4146l-0.2278 0.59226q2.4602 2.5968 4.8292 4.6014 3.098 2.6424 5.9226 4.0547 3.4169 1.7312 6.3782 1.7312l4.1914-0.27335q-1.139-0.18223-5.9682-0.45558-4.8292-0.27336-7.6538-1.3668-0.18224-0.0911-1.9135-1.6857-1.3212-1.2301-1.8679-1.7768-1.7768-1.7768-2.2779-2.369-1.4123-1.6401-1.5946-2.7335-0.13667-1.0934-0.13667-2.0501 0-0.86561 0.13667-2.6424 0.13668-1.7768 0.13668-2.5968zm12.756 8.6561 8.2006-0.0911-0.82005 0.50114-6.4693 0.0456-0.91118-0.45558zm4.6014-10.843 10.57 0.31891q-1.0023 0-2.2324 0.0456-1.1845 0-2.6424 0h-0.95673q-1.139 0-2.5968-0.0456-1.4579-0.0456-2.1413-0.31891z" />
|
||||
<path d="m345.75 204.47-1.6401-6.3782h-8.1094l-1.7312 6.3782h-5.8771l7.0616-26.515h9.1117l7.0616 26.515zm-5.7404-21.504-2.9158 10.752h5.8315zm-2.1413-6.3326q0.18224 0 1.3212 0.13667 1.139 0.0911 1.7768 0.0911 0.59226 0 1.139 0 0.59226-0.0456 1.1845-0.0456h0.82005l-0.0456 0.63782q-0.18223 0-2.9613 0.0911-2.7335 0.0911-3.9636 0.0911h-2.5513q0.45559-0.13668 0.91117-0.36447 0.68338-0.36447 0.72894-0.59226 0.27335-0.0456 0.68338-0.0456t0.95673 0zm7.0616 0.63782 1.139 0.5467q0.59226 2.8246 0.86561 4.237 0.54671 2.5513 1.0934 4.237-1.0934-2.6424-1.7312-4.4647-0.86561-2.4602-1.3668-4.5559zm5.7404 22.688-0.18224 0.41003q-0.0456-0.59226-0.0911-1.2301 0-4.2825-1.0934-7.4716-0.45559-1.3212-1.8679-4.647l0.95673-0.36447q0.59226 3.5536 1.0478 5.467 0.22779 1.0023 2.2779 8.8839l-1.0478-1.0478zm1.0478 1.7312q0.36446 0.95673 0.95673 1.8223 0.95673 1.4123 1.0934 1.5946l-1.0478-0.18223q-0.13667-0.36447-0.45558-1.0478-0.27335-0.68338-0.36447-1.0934-0.0911-0.45558-0.18223-1.0934zm-17.631-22.962 0.68338-0.36446q-1.0023 3.5536-2.1413 7.5172-1.2756 4.4192-2.2324 7.5172-0.41003 1.3212-2.369 7.5627 1.3212-5.6037 2.8702-11.162 0.45558-1.6857 3.1891-11.071zm6.2871 19.454q0.86562 0 1.959 0.18223 0.36447 0.0456 1.139 0.18223l-0.18223-0.45558q-2.9158 0-4.2825 0.50114-0.31891 0.13668-2.7791 1.1845l0.54671-1.4579zm-13.394 6.2415q0.86561-1.0934 2.369-7.0616 0.95673-3.9636 1.9135-7.9728l-3.4625 15.809zm6.1048-24.647 0.27335-0.18223q-0.7745 2.9158-1.7312 6.196-0.91117 3.2802-2.0046 6.7427 0.68337-3.2347 1.4123-6.4693 1.0023-4.0547 2.0501-6.2871zm10.387 20q0.36447-0.0456 1.0023 2.5513 0.68338 2.5968 1.0478 2.5968 0.86561 0 1.8223 0t2.0046-0.0456q1.3212 0 2.0957 0.0456 0.82006 0.0456 1.5946 0.27335-0.50114 0.31891-1.6857 0.41003-1.139 0.0911-2.0046 0.0911l-5.1026-0.13667q-0.13668 0.0456-0.18224-0.13668 0-0.18223 0-0.63782v-0.68338q0-0.63782-0.18223-1.7768-0.18224-1.1845-0.41003-2.5513zm-7.745 0.77449q0.18223 0.59226 0.18223 1.4579 0 0.5467-0.0456 1.1845-0.0455 0.63782-0.0455 0.68338l0.13667 1.4123h-1.8679q-1.139 0-3.7814-0.0911-2.6424-0.0911-2.8246-0.0911 4.237 0 7.6538-0.36447 0.13667-0.45558 0.18223-1.2301 0.0456-0.77449 0.0456-0.86561 0.18223-0.0911 0.27335-1.0023 0.0911-0.95673 0.0911-1.0934zm6.0137-11.982-1.2301-2.5057 1.0934 4.237zm0.54671 4.5559-0.54671 0.22779-0.0456-0.86561v-0.27335l0.0456-0.50115zm-2.2779-8.7017-1.9135 8.7017 3.0524-0.0456 0.0911 0.41003h-3.6447l2.4146-9.0662z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg></a>
|
||||
</header>
|
||||
<main>
|
||||
<section id="mainColumn">
|
||||
<section id="detailsSection">
|
||||
<h2 id="dictionaryName">Advertising on Lexiconga</h2>
|
||||
<article id="detailsPanel" style="max-height:unset;">
|
||||
<h3>About</h3>
|
||||
<p>
|
||||
Advertisements are shown in-line with dictionary entries as plain text with a link to your advertised content.
|
||||
You get up to 50 characters for your headline, up to 200 characters for your body text, and 30 characters for the
|
||||
text in a button that links wherever you would like it to go.
|
||||
</p>
|
||||
<figure>
|
||||
<img src="src/images/ad-example.jpg" alt="Inline Ad Example" style="max-width:100%;">
|
||||
<figcaption>Your in-line advertisement will look like this.</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
At Lexiconga, we only want to display advertisements that will appeal to our users, so we work directly with our
|
||||
advertisers to maintain a manually curated selection of ads. As an advertiser, you will be creating a professional
|
||||
relationship with Lexiconga to ensure that your advertisements are up to par with what we want our users to see.
|
||||
All this means is that as long as your advertisement is relevant and your product is not predatory, you're good to go.
|
||||
</p>
|
||||
<p>
|
||||
"Relevant" advertisements include but are not limited to:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Books and Authors</li>
|
||||
<li>Conlanging tools</li>
|
||||
<li>Writing tutorials and resoruces</li>
|
||||
<li>Art tutorials and resources</li>
|
||||
<li>Video and tabletop games</li>
|
||||
<li>Blogs and vlogs</li>
|
||||
<li>Cool and interesting products</li>
|
||||
<li>Kickstarters related to the above</li>
|
||||
</ul>
|
||||
<p>
|
||||
We want to help you get the word out about your project or product to Lexiconga users in a way that doesn't
|
||||
interfere with their work while also allowing them to find your cool, interesting, and helpful things.
|
||||
</p>
|
||||
|
||||
<h3>Pricing</h3>
|
||||
<p>
|
||||
As with the advertisements themselves, the pricing structure is fairly simple, too:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Monthly:</strong> $5.00 per ad
|
||||
<ul>
|
||||
<li>With Priority: $20.00 per ad</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Yearly:</strong> $50.00 per ad <em>(2 months discounted)</em>
|
||||
<ul>
|
||||
<li>With Priority: $200.00 per ad</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>What's Priority?</h3>
|
||||
<p>
|
||||
All current advertisements are shuffled and then displayed to Lexiconga users once every 10 words. If you
|
||||
rent your ad space with Priority, then your ads will be shuffled among any other Priority ads and shown
|
||||
<em>before</em> any other ads that were not rented with priority.
|
||||
</p>
|
||||
|
||||
<h3>Get in Touch</h3>
|
||||
<p>
|
||||
If you would like to advertise on Lexiconga or if you have any questions, please contact us at
|
||||
<a href="mailto:ads@lexicon.ga?subject=Advertising%20on%20Lexiconga">ads@lexicon.ga</a> to get started.
|
||||
We look forward to working with you!
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
<footer id="bottom">
|
||||
<a href="https://liberapay.com/robbieantenesse" target="_blank" rel="noopener" class="small button">Support Lexiconga</a>
|
||||
<a href="https://blog.lexicon.ga" target="_blank" rel="noopener" class="small button">Blog</a>
|
||||
<a href="." target="_blank" class="small button">Advertise</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/issues" target="_blank" rel="noopener" class="small button">Issues</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/releases" target="_blank" rel="noopener" class="small button">Updates</a>
|
||||
<span class="separator">|</span>
|
||||
<a class="button" id="helpInfoButton">Help</a>
|
||||
<a class="button" id="termsInfoButton">Terms</a>
|
||||
<a class="button" id="privacyInfoButton">Privacy</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -9,7 +9,7 @@ if (!fs.existsSync(folder)) {
|
|||
|
||||
const favicon = sharp('./src/images/favicon.svg');
|
||||
|
||||
sharp('./src/images/logo.svg').toFile(folder + 'logo.png', (err, info) => {
|
||||
sharp('./src/images/social.jpg').toFile(folder + 'social.jpg', (err, info) => {
|
||||
if (err) return console.error(err);
|
||||
console.log(info);
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "/lexiconga/",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"background_color": "#e6cfaa",
|
||||
|
|
50
offline.html
50
offline.html
|
@ -12,8 +12,8 @@
|
|||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Lexiconga (OFFLINE)">
|
||||
<meta property="og:description" content="The quick and easy (offline) dictionary builder for constructed languages.">
|
||||
<meta property="og:image" content="processedImages/logo.png">
|
||||
|
||||
<meta property="og:image" content="processedImages/social.jpg">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image:alt" content="Lexiconga logo">
|
||||
|
||||
|
@ -26,13 +26,13 @@
|
|||
<meta name="apple-mobile-web-app-title" content="Lexiconga">
|
||||
<link rel="apple-touch-icon" href="processedImages/icon-152.png">
|
||||
|
||||
<link rel="stylesheet" href="src/main.scss" />
|
||||
<link rel="stylesheet" href="src/main.scss">
|
||||
<script>window.isOffline = true;</script>
|
||||
<script src="src/index.js"></script>
|
||||
</head>
|
||||
<body id="defaultTheme">
|
||||
<header id="top">
|
||||
<a href="/" title="Lexiconga"><svg id="title" alt="Lexiconga Logo"viewBox="0 0 249.78 55.087">
|
||||
<a href="/" title="Lexiconga"><svg id="title" alt="Lexiconga Logo" viewBox="0 0 249.78 55.087">
|
||||
<g transform="translate(-107.53 -155.84)">
|
||||
<g id="lexi">
|
||||
<path d="m144.03 159.39-11.339 22.409h-21.62l11.339-22.409z" />
|
||||
|
@ -81,6 +81,9 @@
|
|||
<label>Exact Words
|
||||
<input type="checkbox" id="searchExactWords">
|
||||
</label>
|
||||
<label>Translations
|
||||
<input type="checkbox" id="searchOrthography">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split">
|
||||
|
@ -136,6 +139,17 @@
|
|||
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="wordDetails" placeholder="Markdown formatting allowed"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
<a id="expandAdvancedForm" class="small button expand-advanced-form">Show Advanced Fields</a>
|
||||
</label>
|
||||
<div id="advancedForm" class="advanced-word-form" style="display:none;">
|
||||
<label>Etymology / Root Words<br>
|
||||
<input id="wordEtymology" maxlength="2500" placeholder="comma,separated,root,words">
|
||||
</label>
|
||||
<label>Related Words<br>
|
||||
<input id="wordRelated" maxlength="2500" placeholder="comma,separated,related,words">
|
||||
</label>
|
||||
</div>
|
||||
<div id="wordErrorMessage"></div>
|
||||
<a class="button" id="addWordButton">Add Word</a>
|
||||
</form>
|
||||
|
@ -174,7 +188,6 @@
|
|||
<footer id="bottom">
|
||||
<a href="https://liberapay.com/robbieantenesse" target="_blank" rel="noopener" class="small button">Support Lexiconga</a>
|
||||
<a href="https://blog.lexicon.ga" target="_blank" rel="noopener" class="small button">Blog</a>
|
||||
<a href="./advertising.html" target="_blank" class="small button">Advertise</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/issues" target="_blank" rel="noopener" class="small button">Issues</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/releases" target="_blank" rel="noopener" class="small button">Updates</a>
|
||||
<span class="separator">|</span>
|
||||
|
@ -241,9 +254,11 @@
|
|||
<section id="editDescriptionTab">
|
||||
<label>Name<br>
|
||||
<input id="editName" maxlength="50">
|
||||
<small>Won't update if left blank.</small>
|
||||
</label>
|
||||
<label>Specification<br>
|
||||
<input id="editSpecification" maxlength="50">
|
||||
<small>Won't update if left blank.</small>
|
||||
</label>
|
||||
<label>Description<a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editDescription"></textarea>
|
||||
|
@ -254,8 +269,12 @@
|
|||
<label>Parts of Speech <small>(Comma Separated List)</small><br>
|
||||
<input id="editPartsOfSpeech" maxlength="2500" placeholder="Noun,Adjective,Verb">
|
||||
</label>
|
||||
<label>Alphabetical Order <small>(Comma Separated List. Include every letter!)</small><br>
|
||||
<input id="editAlphabeticalOrder" disabled value="English Alphabet">
|
||||
<label>Alphabetical Order <small>(Space Separated List)</small><br>
|
||||
<input id="editAlphabeticalOrder" placeholder="a A b B c C d D ...">
|
||||
<a class="label-help-button" onclick="alert('Include every letter and case! Any letters used in your words that are not specified will be sorted in the default order below your alphabetically custom-sorted words.\n\nLexiconga can only sort by single characters and will sort by the words AS ENTERED, not using orthographic translation.')">
|
||||
Field Info
|
||||
</a>
|
||||
<small>Leave blank for default (case-insensitive ASCII/Unicode sorting)</small>
|
||||
</label>
|
||||
<h3>Phonology</h3>
|
||||
<div class="split three">
|
||||
|
@ -284,6 +303,9 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label>Notes <small>(Markdown-enabled)</small><br>
|
||||
<textarea id="editPhonologyNotes"></textarea>
|
||||
</label>
|
||||
<h3>Phonotactics</h3>
|
||||
<div class="split three">
|
||||
<div>
|
||||
|
@ -305,10 +327,17 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label>Exceptions <small>(Markdown-enabled)</small><br>
|
||||
<textarea id="editExceptions"></textarea>
|
||||
<label>Notes <small>(Markdown-enabled)</small><br>
|
||||
<textarea id="editPhonotacticsNotes"></textarea>
|
||||
</label>
|
||||
<h3>Orthography</h3>
|
||||
<label>Translations <small>(One translation per line)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editTranslations" placeholder="ai=I
|
||||
AA=ay
|
||||
ou=ow"></textarea>
|
||||
<small>Use format: <code>sequence=replacement</code></small><br>
|
||||
<small>Translations occur in the order specified here, so try to avoid double translations!</small>
|
||||
</label>
|
||||
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editOrthography"></textarea>
|
||||
</label>
|
||||
|
@ -345,6 +374,9 @@
|
|||
<option value="grape">Grape</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Custom Styling <small>(CSS Only)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editCustomCSS" placeholder=".orthographic-translation {font-family: serif;}"></textarea>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section id="editActionsTab" style="display:none;">
|
||||
|
|
31
package.json
31
package.json
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"name": "lexiconga-lite",
|
||||
"version": "1.0.0",
|
||||
"description": "A light-as-possible rewrite of Lexiconga",
|
||||
"name": "lexiconga",
|
||||
"version": "2.2.1",
|
||||
"description": "The quick and easy dictionary builder for constructed languages.",
|
||||
"main": "template-index.html",
|
||||
"repository": "https://cybre.tech/Alamantus/lexiconga-lite.git",
|
||||
"repository": "https://github.com/Alamantus/Lexiconga.git",
|
||||
"author": "Robbie Antenesse <dev@alamantus.com>",
|
||||
"license": "UNLICENCED",
|
||||
"scripts": {
|
||||
"start": "npm run process-images && concurrently \"npm run watch-js\" \"npm run watch-php\" \"npm run copy-files\"",
|
||||
"dev": "npm run process-images && concurrently \"npm run watch-js\" \"npm run watch-php\" \"npm run copy-files\"",
|
||||
"watch-js": "parcel watch template-index.html offline.html template-view.html template-passwordreset.html --no-hmr --public-url /lexiconga/",
|
||||
"watch-php": "cpx \"src/php/**/{*,.*}\" dist -v -w",
|
||||
"bundle": "npm run process-images && npm run bundle-js && npm run copy-files && npm run copy-php",
|
||||
"bundle-js": "parcel build template-index.html offline.html template-view.html template-passwordreset.html",
|
||||
"build": "npm run process-images && npm run bundle-js && npm run copy-files && npm run copy-php",
|
||||
"bundle-js": "parcel build template-index.html offline.html template-view.html template-passwordreset.html --no-source-maps",
|
||||
"copy-files": "cpx \"node_modules/upup/dist/*.min.js\" dist -v",
|
||||
"copy-php": "cpx \"src/php/**/{*,.*}\" dist",
|
||||
"process-images": "node dev/resize-images.js",
|
||||
|
@ -21,18 +21,19 @@
|
|||
"clear-cache": "rimraf .cache/{*,.*}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.5.1",
|
||||
"concurrently": "^4.1.0",
|
||||
"autoprefixer": "9.8.6",
|
||||
"concurrently": "^6.4.0",
|
||||
"cpx": "^1.5.0",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"rimraf": "^2.6.3",
|
||||
"sass": "^1.19.0",
|
||||
"sharp": "^0.22.1"
|
||||
"parcel-bundler": "^1.12.5",
|
||||
"parcel-plugin-goodie-bag": "^2.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.32.6",
|
||||
"sharp": "^0.29.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"marked": "^0.6.2",
|
||||
"marked": "^3.0.8",
|
||||
"normalize.css": "^8.0.1",
|
||||
"papaparse": "^4.6.3",
|
||||
"papaparse": "^5.3.0",
|
||||
"upup": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,32 @@
|
|||
import { getTimestampInSeconds } from "./helpers";
|
||||
|
||||
export const MIGRATE_VERSION = '2.0.0';
|
||||
export const MIGRATE_VERSION = '2.1.0';
|
||||
export const DEFAULT_DICTIONARY = {
|
||||
name: 'New',
|
||||
specification: 'Dictionary',
|
||||
description: 'A new dictionary.',
|
||||
partsOfSpeech: ['Noun', 'Adjective', 'Verb'],
|
||||
partsOfSpeech: ['Noun', 'Adjective', 'Verb', 'Adverb', 'Preposition', 'Pronoun', 'Conjunction'],
|
||||
alphabeticalOrder: [],
|
||||
details: {
|
||||
phonology: {
|
||||
consonants: [],
|
||||
vowels: [],
|
||||
blends: [],
|
||||
phonotactics: {
|
||||
onset: [],
|
||||
nucleus: [],
|
||||
coda: [],
|
||||
exceptions: '',
|
||||
},
|
||||
notes: '',
|
||||
},
|
||||
phonotactics: {
|
||||
onset: [],
|
||||
nucleus: [],
|
||||
coda: [],
|
||||
notes: '',
|
||||
},
|
||||
orthography: {
|
||||
translations: [],
|
||||
notes: '',
|
||||
},
|
||||
grammar: {
|
||||
notes: '',
|
||||
},
|
||||
// custom: [
|
||||
// // {
|
||||
// // name: 'Example Tab',
|
||||
// // content: `This is an _example_ tab to show how **tabs** work with [Markdown](${ MARKDOWN_LINK })!`,
|
||||
// // }
|
||||
// ],
|
||||
},
|
||||
words: [
|
||||
/* {
|
||||
|
@ -39,6 +35,9 @@ export const DEFAULT_DICTIONARY = {
|
|||
partOfSpeech: '',
|
||||
definition: '',
|
||||
details: '',
|
||||
etymology: [],
|
||||
related: [],
|
||||
principalParts: [],
|
||||
wordId: 0
|
||||
}, */
|
||||
],
|
||||
|
@ -47,6 +46,7 @@ export const DEFAULT_DICTIONARY = {
|
|||
caseSensitive: false,
|
||||
sortByDefinition: false,
|
||||
theme: 'default',
|
||||
customCSS: '',
|
||||
isPublic: false,
|
||||
},
|
||||
lastUpdated: getTimestampInSeconds(),
|
||||
|
@ -57,11 +57,11 @@ export const DEFAULT_DICTIONARY = {
|
|||
export const DEFAULT_SETTINGS = {
|
||||
useIPAPronunciationField: true,
|
||||
useHotkeys: true,
|
||||
showAdvanced: false,
|
||||
defaultTheme: 'default',
|
||||
templates: [],
|
||||
};
|
||||
|
||||
export const DISPLAY_AD_EVERY = 10;
|
||||
|
||||
export const DEFAULT_PAGE_SIZE = 50;
|
||||
|
||||
export const LOCAL_STORAGE_KEY = 'dictionary';
|
||||
|
|
|
@ -69,15 +69,6 @@ export function removeTags(html) {
|
|||
return html;
|
||||
}
|
||||
|
||||
export function shuffle(array) {
|
||||
// Fisher-Yates shuffle
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
export function slugify(string) {
|
||||
return removeDiacritics(string).replace(/[^a-zA-Z0-9-_]/g, '-');
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
|
@ -4,7 +4,6 @@ import { renderAll } from './js/render';
|
|||
import { hasToken, addMessage } from './js/utilities';
|
||||
import { loadDictionary } from './js/dictionaryManagement';
|
||||
import { loadSettings } from './js/settings';
|
||||
import { setupAds } from './js/ads';
|
||||
|
||||
function initialize() {
|
||||
if (window.isOffline) {
|
||||
|
@ -23,7 +22,6 @@ function initialize() {
|
|||
});
|
||||
}
|
||||
|
||||
setupAds();
|
||||
renderAll();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<p>
|
||||
<em>Hover over characters to see their Phondue shortcuts.</em>
|
||||
<em>Hover over characters to see their Phondue shortcuts. Use Field Help to show full Phondue instructions.</em>
|
||||
</p>
|
||||
<table className='table is-bordered'>
|
||||
<thead>
|
||||
|
@ -583,6 +583,9 @@
|
|||
<th id='cell_9_9' class='td-lbl' colspan="7">
|
||||
Diacritics
|
||||
</th>
|
||||
<th id='cell_9_10' class='td-lbl' colspan="1">
|
||||
Tones
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -647,35 +650,42 @@
|
|||
<td id='cell_10_13' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_10_14' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='O_ o_ _O _o'>
|
||||
̥
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_15' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='U_ u_ _U _u'>
|
||||
̬
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_16' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title=':_ _:'>
|
||||
̤
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_17' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='~_ _~'>
|
||||
̰
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_18' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='_|'>
|
||||
̩
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_19' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='^_ _^'>
|
||||
̯
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_20' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_10_21' class='td-btn'>
|
||||
<button title='5|'>
|
||||
˥
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id='cell_11_0' class='td-lbl' colspan="1">
|
||||
|
@ -729,35 +739,42 @@
|
|||
<td id='cell_11_13' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_11_14' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='[_ _['>
|
||||
̪
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_11_15' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='{_ _{'>
|
||||
̼
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_11_16' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title=']_ _]'>
|
||||
̺
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_11_17' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='#_ _# []'>
|
||||
̻
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_11_18' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='h^ ^h'>
|
||||
ʰ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_11_19' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='>^ ^>'>
|
||||
̚
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_20' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_10_21' class='td-btn'>
|
||||
<button title='4|'>
|
||||
˦
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id='cell_12_0' class='td-lbl' colspan="1">
|
||||
|
@ -820,35 +837,42 @@
|
|||
<td id='cell_12_13' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_12_14' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='+_ _+'>
|
||||
̟
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_12_15' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='__'>
|
||||
̠
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_12_16' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title=':^ ^:'>
|
||||
̈
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_12_17' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='x^ ^x'>
|
||||
̽
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_12_18' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title="'-_ -'_">
|
||||
̝
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_12_19' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title=',-_ -,_'>
|
||||
̞
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_20' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_10_21' class='td-btn'>
|
||||
<button title='3|'>
|
||||
˧
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id='cell_13_0' class='td-lbl' colspan="1">
|
||||
|
@ -877,7 +901,7 @@
|
|||
</button>
|
||||
</td>
|
||||
<td id='cell_13_9' class='td-btn'>
|
||||
<button title='")'>
|
||||
<button title="')">
|
||||
ʼ
|
||||
</button>
|
||||
</td>
|
||||
|
@ -896,40 +920,45 @@
|
|||
<td id='cell_13_13' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_13_14' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='w^ ^w'>
|
||||
ʷ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_13_15' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='j^ ^j'>
|
||||
ʲ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_13_16' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='h/^'>
|
||||
ᶣ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_13_17' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='vO^ v0^'>
|
||||
ᶹ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_13_18' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='XO^'>
|
||||
ˠ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_13_19' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='?/^'>
|
||||
ˤ
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_13_20' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='~^ ^~'>
|
||||
̴
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_21' class='td-btn'>
|
||||
<button title='2|'>
|
||||
˨
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id='cell_14_0' class='td-lbl' colspan="1">
|
||||
|
@ -992,35 +1021,42 @@
|
|||
<td id='cell_14_13' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_14_14' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title=')_ _)'>
|
||||
̹
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_14_15' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='(_ _('>
|
||||
̜
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_14_16' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='<|_ |<_'>
|
||||
̘
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_14_17' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='>|_ |>_'>
|
||||
̙
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_14_18' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='~^ ^~'>
|
||||
̃
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_14_19' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='>r r>'>
|
||||
˞
|
||||
</button>
|
||||
</td>
|
||||
<td id='cell_10_20' class='td-lbl'>
|
||||
</td>
|
||||
<td id='cell_10_21' class='td-btn'>
|
||||
<button title='1|'>
|
||||
˩
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id='cell_15_0' class='td-lbl' colspan="1">
|
||||
|
@ -1111,7 +1147,7 @@
|
|||
Suprasegmental
|
||||
</th>
|
||||
<td id='cell_16_12' class='td-btn'>
|
||||
<button title='""'>
|
||||
<button title="''">
|
||||
ˈ
|
||||
</button>
|
||||
</td>
|
||||
|
@ -1131,7 +1167,7 @@
|
|||
</button>
|
||||
</td>
|
||||
<td id='cell_16_16' class='td-btn'>
|
||||
<button title=''>
|
||||
<button title='u^ ^u'>
|
||||
̆
|
||||
</button>
|
||||
</td>
|
||||
|
|
|
@ -18,6 +18,7 @@ export function createNewDictionary() {
|
|||
export function changeDictionary(dictionary) {
|
||||
dictionary = typeof dictionary.target !== 'undefined' ? dictionary.target.value : dictionary;
|
||||
if (dictionary !== window.currentDictionary.externalID) {
|
||||
addMessage('Loading Dictionary...');
|
||||
request({
|
||||
action: 'change-dictionary',
|
||||
dictionary,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { setupLoginModal, setupChangeDictionary, setupCreateNewDictionary, setupDeletedDictionaryChangeModal, setupMakePublic } from "./setupListeners";
|
||||
import { getPublicLink } from "./utilities";
|
||||
import { request } from "./helpers";
|
||||
|
||||
export function renderLoginForm() {
|
||||
|
@ -24,7 +25,6 @@ export function renderLoginForm() {
|
|||
<div>
|
||||
<h2>Create a New Account</h2>
|
||||
<p>Creating an account allows you to save and switch between as many dictionaries as you need and access them from any device for free! If you have a dictionary you've been working on loaded already, it will automatically be uploaded to your account when you log in for the first time.</p>
|
||||
<p>Plus if you allow us to send you emails, we'll make sure that you're the first to hear about any new features that get added or if any of our policies change for any reason. We'll never spam you or sell your information.</p>
|
||||
<p>By creating an account, you are indicating that you agree to the Terms of Service and that you understand Lexiconga's Privacy Policy.</p>
|
||||
<label>Email<br>
|
||||
<input type="email" id="createNewEmail" maxlength="100">
|
||||
|
@ -39,8 +39,10 @@ export function renderLoginForm() {
|
|||
<input type="text" id="createNewPublicName" maxlength="50">
|
||||
</label>
|
||||
<label>Allow Emails
|
||||
<input type="checkbox" id="createNewAllowEmails">
|
||||
<input type="checkbox" id="createNewAllowEmails">
|
||||
</label>
|
||||
<p><small>If you allow emails, you'll be informed if any of our policies change for any reason. We'll <em>never</em> spam you or sell your information.</small></p>
|
||||
<p><strong>Remember:</strong> Creating an account is <em>not</em> required to use Lexiconga's core features. Click "Help" in the site footer to learn what accounts provide.</p>
|
||||
<section id="createAccountErrorMessages"></section>
|
||||
<button id="createAccountSubmit" class="button">Create Account</button>
|
||||
</div>
|
||||
|
@ -56,20 +58,24 @@ export function renderLoginForm() {
|
|||
export function renderMakePublic() {
|
||||
const editSettingsTab = document.getElementById('editSettingsTab');
|
||||
const { isPublic } = window.currentDictionary.settings;
|
||||
const { externalID } = window.currentDictionary;
|
||||
const editSettingsTabHTML = `<label>Make Public
|
||||
<input type="checkbox" id="editIsPublic"${isPublic ? ' checked' : ''}><br>
|
||||
<small>Checking this box will make this public via a link you can share with others.</small>
|
||||
</label>
|
||||
<p id="publicLinkDisplay" style="${!isPublic ? 'display:none;': ''}margin-left:20px;">
|
||||
<strong>Public Link:</strong><br>
|
||||
<input readonly id="publicLink" value="${document.domain + window.location.pathname + (externalID ? externalID.toString() : '')}">
|
||||
<a class="small button" id="publicLinkCopy">Copy</a>
|
||||
</p>
|
||||
`;
|
||||
editSettingsTab.innerHTML += editSettingsTabHTML;
|
||||
|
||||
setupMakePublic();
|
||||
let waitForSync = setInterval(() => {
|
||||
if (window.currentDictionary.hasOwnProperty('externalID') && !isNaN(window.currentDictionary.externalID)) {
|
||||
clearInterval(waitForSync);
|
||||
const editSettingsTabHTML = `<label>Make Public
|
||||
<input type="checkbox" id="editIsPublic"${isPublic ? ' checked' : ''}><br>
|
||||
<small>Checking this box will make this public via a link you can share with others.</small>
|
||||
</label>
|
||||
<p id="publicLinkDisplay" style="${!isPublic ? 'display:none;': ''}margin-left:20px;">
|
||||
<strong>Public Link:</strong><br>
|
||||
<input readonly id="publicLink" value="${getPublicLink()}">
|
||||
<a class="small button" id="publicLinkCopy">Copy</a>
|
||||
</p>
|
||||
`;
|
||||
editSettingsTab.innerHTML += editSettingsTabHTML;
|
||||
|
||||
setupMakePublic();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
export function renderAccountSettings() {
|
||||
|
@ -88,18 +94,22 @@ export function renderAccountActions() {
|
|||
const accountActionsHTML = `<h3>Account Actions</h3>
|
||||
<label>Change Dictionary<br><select id="accountSettingsChangeDictionary"></select></label>
|
||||
<p><a class="button" id="accountSettingsCreateNewDictionary">Create New Dictionary</a></p>
|
||||
<h4>Request Your Data</h4>
|
||||
<p>
|
||||
Per your <a href="https://www.eugdpr.org/" target="_blank">GDPR</a> rights in Articles 13–15 and 20, we allow you to request any and all data we have stored about you. The only data we have about you personally is your email address and your Public Name, if you decided to set one. All other data (your Dictionary data) is visible and accessible via the Export button under your Dictionary's Settings. Send an email to help@lexicon.ga to request your information.
|
||||
</p>
|
||||
<details>
|
||||
<summary><h4>Request Your Data</h4></summary>
|
||||
<p>
|
||||
Per your <a href="https://www.eugdpr.org/" target="_blank">GDPR</a> rights in Articles 13–15 and 20, we allow you to request any and all data we have stored about you. The only data we have about you personally is your email address and your Public Name, if you decided to set one. All other data (your Dictionary data) is visible and accessible via the Export button under your Dictionary's Settings. Send an email to help@lexicon.ga to request your information.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<h4>Delete Your Account</h4>
|
||||
<p>
|
||||
Per your <a href="https://www.eugdpr.org/" target="_blank">GDPR</a> rights in Articles 17, if you wish for your account to be deleted, please contact us at help@lexicon.ga, and we will delete your account and all associated dictionaries and words as quickly as possible. Note that you can delete dictionaries yourself via your Dictionary's Settings.
|
||||
</p>
|
||||
<p>
|
||||
Anything that is deleted from our system is permanently and irretrievably removed from our system and cannot be restored, though search engines or internet archives may retain a cached version of your content (there is nothing we can do about this, and you will need to seek out removal of that information by directly contacting the services that are caching your data).
|
||||
</p>
|
||||
<details>
|
||||
<summary><h4>Delete Your Account</h4></summary>
|
||||
<p>
|
||||
Per your <a href="https://www.eugdpr.org/" target="_blank">GDPR</a> rights in Articles 17, if you wish for your account to be deleted, please contact us at help@lexicon.ga, and we will delete your account and all associated dictionaries and words as quickly as possible. Note that you can delete dictionaries yourself via your Dictionary's Settings.
|
||||
</p>
|
||||
<p>
|
||||
Anything that is deleted from our system is permanently and irretrievably removed from our system and cannot be restored, though search engines or internet archives may retain a cached version of your content (there is nothing we can do about this, and you will need to seek out removal of that information by directly contacting the services that are caching your data).
|
||||
</p>
|
||||
</details>
|
||||
`;
|
||||
accountActionsColumn.innerHTML = accountActionsHTML;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { setCookie } from "../StackOverflow/cookie";
|
|||
import { changeDictionary, createNewDictionary } from "./dictionaryManagement";
|
||||
import { addMessage } from "../utilities";
|
||||
import { renderForgotPasswordForm } from "./passwordReset";
|
||||
import { setupMaximizeButtons } from "../setupListeners/buttons";
|
||||
|
||||
export function setupLoginModal(modal) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
|
||||
|
@ -73,4 +74,5 @@ export function setupMakePublic() {
|
|||
document.execCommand('copy');
|
||||
addMessage('Copied public link to clipboard', 3000);
|
||||
});
|
||||
setupMaximizeButtons();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { addMessage } from "../utilities";
|
||||
import { saveDictionary, clearDictionary } from "../dictionaryManagement";
|
||||
import { request } from "./helpers";
|
||||
import { saveToken, dictionaryIsDefault } from "./utilities";
|
||||
import { saveToken, dictionaryIsDefault, getPublicLink } from "./utilities";
|
||||
import { renderAll } from "../render";
|
||||
import { sortWords } from "../wordManagement";
|
||||
import { getLocalDeletedWords, clearLocalDeletedWords, saveDeletedWordsLocally } from "./utilities";
|
||||
|
@ -37,8 +37,14 @@ export function performSync(remoteDictionary) {
|
|||
syncWords(remoteDictionary.words, remoteDictionary.deletedWords).then(success => {
|
||||
if (success) {
|
||||
renderAll();
|
||||
|
||||
document.getElementById('accountSettingsChangeDictionary').value = window.currentDictionary.externalID;
|
||||
document.getElementById('publicLinkDisplay').style.display = window.currentDictionary.settings.isPublic ? '' : 'none';
|
||||
if (document.getElementById('publicLink')) {
|
||||
document.getElementById('publicLink').value = getPublicLink();
|
||||
}
|
||||
if (document.getElementById('publicLinkDisplay')) {
|
||||
document.getElementById('publicLinkDisplay').style.display = window.currentDictionary.settings.isPublic ? '' : 'none';
|
||||
}
|
||||
} else {
|
||||
console.error('word sync failed');
|
||||
}
|
||||
|
@ -74,7 +80,9 @@ export function uploadWholeDictionary(asNew = false) {
|
|||
dictionary,
|
||||
}, remoteId => {
|
||||
window.currentDictionary.externalID = remoteId;
|
||||
document.getElementById('publicLink').value = document.domain + window.location.pathname + remoteId.toString();
|
||||
if (document.getElementById('publicLink')) {
|
||||
document.getElementById('publicLink').value = getPublicLink();
|
||||
}
|
||||
saveDictionary(false);
|
||||
addMessage('Dictionary Uploaded Successfully');
|
||||
renderChangeDictionaryOptions();
|
||||
|
@ -166,10 +174,11 @@ export function syncWords(remoteWords, deletedWords) {
|
|||
}
|
||||
|
||||
remoteWords.forEach(remoteWord => {
|
||||
let localWord = words.find(word => word.wordId === remoteWord.wordId);
|
||||
const localWordIndex = words.findIndex(word => word.wordId === remoteWord.wordId);
|
||||
const localWord = words[localWordIndex];
|
||||
if (localWord) {
|
||||
if (localWord.lastUpdated < remoteWord.lastUpdated) {
|
||||
localWord = remoteWord;
|
||||
words[localWordIndex] = remoteWord;
|
||||
} else if (localWord.lastUpdated > remoteWord.lastUpdated) {
|
||||
// Add more-recently-updated words to upload
|
||||
localWordsToUpload.push(localWord);
|
||||
|
|
|
@ -21,6 +21,22 @@ export function dictionaryIsDefault() {
|
|||
return JSON.stringify(defaultDictionary) === JSON.stringify(currentDictionary);
|
||||
}
|
||||
|
||||
export function getPublicLink() {
|
||||
const { externalID } = window.currentDictionary;
|
||||
let path;
|
||||
if (externalID) {
|
||||
path = window.location.pathname.match(new RegExp(externalID + '$'))
|
||||
? window.location.pathname
|
||||
: (window.location.pathname.indexOf(externalID) > -1
|
||||
? window.location.pathname.substring(0, window.location.pathname.indexOf(externalID)) + externalID
|
||||
: window.location.pathname + externalID
|
||||
);
|
||||
} else {
|
||||
path = '';
|
||||
}
|
||||
return 'https://' + document.domain + path;
|
||||
}
|
||||
|
||||
export function saveDeletedWordsLocally(wordIds) {
|
||||
let storedDeletedWords = getLocalDeletedWords();
|
||||
wordIds.forEach(wordId => {
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { DISPLAY_AD_EVERY } from '../constants.js';
|
||||
import ads from '../../ads.json';
|
||||
import { shuffle } from '../helpers.js';
|
||||
|
||||
export function setupAds() {
|
||||
const priority = shuffle(ads.filter(ad => isActive(ad) && ad.isPriority));
|
||||
const regular = shuffle(ads.filter(ad => isActive(ad) && !ad.isPriority));
|
||||
window.ads = [...priority, ...regular];
|
||||
}
|
||||
|
||||
function isActive(ad) {
|
||||
const date = new Date();
|
||||
return date >= new Date(ad.start) && date < new Date(ad.end);
|
||||
}
|
||||
|
||||
export function renderAd(wordPosition) {
|
||||
if (window.hasOwnProperty('ads') && window.ads) {
|
||||
if (wordPosition % DISPLAY_AD_EVERY === 3) {
|
||||
const adIndex = Math.floor(wordPosition / DISPLAY_AD_EVERY) % window.ads.length;
|
||||
const ad = window.ads[adIndex];
|
||||
return `<article class="entry">
|
||||
<header>
|
||||
<h4 class="word">${ad.header}</h4>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">${ad.body}</dt>
|
||||
<dd class="details">
|
||||
<a href="${ad.link}" target="_blank" class="button">${ad.cta}</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</article>`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
|
@ -1,34 +1,39 @@
|
|||
import papa from 'papaparse';
|
||||
import { renderDictionaryDetails, renderPartsOfSpeech, renderAll, renderTheme } from "./render";
|
||||
import { renderDictionaryDetails, renderPartsOfSpeech } from "./render/details";
|
||||
import { renderAll, renderTheme, renderCustomCSS } from "./render";
|
||||
import { removeTags, cloneObject, getTimestampInSeconds, download, slugify } from "../helpers";
|
||||
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY, MIGRATE_VERSION } from "../constants";
|
||||
import { addMessage, getNextId, hasToken } from "./utilities";
|
||||
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY } from "../constants";
|
||||
import { addMessage, getNextId, hasToken, objectValuesAreDifferent } from "./utilities";
|
||||
import { addWord, sortWords } from "./wordManagement";
|
||||
import { migrateDictionary } from './migration';
|
||||
|
||||
export function updateDictionary () {
|
||||
|
||||
renderDictionaryDetails();
|
||||
}
|
||||
|
||||
export function openEditModal() {
|
||||
const { name, specification, description, partsOfSpeech } = window.currentDictionary;
|
||||
const { consonants, vowels, blends, phonotactics } = window.currentDictionary.details.phonology;
|
||||
const { orthography, grammar } = window.currentDictionary.details;
|
||||
const { allowDuplicates, caseSensitive, sortByDefinition, theme, isPublic } = window.currentDictionary.settings;
|
||||
const { name, specification, description, partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
|
||||
const { phonology, phonotactics, orthography, grammar } = window.currentDictionary.details;
|
||||
const { consonants, vowels, blends } = phonology;
|
||||
const { allowDuplicates, caseSensitive, sortByDefinition, theme, customCSS, isPublic } = window.currentDictionary.settings;
|
||||
|
||||
document.getElementById('editName').value = name;
|
||||
document.getElementById('editSpecification').value = specification;
|
||||
document.getElementById('editDescription').value = description;
|
||||
document.getElementById('editPartsOfSpeech').value = partsOfSpeech.join(',');
|
||||
document.getElementById('editAlphabeticalOrder').value = alphabeticalOrder.join(' ');
|
||||
|
||||
document.getElementById('editConsonants').value = consonants.join(' ');
|
||||
document.getElementById('editVowels').value = vowels.join(' ');
|
||||
document.getElementById('editBlends').value = blends.join(' ');
|
||||
document.getElementById('editPhonologyNotes').value = phonology.notes;
|
||||
|
||||
document.getElementById('editOnset').value = phonotactics.onset.join(',');
|
||||
document.getElementById('editNucleus').value = phonotactics.nucleus.join(',');
|
||||
document.getElementById('editCoda').value = phonotactics.coda.join(',');
|
||||
document.getElementById('editExceptions').value = phonotactics.exceptions;
|
||||
document.getElementById('editPhonotacticsNotes').value = phonotactics.notes;
|
||||
|
||||
document.getElementById('editTranslations').value = orthography.translations.join('\n');
|
||||
document.getElementById('editOrthography').value = orthography.notes;
|
||||
document.getElementById('editGrammar').value = grammar.notes;
|
||||
|
||||
|
@ -37,59 +42,76 @@ export function openEditModal() {
|
|||
if (allowDuplicates) document.getElementById('editCaseSensitive').disabled = true;
|
||||
document.getElementById('editSortByDefinition').checked = sortByDefinition;
|
||||
document.getElementById('editTheme').value = theme;
|
||||
document.getElementById('editCustomCSS').value = customCSS;
|
||||
if (hasToken()) {
|
||||
document.getElementById('editIsPublic').checked = isPublic;
|
||||
}
|
||||
|
||||
document.getElementById('editModal').style.display = '';
|
||||
Array.from(document.querySelectorAll('#editModal .modal-content section')).forEach(section => section.scrollTop = 0);
|
||||
}
|
||||
|
||||
export function saveEditModal() {
|
||||
window.currentDictionary.name = removeTags(document.getElementById('editName').value.trim());
|
||||
window.currentDictionary.specification = removeTags(document.getElementById('editSpecification').value.trim());
|
||||
window.currentDictionary.description = removeTags(document.getElementById('editDescription').value.trim());
|
||||
window.currentDictionary.partsOfSpeech = document.getElementById('editPartsOfSpeech').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
const updatedDictionary = cloneObject(window.currentDictionary);
|
||||
delete updatedDictionary.words;
|
||||
updatedDictionary.name = removeTags(document.getElementById('editName').value.trim());
|
||||
if (updatedDictionary.name.length < 1) {
|
||||
updatedDictionary.name = window.currentDictionary.name;
|
||||
}
|
||||
updatedDictionary.specification = removeTags(document.getElementById('editSpecification').value.trim());
|
||||
if (updatedDictionary.specification.length < 1) {
|
||||
updatedDictionary.specification = window.currentDictionary.specification;
|
||||
}
|
||||
updatedDictionary.description = removeTags(document.getElementById('editDescription').value.trim());
|
||||
updatedDictionary.partsOfSpeech = document.getElementById('editPartsOfSpeech').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.alphabeticalOrder = document.getElementById('editAlphabeticalOrder').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
|
||||
window.currentDictionary.details.phonology.consonants = document.getElementById('editConsonants').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
window.currentDictionary.details.phonology.vowels = document.getElementById('editVowels').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
window.currentDictionary.details.phonology.blends = document.getElementById('editBlends').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
window.currentDictionary.details.phonology.phonotactics.onset = document.getElementById('editOnset').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
window.currentDictionary.details.phonology.phonotactics.nucleus = document.getElementById('editNucleus').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
window.currentDictionary.details.phonology.phonotactics.coda = document.getElementById('editCoda').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
window.currentDictionary.details.phonology.phonotactics.exceptions = removeTags(document.getElementById('editExceptions').value.trim());
|
||||
updatedDictionary.details.phonology.consonants = document.getElementById('editConsonants').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.phonology.vowels = document.getElementById('editVowels').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.phonology.blends = document.getElementById('editBlends').value.split(' ').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.phonology.notes = removeTags(document.getElementById('editPhonologyNotes').value.trim());
|
||||
|
||||
window.currentDictionary.details.orthography.notes = removeTags(document.getElementById('editOrthography').value.trim());
|
||||
window.currentDictionary.details.grammar.notes = removeTags(document.getElementById('editGrammar').value.trim());
|
||||
updatedDictionary.details.phonotactics.onset = document.getElementById('editOnset').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.phonotactics.nucleus = document.getElementById('editNucleus').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.phonotactics.coda = document.getElementById('editCoda').value.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.phonotactics.notes = removeTags(document.getElementById('editPhonotacticsNotes').value.trim());
|
||||
|
||||
window.currentDictionary.settings.allowDuplicates = !document.getElementById('editPreventDuplicates').checked;
|
||||
window.currentDictionary.settings.caseSensitive = document.getElementById('editCaseSensitive').checked;
|
||||
const needsReSort = window.currentDictionary.settings.sortByDefinition !== document.getElementById('editSortByDefinition').checked;
|
||||
window.currentDictionary.settings.sortByDefinition = document.getElementById('editSortByDefinition').checked;
|
||||
window.currentDictionary.settings.theme = document.getElementById('editTheme').value;
|
||||
updatedDictionary.details.orthography.translations = document.getElementById('editTranslations').value.split('\n').map(val => val.trim()).filter(val => val !== '');
|
||||
updatedDictionary.details.orthography.notes = removeTags(document.getElementById('editOrthography').value.trim());
|
||||
updatedDictionary.details.grammar.notes = removeTags(document.getElementById('editGrammar').value.trim());
|
||||
|
||||
updatedDictionary.settings.allowDuplicates = !document.getElementById('editPreventDuplicates').checked;
|
||||
updatedDictionary.settings.caseSensitive = document.getElementById('editCaseSensitive').checked;
|
||||
updatedDictionary.settings.sortByDefinition = document.getElementById('editSortByDefinition').checked;
|
||||
updatedDictionary.settings.theme = document.getElementById('editTheme').value;
|
||||
updatedDictionary.settings.customCSS = removeTags(document.getElementById('editCustomCSS').value.trim());
|
||||
|
||||
let needsWordRender = false;
|
||||
if (hasToken()) {
|
||||
needsWordRender = window.currentDictionary.settings.isPublic !== document.getElementById('editIsPublic').checked;
|
||||
window.currentDictionary.settings.isPublic = document.getElementById('editIsPublic').checked;
|
||||
updatedDictionary.settings.isPublic = document.getElementById('editIsPublic').checked;
|
||||
} else {
|
||||
window.currentDictionary.settings.isPublic = false;
|
||||
updatedDictionary.settings.isPublic = false;
|
||||
}
|
||||
|
||||
addMessage('Saved ' + window.currentDictionary.specification + ' Successfully');
|
||||
saveDictionary();
|
||||
renderTheme();
|
||||
renderDictionaryDetails();
|
||||
renderPartsOfSpeech();
|
||||
|
||||
if (needsReSort || needsWordRender) {
|
||||
if (objectValuesAreDifferent(updatedDictionary, window.currentDictionary)) {
|
||||
window.currentDictionary = Object.assign(window.currentDictionary, updatedDictionary);
|
||||
|
||||
renderTheme();
|
||||
renderCustomCSS();
|
||||
renderDictionaryDetails();
|
||||
renderPartsOfSpeech();
|
||||
sortWords(true);
|
||||
}
|
||||
|
||||
if (hasToken()) {
|
||||
import('./account/index.js').then(account => {
|
||||
account.uploadDetailsDirect();
|
||||
account.updateChangeDictionaryOption();
|
||||
})
|
||||
addMessage('Saved ' + window.currentDictionary.specification + ' Successfully');
|
||||
saveDictionary();
|
||||
|
||||
if (hasToken()) {
|
||||
import('./account/index.js').then(account => {
|
||||
account.uploadDetailsDirect();
|
||||
account.updateChangeDictionaryOption();
|
||||
})
|
||||
}
|
||||
} else {
|
||||
addMessage('No changes made to Dictionary Settings.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,21 +237,63 @@ export function importWords() {
|
|||
console.error('Error Importing Word: ', err)
|
||||
});
|
||||
} else {
|
||||
const row = results.data[0];
|
||||
const importedWord = addWord({
|
||||
const row = results.data;
|
||||
const wordToImport = {
|
||||
name: removeTags(row.word).trim(),
|
||||
pronunciation: removeTags(row.pronunciation).trim(),
|
||||
partOfSpeech: removeTags(row['part of speech']).trim(),
|
||||
definition: removeTags(row.definition).trim(),
|
||||
details: removeTags(row.explanation).trim(),
|
||||
wordId: getNextId(),
|
||||
}, false, false, false);
|
||||
};
|
||||
if (typeof row['etymology'] !== 'undefined') {
|
||||
const etymology = removeTags(row['etymology']).trim().split(',').filter(etymology => etymology.trim() !== '');
|
||||
if (etymology.length > 0) {
|
||||
wordToImport.etymology = etymology;
|
||||
}
|
||||
}
|
||||
if (typeof row['etymology (comma-separated)'] !== 'undefined') {
|
||||
const etymology = removeTags(row['etymology (comma-separated)']).trim().split(',').filter(etymology => etymology.trim() !== '');
|
||||
if (etymology.length > 0) {
|
||||
wordToImport.etymology = etymology;
|
||||
}
|
||||
}
|
||||
if (typeof row['related words'] !== 'undefined') {
|
||||
const related = removeTags(row['related words']).trim().split(',').filter(related => related.trim() !== '');
|
||||
if (related.length > 0) {
|
||||
wordToImport.related = related;
|
||||
}
|
||||
}
|
||||
if (typeof row['related words (comma-separated)'] !== 'undefined') {
|
||||
const related = removeTags(row['related words (comma-separated)']).trim().split(',').filter(related => related.trim() !== '');
|
||||
if (related.length > 0) {
|
||||
wordToImport.related = related;
|
||||
}
|
||||
}
|
||||
if (typeof row['principal parts'] !== 'undefined') {
|
||||
const principalParts = removeTags(row['principal parts']).trim().split(',').filter(principalParts => principalParts.trim() !== '');
|
||||
if (principalParts.length > 0) {
|
||||
wordToImport.principalParts = principalParts;
|
||||
}
|
||||
}
|
||||
if (typeof row['principal parts (comma-separated)'] !== 'undefined') {
|
||||
const principalParts = removeTags(row['principal parts (comma-separated)']).trim().split(',').filter(principalParts => principalParts.trim() !== '');
|
||||
if (principalParts.length > 0) {
|
||||
wordToImport.principalParts = principalParts;
|
||||
}
|
||||
}
|
||||
const importedWord = addWord(wordToImport, false);
|
||||
|
||||
importedWords.push(importedWord);
|
||||
|
||||
// Sort and save every 500 words, just in case something goes wrong on large imports
|
||||
if (importedWords.length % 500 == 499) {
|
||||
sortWords(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
saveDictionary(false);
|
||||
sortWords(false);
|
||||
renderAll();
|
||||
importWordsField.value = '';
|
||||
document.getElementById('editModal').style.display = 'none';
|
||||
|
@ -279,45 +343,12 @@ export function exportWords() {
|
|||
'part of speech': word.partOfSpeech,
|
||||
definition: word.definition,
|
||||
explanation: word.details,
|
||||
'etymology (comma-separated)': typeof word.etymology !== 'undefined' ? word.etymology.join(',') : '',
|
||||
'related words (comma-separated)': typeof word.related !== 'undefined' ? word.related.join(',') : '',
|
||||
'principal parts (comma-separated)': typeof word.principalParts !== 'undefined' ? word.principalParts.join(',') : '',
|
||||
}
|
||||
});
|
||||
const csv = papa.unparse(words, { quotes: true });
|
||||
download(csv, fileName, 'text/csv;charset=utf-8');
|
||||
}, 1);
|
||||
}
|
||||
|
||||
export function migrateDictionary() {
|
||||
let migrated = false;
|
||||
if (!window.currentDictionary.hasOwnProperty('version')) {
|
||||
const fixStupidOldNonsense = string => string.replace(/"/g, '"').replace(/'/g, "'").replace(/\/g, '\\').replace(/<br>/g, '\n');
|
||||
window.currentDictionary.description = fixStupidOldNonsense(window.currentDictionary.description);
|
||||
const timestamp = getTimestampInSeconds();
|
||||
window.currentDictionary.words = window.currentDictionary.words.map(word => {
|
||||
word.definition = word.simpleDefinition;
|
||||
delete word.simpleDefinition;
|
||||
word.details = fixStupidOldNonsense(word.longDefinition);
|
||||
delete word.longDefinition;
|
||||
word.lastUpdated = timestamp;
|
||||
word.createdOn = timestamp;
|
||||
return word;
|
||||
});
|
||||
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);
|
||||
window.currentDictionary.partsOfSpeech = window.currentDictionary.settings.partsOfSpeech.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
delete window.currentDictionary.settings.partsOfSpeech;
|
||||
delete window.currentDictionary.nextWordId;
|
||||
window.currentDictionary.settings.sortByDefinition = window.currentDictionary.settings.sortByEquivalent;
|
||||
delete window.currentDictionary.settings.sortByEquivalent;
|
||||
window.currentDictionary.settings.theme = 'default';
|
||||
delete window.currentDictionary.settings.isComplete;
|
||||
|
||||
migrated = true;
|
||||
} else if (window.currentDictionary.version !== MIGRATE_VERSION) {
|
||||
switch (window.currentDictionary.version) {
|
||||
default: console.error('Unknown version'); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (migrated) {
|
||||
saveDictionary();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { renderDescription, renderDetails, renderStats } from './render';
|
||||
import { renderDescription, renderDetails, renderStats } from './render/details';
|
||||
|
||||
export function showSection(sectionName) {
|
||||
switch (sectionName) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { confirmEditWord, submitWordForm } from "./wordManagement";
|
||||
import { showSection, hideDetailsPanel } from "./displayToggles";
|
||||
import { renderInfoModal, renderMaximizedTextbox } from "./render";
|
||||
import { renderInfoModal, renderMaximizedTextbox } from "./render/modals";
|
||||
import { showSearchModal, clearSearchText } from "./search";
|
||||
import { saveAndCloseSettingsModal, openSettingsModal, saveSettings } from "./settings";
|
||||
import { saveAndCloseEditModal, openEditModal } from "./dictionaryManagement";
|
||||
|
@ -56,7 +56,8 @@ export function hotKeyActions(event) {
|
|||
break;
|
||||
}
|
||||
case 'S': if (event.ctrlKey) {event.preventDefault(); hideAllModals(); openSettingsModal();} break;
|
||||
case 'x': if (event.ctrlKey) {event.preventDefault(); clearSearchText();} break;
|
||||
case 'Delete':
|
||||
case 'Backspace': if (event.ctrlKey) {event.preventDefault(); clearSearchText();} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { LOCAL_STORAGE_KEY } from "../constants";
|
||||
import { LOCAL_STORAGE_KEY, DEFAULT_DICTIONARY, MIGRATE_VERSION } from "../constants";
|
||||
import { saveDictionary } from "./dictionaryManagement";
|
||||
import { getTimestampInSeconds } from "../helpers";
|
||||
|
||||
export default function migrate() {
|
||||
if (window.location.pathname === '/') {
|
||||
|
@ -74,4 +76,53 @@ function checkForReceived() {
|
|||
delete window.dictionaryImportedFromHTTP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function migrateDictionary() {
|
||||
let migrated = false;
|
||||
if (!window.currentDictionary.hasOwnProperty('version')) {
|
||||
const fixStupidOldNonsense = string => string.replace(/"/g, '"').replace(/'/g, "'").replace(/\/g, '\\').replace(/<br>/g, '\n');
|
||||
window.currentDictionary.description = fixStupidOldNonsense(window.currentDictionary.description);
|
||||
const timestamp = getTimestampInSeconds();
|
||||
window.currentDictionary.words = window.currentDictionary.words.map(word => {
|
||||
word.definition = word.simpleDefinition;
|
||||
delete word.simpleDefinition;
|
||||
word.details = fixStupidOldNonsense(word.longDefinition);
|
||||
delete word.longDefinition;
|
||||
word.lastUpdated = timestamp;
|
||||
word.createdOn = timestamp;
|
||||
return word;
|
||||
});
|
||||
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);
|
||||
window.currentDictionary.partsOfSpeech = window.currentDictionary.settings.partsOfSpeech.split(',').map(val => val.trim()).filter(val => val !== '');
|
||||
delete window.currentDictionary.settings.partsOfSpeech;
|
||||
delete window.currentDictionary.nextWordId;
|
||||
window.currentDictionary.settings.sortByDefinition = window.currentDictionary.settings.sortByEquivalent;
|
||||
delete window.currentDictionary.settings.sortByEquivalent;
|
||||
window.currentDictionary.settings.theme = 'default';
|
||||
delete window.currentDictionary.settings.isComplete;
|
||||
|
||||
migrated = true;
|
||||
} else if (window.currentDictionary.version !== MIGRATE_VERSION) {
|
||||
switch (window.currentDictionary.version) {
|
||||
default: console.error('Unknown version'); break;
|
||||
case '2.0.0': {
|
||||
window.currentDictionary.details.phonology.notes = '';
|
||||
window.currentDictionary.details.phonotactics = Object.assign({}, window.currentDictionary.details.phonology.phonotactics);
|
||||
delete window.currentDictionary.details.phonology.phonotactics;
|
||||
window.currentDictionary.details.phonotactics.notes = window.currentDictionary.details.phonotactics.exceptions;
|
||||
delete window.currentDictionary.details.phonotactics.exceptions;
|
||||
window.currentDictionary.details.orthography.translations = [];
|
||||
window.currentDictionary.settings.customCSS = '';
|
||||
window.currentDictionary = Object.assign({}, DEFAULT_DICTIONARY, window.currentDictionary);
|
||||
window.currentDictionary.version = MIGRATE_VERSION;
|
||||
migrated = true;
|
||||
// break; By skipping the break, all migrations can happen in sequence.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (migrated) {
|
||||
saveDictionary();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { DEFAULT_PAGE_SIZE } from '../constants';
|
||||
import { renderWords } from "./render";
|
||||
import { renderWords } from "./render/words";
|
||||
|
||||
export function getPaginationData(words) {
|
||||
const numWords = words.length;
|
||||
|
|
368
src/js/render.js
368
src/js/render.js
|
@ -1,368 +0,0 @@
|
|||
import md from 'marked';
|
||||
import { removeTags, slugify } from '../helpers';
|
||||
import { getWordsStats, getHomonymnNumber, hasToken } from './utilities';
|
||||
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from './search';
|
||||
import { showSection } from './displayToggles';
|
||||
import {
|
||||
setupSearchFilters,
|
||||
setupWordOptionButtons,
|
||||
setupPagination,
|
||||
setupWordOptionSelections,
|
||||
setupWordEditFormButtons,
|
||||
setupMaximizeModal,
|
||||
setupInfoModal,
|
||||
setupIPATable,
|
||||
setupIPAFields
|
||||
} from './setupListeners';
|
||||
import { getPaginationData } from './pagination';
|
||||
import { getOpenEditForms, parseReferences } from './wordManagement';
|
||||
import { renderAd } from './ads';
|
||||
import ipaTableFile from './KeyboardFire/phondue/ipa-table.html';
|
||||
|
||||
export function renderAll() {
|
||||
renderTheme();
|
||||
renderDictionaryDetails();
|
||||
renderPartsOfSpeech();
|
||||
renderWords();
|
||||
}
|
||||
|
||||
export function renderTheme() {
|
||||
const { theme } = window.currentDictionary.settings;
|
||||
document.body.id = theme + 'Theme';
|
||||
}
|
||||
|
||||
export function renderDictionaryDetails() {
|
||||
renderName();
|
||||
|
||||
const tabs = document.querySelectorAll('#detailsSection nav li');
|
||||
const shownTab = Array.from(tabs).find(tab => tab.classList.contains('active'));
|
||||
if (shownTab) {
|
||||
const tabName = shownTab.innerText.toLowerCase();
|
||||
showSection(tabName);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderName() {
|
||||
const dictionaryName = removeTags(window.currentDictionary.name) + ' ' + removeTags(window.currentDictionary.specification);
|
||||
const name = document.getElementById('dictionaryName');
|
||||
name.innerHTML = dictionaryName;
|
||||
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
|
||||
const shareLinkElement = document.getElementById('dictionaryShare');
|
||||
|
||||
if (isPublic && !shareLinkElement) {
|
||||
const shareLink = document.createElement('a');
|
||||
shareLink.id = 'dictionaryShare';
|
||||
shareLink.classList.add('button');
|
||||
shareLink.style.float = 'right';
|
||||
shareLink.href = window.location.pathname.match(new RegExp(window.currentDictionary.externalID + '$')) ? window.location.pathname
|
||||
: window.location.pathname.substring(0, window.location.pathname.indexOf(window.currentDictionary.externalID)) + window.currentDictionary.externalID;
|
||||
shareLink.target = '_blank';
|
||||
shareLink.title = 'Public Link to Dictionary';
|
||||
shareLink.innerHTML = '➦';
|
||||
name.parentElement.insertBefore(shareLink, name);
|
||||
} else if (!isPublic && shareLinkElement) {
|
||||
shareLinkElement.parentElement.removeChild(shareLinkElement);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderDescription() {
|
||||
const descriptionHTML = md(removeTags(window.currentDictionary.description));
|
||||
|
||||
document.getElementById('detailsPanel').innerHTML = '<div class="content">' + descriptionHTML + '</div>';
|
||||
}
|
||||
|
||||
export function renderDetails() {
|
||||
const { partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
|
||||
const { phonology, orthography, grammar } = window.currentDictionary.details;
|
||||
const partsOfSpeechHTML = `<p><strong>Parts of Speech:</strong> ${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</p>`;
|
||||
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order:</strong> ${
|
||||
(alphabeticalOrder.length > 0 ? alphabeticalOrder : ['English Alphabet']).map(letter => `<span class="tag">${letter}</span>`).join(' ')
|
||||
}</p>`;
|
||||
const generalHTML = `<h3>General</h3>${partsOfSpeechHTML}${alphabeticalOrderHTML}`;
|
||||
|
||||
const { consonants, vowels, blends, phonotactics } = phonology
|
||||
const consonantHTML = `<p><strong>Consonants:</strong> ${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const vowelHTML = `<p><strong>Vowels:</strong> ${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs / Blends:</strong> ${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
|
||||
const phonologyHTML = `<h3>Phonology</h3>
|
||||
<div class="split two">
|
||||
<div>${consonantHTML}</div>
|
||||
<div>${vowelHTML}</div>
|
||||
</div>
|
||||
${blendHTML}`;
|
||||
|
||||
const { onset, nucleus, coda, exceptions } = phonotactics;
|
||||
const onsetHTML = `<p><strong>Onset:</strong> ${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const nucleusHTML = `<p><strong>Nucleus:</strong> ${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const codaHTML = `<p><strong>Coda:</strong> ${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const exceptionsHTML = exceptions.trim().length > 0 ? '<p><strong>Exceptions:</strong></p><div>' + md(removeTags(exceptions)) + '</div>' : '';
|
||||
const phonotacticsHTML = `<h3>Phonotactics</h3>
|
||||
<div class="split three">
|
||||
<div>${onsetHTML}</div>
|
||||
<div>${nucleusHTML}</div>
|
||||
<div>${codaHTML}</div>
|
||||
</div>
|
||||
${exceptionsHTML}`;
|
||||
|
||||
const orthographyHTML = '<h3>Orthography</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(orthography.notes)) + '</div>';
|
||||
const grammarHTML = '<h3>Grammar</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(grammar.notes)) + '</div>';
|
||||
|
||||
detailsPanel.innerHTML = generalHTML + phonologyHTML + phonotacticsHTML + orthographyHTML + grammarHTML;
|
||||
}
|
||||
|
||||
export function renderStats() {
|
||||
const wordStats = getWordsStats();
|
||||
const numberOfWordsHTML = `<p><strong>Number of Words</strong><br>${wordStats.numberOfWords.map(stat => `<span><span class="tag">${stat.name}</span><span class="tag">${stat.value}</span></span>`).join(' ')}</p>`;
|
||||
const wordLengthHTML = `<p><strong>Word Length</strong><br><span><span class="tag">Shortest</span><span class="tag">${wordStats.wordLength.shortest}</span></span>
|
||||
<span><span class="tag">Longest</span><span class="tag">${wordStats.wordLength.longest}</span></span>
|
||||
<span><span class="tag">Average</span><span class="tag">${wordStats.wordLength.average}</span></span></p>`;
|
||||
const letterDistributionHTML = `<p><strong>Letter Distribution</strong><br>${wordStats.letterDistribution.map(stat => `<span title="${stat.number} ${stat.letter}'s total"><span class="tag">${stat.letter}</span><span class="tag">${stat.percentage.toFixed(2)}</span></span>`).join(' ')}</p>`;
|
||||
const totalLettersHTML = `<p><strong>${wordStats.totalLetters} Total Letters</strong></p>`;
|
||||
|
||||
detailsPanel.innerHTML = numberOfWordsHTML + wordLengthHTML + letterDistributionHTML + totalLettersHTML;
|
||||
}
|
||||
|
||||
export function renderPartsOfSpeech(onlyOptions = false) {
|
||||
let optionsHTML = '<option value=""></option>',
|
||||
searchHTML = '<label>Unclassified <input type="checkbox" checked id="searchPartOfSpeech__None"></label>';
|
||||
window.currentDictionary.partsOfSpeech.forEach(partOfSpeech => {
|
||||
partOfSpeech = removeTags(partOfSpeech);
|
||||
optionsHTML += `<option value="${partOfSpeech}">${partOfSpeech}</option>`;
|
||||
searchHTML += `<label>${partOfSpeech} <input type="checkbox" checked id="searchPartOfSpeech_${slugify(partOfSpeech)}"></label>`;
|
||||
});
|
||||
searchHTML += `<a class="small button" id="checkAllFilters">Check All</a> <a class="small button" id="uncheckAllFilters">Uncheck All</a>`;
|
||||
|
||||
Array.from(document.getElementsByClassName('part-of-speech-select')).forEach(select => {
|
||||
const selectedValue = select.value;
|
||||
select.innerHTML = optionsHTML;
|
||||
select.value = selectedValue;
|
||||
});
|
||||
if (!onlyOptions) {
|
||||
document.getElementById('searchPartsOfSpeech').innerHTML = searchHTML;
|
||||
}
|
||||
|
||||
setupSearchFilters();
|
||||
}
|
||||
|
||||
export function renderWords() {
|
||||
let wordsHTML = '';
|
||||
let openEditForms = getOpenEditForms();
|
||||
let words = false;
|
||||
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
|
||||
|
||||
if (window.currentDictionary.words.length === 0) {
|
||||
wordsHTML = `<article class="entry">
|
||||
<header>
|
||||
<h4 class="word">No Words Created</h4>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">Use the Word Form to create words or click the Help button below!</dt>
|
||||
</dl>
|
||||
</article>`;
|
||||
} else {
|
||||
words = getMatchingSearchWords();
|
||||
|
||||
if (words.length === 0) {
|
||||
wordsHTML = `<article class="entry">
|
||||
<header>
|
||||
<h4 class="word">No Search Results</h4>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">Edit your search or filter to show words.</dt>
|
||||
</dl>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
if (openEditForms.length > 0) {
|
||||
// Clone the dom nodes
|
||||
openEditForms.forEach((wordFormId, index) => {
|
||||
openEditForms[index] = document.getElementById(wordFormId.toString()).cloneNode(true);
|
||||
});
|
||||
}
|
||||
|
||||
// const { pageStart, pageEnd } = getPaginationData(words);
|
||||
|
||||
// words.slice(pageStart, pageEnd).forEach(originalWord => {
|
||||
words.forEach((originalWord, displayIndex) => {
|
||||
const word = highlightSearchTerm({
|
||||
name: removeTags(originalWord.name),
|
||||
pronunciation: removeTags(originalWord.pronunciation),
|
||||
partOfSpeech: removeTags(originalWord.partOfSpeech),
|
||||
definition: removeTags(originalWord.definition),
|
||||
details: parseReferences(removeTags(originalWord.details)),
|
||||
wordId: originalWord.wordId,
|
||||
});
|
||||
const homonymnNumber = getHomonymnNumber(originalWord);
|
||||
const shareLink = window.currentDictionary.hasOwnProperty('externalID')
|
||||
? window.location.pathname + window.currentDictionary.externalID + '/' + word.wordId : '';
|
||||
|
||||
wordsHTML += renderAd(displayIndex);
|
||||
|
||||
wordsHTML += `<article class="entry" id="${word.wordId}">
|
||||
<header>
|
||||
<h4 class="word">${word.name}${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
|
||||
<span class="pronunciation">${word.pronunciation}</span>
|
||||
<span class="part-of-speech">${word.partOfSpeech}</span>
|
||||
${isPublic ? `<a class="small button share-link" href="${shareLink}" target="_blank" title="Public Link to Word">➦</a>` : ''}
|
||||
<span class="small button word-option-button">Options</span>
|
||||
<div class="word-option-list" style="display:none;">
|
||||
<div class="word-option" id="edit_${word.wordId}">Edit</div>
|
||||
<div class="word-option" id="delete_${word.wordId}">Delete</div>
|
||||
</div>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">${word.definition}</dt>
|
||||
<dd class="details">
|
||||
${md(word.details)}
|
||||
</dd>
|
||||
</dl>
|
||||
</article>`;
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('entries').innerHTML = wordsHTML;
|
||||
|
||||
if (openEditForms.length > 0) {
|
||||
// Clone the dom nodes
|
||||
openEditForms.forEach(editForm => {
|
||||
const entryElement = document.getElementById(editForm.id);
|
||||
entryElement.parentNode.replaceChild(editForm, entryElement);
|
||||
});
|
||||
setupWordEditFormButtons();
|
||||
}
|
||||
|
||||
setupWordOptionButtons();
|
||||
setupWordOptionSelections();
|
||||
|
||||
// Show Search Results
|
||||
const searchTerm = getSearchTerm();
|
||||
const filters = getSearchFilters();
|
||||
let resultsText = searchTerm !== '' || !filters.allPartsOfSpeechChecked ? (words ? words.length : 0).toString() + ' Results' : '';
|
||||
resultsText += !filters.allPartsOfSpeechChecked ? ' (Filtered)' : '';
|
||||
document.getElementById('searchResults').innerHTML = resultsText;
|
||||
|
||||
// renderPagination(words);
|
||||
}
|
||||
|
||||
export function renderPagination(filteredWords) {
|
||||
const paginationData = getPaginationData(filteredWords);
|
||||
|
||||
if (paginationData.pages > 0) {
|
||||
let paginationHTML = (paginationData.currentPage > 0 ? '<span class="button prev-button">« Previous</span>' : '')
|
||||
+ '<select class="page-selector">';
|
||||
for (let i = 0; i < paginationData.pages; i++) {
|
||||
paginationHTML += `<option value="${i}"${paginationData.currentPage === i ? ' selected' : ''}>Page ${i + 1}</option>`;
|
||||
}
|
||||
paginationHTML += '</select>'
|
||||
+ (paginationData.currentPage < paginationData.pages - 1 ? '<span class="button next-button">Next »</span>' : '');
|
||||
|
||||
Array.from(document.getElementsByClassName('pagination')).forEach(pagination => {
|
||||
pagination.innerHTML = paginationHTML;
|
||||
});
|
||||
|
||||
setupPagination();
|
||||
}
|
||||
}
|
||||
|
||||
export function renderEditForm(wordId = false) {
|
||||
wordId = typeof wordId.target === 'undefined' ? wordId : parseInt(this.id.replace('edit_', ''));
|
||||
const word = window.currentDictionary.words.find(w => w.wordId === wordId);
|
||||
if (word) {
|
||||
const ipaPronunciationField = `<input id="wordPronunciation_${wordId}" class="ipa-field" maxlength="200" value="${word.pronunciation}"><br>
|
||||
<a class="label-help-button ipa-field-help-button">Field Help</a>`;
|
||||
const plainPronunciationField = `<input id="wordPronunciation_${wordId}" maxlength="200" value="${word.pronunciation}">`;
|
||||
const editForm = `<form id="editForm_${wordId}" class="edit-form">
|
||||
<label>Word<span class="red">*</span><br>
|
||||
<input id="wordName_${wordId}" maxlength="200" value="${word.name}">
|
||||
</label>
|
||||
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
|
||||
${window.settings.useIPAPronunciationField ? ipaPronunciationField : plainPronunciationField}
|
||||
</label>
|
||||
<label>Part of Speech<br>
|
||||
<select id="wordPartOfSpeech_${wordId}" class="part-of-speech-select">
|
||||
<option value="${word.partOfSpeech}" selected>${word.partOfSpeech}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Definition<span class="red">*</span><br>
|
||||
<input id="wordDefinition_${wordId}" maxlength="2500" value="${word.definition}" placeholder="Equivalent words">
|
||||
</label>
|
||||
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="wordDetails_${wordId}" placeholder="Markdown formatting allowed">${word.details}</textarea>
|
||||
</label>
|
||||
<div id="wordErrorMessage_${wordId}"></div>
|
||||
<a class="button edit-save-changes" id="editWordButton_${wordId}">Save Changes</a>
|
||||
<a class="button edit-cancel">Cancel Edit</a>
|
||||
</form>`;
|
||||
|
||||
document.getElementById(wordId.toString()).innerHTML = editForm;
|
||||
setupWordEditFormButtons();
|
||||
renderPartsOfSpeech(true);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderIPAHelp() {
|
||||
import('./KeyboardFire/phondue/usage.html').then(html => {
|
||||
renderInfoModal(html);
|
||||
});
|
||||
}
|
||||
|
||||
export function renderIPATable(ipaTableButton) {
|
||||
ipaTableButton = typeof ipaTableButton.target === 'undefined' || ipaTableButton.target === '' ? ipaTableButton : ipaTableButton.target;
|
||||
const label = ipaTableButton.parentElement.innerText.replace(/(Field Help|IPA Chart)/g, '').trim();
|
||||
const textBox = ipaTableButton.parentElement.querySelector('input');
|
||||
const modalElement = document.createElement('section');
|
||||
modalElement.classList.add('modal', 'ipa-table-modal');
|
||||
modalElement.innerHTML = `<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<a class="close-button">×︎</a>
|
||||
<header><label>${label} <input value="${textBox.value}" class="ipa-field"></label></header>
|
||||
<section>
|
||||
${ipaTableFile}
|
||||
</section>
|
||||
<footer><a class="button done-button">Done</a></footer>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
|
||||
setupIPAFields();
|
||||
setupIPATable(modalElement, textBox);
|
||||
}
|
||||
|
||||
export function renderMaximizedTextbox(maximizeButton) {
|
||||
maximizeButton = typeof maximizeButton.target === 'undefined' || maximizeButton.target === '' ? maximizeButton : maximizeButton.target;
|
||||
const label = maximizeButton.parentElement.innerText.replace(/(\*|Maximize)/g, '').trim();
|
||||
const textBox = maximizeButton.parentElement.querySelector('textarea');
|
||||
const modalElement = document.createElement('section');
|
||||
modalElement.classList.add('modal', 'maximize-modal');
|
||||
modalElement.innerHTML = `<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<a class="close-button">×︎</a>
|
||||
<header><h3>${label}</h3></header>
|
||||
<section>
|
||||
<textarea>${textBox.value}</textarea>
|
||||
</section>
|
||||
<footer><a class="button done-button">Done</a></footer>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
|
||||
setupMaximizeModal(modalElement, textBox);
|
||||
}
|
||||
|
||||
export function renderInfoModal(content) {
|
||||
const modalElement = document.createElement('section');
|
||||
modalElement.classList.add('modal', 'info-modal');
|
||||
modalElement.innerHTML = `<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<a class="close-button">×︎</a>
|
||||
<section class="info-modal">
|
||||
<div class="content">
|
||||
${content}
|
||||
</div>
|
||||
</section>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
|
||||
setupInfoModal(modalElement);
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
import md from 'marked';
|
||||
import { removeTags, slugify } from '../../helpers';
|
||||
import { getWordsStats, hasToken } from '../utilities';
|
||||
import { showSection } from '../displayToggles';
|
||||
import { setupSearchFilters } from '../setupListeners/search';
|
||||
import { parseReferences } from '../wordManagement';
|
||||
import { getPublicLink } from '../account/utilities';
|
||||
|
||||
export function renderDictionaryDetails() {
|
||||
renderName();
|
||||
|
||||
const tabs = document.querySelectorAll('#detailsSection nav li');
|
||||
const shownTab = Array.from(tabs).find(tab => tab.classList.contains('active'));
|
||||
if (shownTab) {
|
||||
const tabName = shownTab.innerText.toLowerCase();
|
||||
showSection(tabName);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderName() {
|
||||
const dictionaryName = removeTags(window.currentDictionary.name) + ' ' + removeTags(window.currentDictionary.specification);
|
||||
const name = document.getElementById('dictionaryName');
|
||||
name.innerHTML = dictionaryName;
|
||||
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
|
||||
const shareLinkElement = document.getElementById('dictionaryShare');
|
||||
|
||||
if (isPublic && !shareLinkElement) {
|
||||
const shareLink = document.createElement('a');
|
||||
shareLink.id = 'dictionaryShare';
|
||||
shareLink.classList.add('button');
|
||||
shareLink.style.float = 'right';
|
||||
shareLink.href = getPublicLink();
|
||||
shareLink.target = '_blank';
|
||||
shareLink.title = 'Public Link to Dictionary';
|
||||
shareLink.innerHTML = '➦';
|
||||
name.parentElement.insertBefore(shareLink, name);
|
||||
} else if (isPublic && shareLinkElement) {
|
||||
shareLinkElement.href = getPublicLink();
|
||||
} else if (!isPublic && shareLinkElement) {
|
||||
shareLinkElement.parentElement.removeChild(shareLinkElement);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderDescription() {
|
||||
const descriptionHTML = md(parseReferences(removeTags(window.currentDictionary.description)));
|
||||
|
||||
document.getElementById('detailsPanel').innerHTML = '<div class="content">' + descriptionHTML + '</div>';
|
||||
}
|
||||
|
||||
export function renderDetails() {
|
||||
const { partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
|
||||
const { phonology, phonotactics, orthography, grammar } = window.currentDictionary.details;
|
||||
const partsOfSpeechHTML = `<p><strong>Parts of Speech</strong><br>${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</div>`;
|
||||
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order</strong><br>${
|
||||
(alphabeticalOrder.length > 0 ? alphabeticalOrder : ['English Alphabet']).map(letter => `<span class="tag">${letter}</span>`).join(' ')
|
||||
}</div>`;
|
||||
const generalHTML = `<h2>General</h2>${partsOfSpeechHTML}${alphabeticalOrderHTML}`;
|
||||
|
||||
const { consonants, vowels, blends } = phonology
|
||||
const consonantHTML = `<p><strong>Consonants</strong><br>${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const vowelHTML = `<p><strong>Vowels</strong><br>${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs / Blends</strong><br>${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
|
||||
const phonologyNotesHTML = phonology.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(parseReferences(removeTags(phonology.notes))) + '</div>' : '';
|
||||
const phonologyHTML = `<h2>Phonology</h2>
|
||||
<div class="split two">
|
||||
<div>${consonantHTML}</div>
|
||||
<div>${vowelHTML}</div>
|
||||
</div>
|
||||
${blendHTML}
|
||||
${phonologyNotesHTML}`;
|
||||
|
||||
const { onset, nucleus, coda } = phonotactics;
|
||||
const onsetHTML = `<p><strong>Onset</strong><br>${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const nucleusHTML = `<p><strong>Nucleus</strong><br>${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const codaHTML = `<p><strong>Coda</strong><br>${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const phonotacticsNotesHTML = phonotactics.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(parseReferences(removeTags(phonotactics.notes))) + '</div>' : '';
|
||||
const phonotacticsHTML = onset.length + nucleus.length + coda.length + phonotacticsNotesHTML.length > 0
|
||||
? `<h2>Phonotactics</h2>
|
||||
${onset.length > 0 || nucleus.length > 0 || coda.length > 0
|
||||
? `<div class="split three">
|
||||
<div>${onsetHTML}</div>
|
||||
<div>${nucleusHTML}</div>
|
||||
<div>${codaHTML}</div>
|
||||
</div>` : ''}
|
||||
${phonotacticsNotesHTML}`
|
||||
: '';
|
||||
|
||||
const { translations } = orthography;
|
||||
const translationsHTML = translations.length > 0 ? `<p><strong>Translations</strong><br>${translations.map(translation => {
|
||||
translation = translation.split('=').map(value => value.trim());
|
||||
if (translation.length > 1 && translation[0] !== '' && translation[1] !== '') {
|
||||
return `<span><span class="tag">${translation[0]}</span><span class="tag orthographic-translation">${translation[1]}</span></span>`;
|
||||
}
|
||||
return false;
|
||||
}).filter(html => html !== false).join(' ')}</p>` : '';
|
||||
const orthographyNotesHTML = orthography.notes.trim().length > 0 ? '<p><strong>Notes</strong><br>' + md(parseReferences(removeTags(orthography.notes))) + '</div>' : '';
|
||||
const orthographyHTML = translations.length + orthographyNotesHTML.length > 0
|
||||
? `<h2>Orthography</h2>
|
||||
${translationsHTML}
|
||||
${orthographyNotesHTML}`
|
||||
: '';
|
||||
const grammarHTML = grammar.notes.trim().length > 0 ? '<h2>Grammar</h2><div>'
|
||||
+ (grammar.notes.trim().length > 0 ? md(parseReferences(removeTags(grammar.notes))) : '')
|
||||
+ '</div>' : '';
|
||||
|
||||
detailsPanel.innerHTML = generalHTML + phonologyHTML + phonotacticsHTML + orthographyHTML + grammarHTML;
|
||||
}
|
||||
|
||||
export function renderStats() {
|
||||
const wordStats = getWordsStats();
|
||||
const numberOfWordsHTML = `<p><strong>Number of Words</strong><br>${wordStats.numberOfWords.map(stat => `<span><span class="tag">${stat.name}</span><span class="tag">${stat.value}</span></span>`).join(' ')}</p>`;
|
||||
const wordLengthHTML = `<p><strong>Word Length</strong><br><span><span class="tag">Shortest</span><span class="tag">${wordStats.wordLength.shortest}</span></span>
|
||||
<span><span class="tag">Longest</span><span class="tag">${wordStats.wordLength.longest}</span></span>
|
||||
<span><span class="tag">Average</span><span class="tag">${wordStats.wordLength.average}</span></span></p>`;
|
||||
const letterDistributionHTML = `<p><strong>Letter Distribution</strong><br>${wordStats.letterDistribution.map(stat => `<span title="${stat.number} ${stat.letter}'s total"><span class="tag">${stat.letter}</span><span class="tag">${stat.percentage.toFixed(2)}</span></span>`).join(' ')}</p>`;
|
||||
const totalLettersHTML = `<p><strong>${wordStats.totalLetters} Total Letters</strong></p>`;
|
||||
|
||||
detailsPanel.innerHTML = numberOfWordsHTML + wordLengthHTML + letterDistributionHTML + totalLettersHTML;
|
||||
}
|
||||
|
||||
export function renderPartsOfSpeech(onlyOptions = false) {
|
||||
let optionsHTML = '<option value=""></option>',
|
||||
searchHTML = '<label>Unclassified <input type="checkbox" checked id="searchPartOfSpeech__None"></label>';
|
||||
window.currentDictionary.partsOfSpeech.forEach(partOfSpeech => {
|
||||
partOfSpeech = removeTags(partOfSpeech);
|
||||
optionsHTML += `<option value="${partOfSpeech.replace(/"/g, '"')}">${partOfSpeech}</option>`;
|
||||
searchHTML += `<label>${partOfSpeech} <input type="checkbox" checked id="searchPartOfSpeech_${slugify(partOfSpeech)}"></label>`;
|
||||
});
|
||||
searchHTML += `<a class="small button" id="checkAllFilters">Check All</a> <a class="small button" id="uncheckAllFilters">Uncheck All</a>`;
|
||||
|
||||
Array.from(document.getElementsByClassName('part-of-speech-select')).forEach(select => {
|
||||
const selectedValue = select.value;
|
||||
select.innerHTML = optionsHTML;
|
||||
select.value = selectedValue;
|
||||
});
|
||||
if (!onlyOptions) {
|
||||
document.getElementById('searchPartsOfSpeech').innerHTML = searchHTML;
|
||||
}
|
||||
|
||||
setupSearchFilters();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { renderDictionaryDetails, renderPartsOfSpeech } from './details';
|
||||
import { renderWords } from './words';
|
||||
import { renderTemplateSelectOptions } from './settings';
|
||||
|
||||
export function renderAll() {
|
||||
renderTheme();
|
||||
renderCustomCSS();
|
||||
renderDictionaryDetails();
|
||||
renderPartsOfSpeech();
|
||||
renderTemplateSelectOptions();
|
||||
renderWords();
|
||||
}
|
||||
|
||||
export function renderTheme() {
|
||||
const { theme } = window.currentDictionary.settings;
|
||||
document.body.id = theme + 'Theme';
|
||||
}
|
||||
|
||||
export function renderCustomCSS() {
|
||||
const { customCSS } = window.currentDictionary.settings;
|
||||
const stylingId = 'customCSS';
|
||||
const stylingElement = document.getElementById(stylingId);
|
||||
if (!stylingElement) {
|
||||
const styling = document.createElement('style');
|
||||
styling.id = stylingId;
|
||||
styling.innerHTML = customCSS;
|
||||
document.body.appendChild(styling);
|
||||
} else {
|
||||
stylingElement.innerHTML = customCSS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { setupIPAFields } from '../setupListeners';
|
||||
import {
|
||||
setupMaximizeModal,
|
||||
setupInfoModal,
|
||||
setupIPATable,
|
||||
} from '../setupListeners/modals';
|
||||
import ipaTableFile from '../KeyboardFire/phondue/ipa-table.html';
|
||||
|
||||
export function renderIPAHelp() {
|
||||
import('../KeyboardFire/phondue/usage.html').then(html => {
|
||||
renderInfoModal(html);
|
||||
});
|
||||
}
|
||||
|
||||
export function renderIPATable(ipaTableButton) {
|
||||
ipaTableButton = typeof ipaTableButton.target === 'undefined' || ipaTableButton.target === '' ? ipaTableButton : ipaTableButton.target;
|
||||
const label = ipaTableButton.parentElement.innerText.replace(/(Field Help|IPA Chart)/g, '').trim();
|
||||
const textBox = ipaTableButton.parentElement.querySelector('input');
|
||||
const modalElement = document.createElement('section');
|
||||
modalElement.classList.add('modal', 'ipa-table-modal');
|
||||
modalElement.innerHTML = `<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<a class="close-button">×︎</a>
|
||||
<header><label>${label} <input value="${textBox.value}" class="ipa-field"></label></header>
|
||||
<section>
|
||||
${ipaTableFile}
|
||||
</section>
|
||||
<footer><a class="button done-button">Done</a></footer>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
|
||||
setupIPAFields();
|
||||
setupIPATable(modalElement, textBox);
|
||||
}
|
||||
|
||||
export function renderMaximizedTextbox(maximizeButton) {
|
||||
maximizeButton = typeof maximizeButton.target === 'undefined' || maximizeButton.target === '' ? maximizeButton : maximizeButton.target;
|
||||
const label = maximizeButton.parentElement.innerText.replace(/(\*|Maximize)/g, '').trim();
|
||||
const textBox = maximizeButton.parentElement.querySelector('textarea');
|
||||
const modalElement = document.createElement('section');
|
||||
modalElement.classList.add('modal', 'maximize-modal');
|
||||
modalElement.innerHTML = `<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<a class="close-button">×︎</a>
|
||||
<header><h3>${label}</h3></header>
|
||||
<section>
|
||||
<textarea>${textBox.value}</textarea>
|
||||
</section>
|
||||
<footer><a class="button done-button">Done</a></footer>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
|
||||
setupMaximizeModal(modalElement, textBox);
|
||||
}
|
||||
|
||||
export function renderInfoModal(content) {
|
||||
const modalElement = document.createElement('section');
|
||||
modalElement.classList.add('modal', 'info-modal');
|
||||
modalElement.innerHTML = `<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<a class="close-button">×︎</a>
|
||||
<section class="info-modal">
|
||||
<div class="content">
|
||||
${content}
|
||||
</div>
|
||||
</section>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modalElement);
|
||||
|
||||
setupInfoModal(modalElement);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { setupTemplateSelectOptions } from "../setupListeners/settings";
|
||||
|
||||
export function renderTemplateSelectOptions() {
|
||||
const { templates } = window.settings;
|
||||
|
||||
if (typeof templates !== 'undefined') {
|
||||
const templatesOptionsHTML = templates.map((template, index) => {
|
||||
return `<option value="${index.toString()}">${template.name}</options>`;
|
||||
}).join('');
|
||||
|
||||
Array.from(document.getElementsByClassName('template-select')).forEach(select => {
|
||||
if (select.id !== 'savedDetailsTemplates' && templates.length < 1) {
|
||||
return select.parentElement.style.display = 'none';
|
||||
} else {
|
||||
select.parentElement.style.display = '';
|
||||
}
|
||||
select.innerHTML = '<option value="" selected="selected">None Selected</option>' + templatesOptionsHTML;
|
||||
});
|
||||
|
||||
setupTemplateSelectOptions();
|
||||
}
|
||||
}
|
||||
|
||||
export function showTemplateEditor(show = true) {
|
||||
document.getElementById('templateFields').style.display = show ? '' : 'none';
|
||||
if (show) {
|
||||
document.getElementById('templateTextarea').focus();
|
||||
document.querySelector('#settingsModal .modal-content section').scrollTop = 9999;
|
||||
} else {
|
||||
clearTemplateEditor();
|
||||
}
|
||||
}
|
||||
|
||||
export function showSelectedTemplate(template, index) {
|
||||
const nameField = document.getElementById('templateNameField');
|
||||
nameField.value = template.name;
|
||||
nameField.setAttribute('template', index.toString());
|
||||
document.getElementById('templateTextarea').value = template.template;
|
||||
showTemplateEditor(true);
|
||||
}
|
||||
|
||||
export function clearTemplateEditor() {
|
||||
document.getElementById('savedDetailsTemplates').value = '';
|
||||
document.getElementById('templateNameField').value = '';
|
||||
document.getElementById('templateTextarea').value = '';
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
import md from 'marked';
|
||||
import { removeTags } from '../../helpers';
|
||||
import { getHomonymnNumber, hasToken } from '../utilities';
|
||||
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from '../search';
|
||||
import {
|
||||
setupWordOptionButtons,
|
||||
// setupPagination,
|
||||
setupWordOptionSelections,
|
||||
setupWordEditFormButtons,
|
||||
} from '../setupListeners/words';
|
||||
// import { getPaginationData } from '../pagination';
|
||||
import { getOpenEditForms, translateOrthography, parseReferences, getWordReferenceMarkdown } from '../wordManagement';
|
||||
import { getPublicLink } from '../account/utilities';
|
||||
import { renderPartsOfSpeech } from './details';
|
||||
import { renderTemplateSelectOptions } from './settings';
|
||||
|
||||
export function renderWord(savedWord, isPublic) {
|
||||
const word = highlightSearchTerm({
|
||||
name: removeTags(savedWord.name),
|
||||
pronunciation: removeTags(savedWord.pronunciation),
|
||||
partOfSpeech: removeTags(savedWord.partOfSpeech),
|
||||
definition: removeTags(savedWord.definition),
|
||||
details: parseReferences(removeTags(savedWord.details)),
|
||||
etymology: typeof savedWord.etymology === 'undefined' || savedWord.etymology.length < 1 ? null
|
||||
: savedWord.etymology.map(root => getWordReferenceMarkdown(removeTags(root))).join(', '),
|
||||
related: typeof savedWord.related === 'undefined' || savedWord.related.length < 1 ? null
|
||||
: savedWord.related.map(relatedWord => getWordReferenceMarkdown(removeTags(relatedWord))).join(', '),
|
||||
principalParts: typeof savedWord.principalParts === 'undefined' || savedWord.principalParts.length < 1 ? null
|
||||
: savedWord.principalParts.join(', '),
|
||||
wordId: savedWord.wordId,
|
||||
});
|
||||
const homonymnNumber = getHomonymnNumber(savedWord);
|
||||
const shareLink = window.currentDictionary.hasOwnProperty('externalID') ? getPublicLink() + '/' + word.wordId : '';
|
||||
|
||||
let wordNameDisplay = translateOrthography(word.name);
|
||||
|
||||
return `<article class="entry" id="${word.wordId}">
|
||||
<header>
|
||||
<h4 class="word"><span class="orthographic-translation">${wordNameDisplay}</span>${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
|
||||
${word.principalParts === null ? '' : `<span class="principalParts">(${word.principalParts})</span>`}
|
||||
<span class="pronunciation">${word.pronunciation}</span>
|
||||
<span class="part-of-speech">${word.partOfSpeech}</span>
|
||||
${isPublic ? `<a class="small button share-link" href="${shareLink}" target="_blank" title="Public Link to Word">➦</a>` : ''}
|
||||
<span class="small button word-option-button">Options</span>
|
||||
<div class="word-option-list" style="display:none;">
|
||||
<div class="word-option" id="edit_${word.wordId}">Edit</div>
|
||||
<div class="word-option" id="delete_${word.wordId}">Delete</div>
|
||||
</div>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">${word.definition}</dt>
|
||||
<dd class="details">
|
||||
${md(word.details)}
|
||||
</dd>
|
||||
${word.etymology === null && word.related === null ? '' : `<hr>`}
|
||||
${word.etymology === null ? '' : `<dt>Etymology <small>(Root Word${savedWord.etymology.length !== 1 ? 's' : ''})</small></dt>
|
||||
<dd class="etymology">
|
||||
${md(word.etymology).replace(/<\/?p>/g, '')}
|
||||
</dd>`}
|
||||
${word.related === null ? '' : `<dt>Related Word${savedWord.related.length !== 1 ? 's' : ''}</dt>
|
||||
<dd class="related">
|
||||
${md(word.related).replace(/<\/?p>/g, '')}
|
||||
</dd>`}
|
||||
</dl>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
export function renderWords() {
|
||||
let wordsHTML = '';
|
||||
let openEditForms = getOpenEditForms();
|
||||
let words = false;
|
||||
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
|
||||
|
||||
if (window.currentDictionary.words.length === 0) {
|
||||
wordsHTML = `<article class="entry">
|
||||
<header>
|
||||
<h4 class="word">No Words Created</h4>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">Use the Word Form to create words or click the Help button below!</dt>
|
||||
</dl>
|
||||
</article>`;
|
||||
} else {
|
||||
words = getMatchingSearchWords();
|
||||
|
||||
if (words.length === 0) {
|
||||
wordsHTML = `<article class="entry">
|
||||
<header>
|
||||
<h4 class="word">No Search Results</h4>
|
||||
</header>
|
||||
<dl>
|
||||
<dt class="definition">Edit your search or filter to show words.</dt>
|
||||
</dl>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
if (openEditForms.length > 0) {
|
||||
// Clone the dom nodes
|
||||
openEditForms.forEach((wordFormId, index) => {
|
||||
openEditForms[index] = document.getElementById(wordFormId.toString()).cloneNode(true);
|
||||
});
|
||||
}
|
||||
|
||||
// const { pageStart, pageEnd } = getPaginationData(words);
|
||||
|
||||
// words.slice(pageStart, pageEnd).forEach(savedWord => {
|
||||
words.forEach(savedWord => {
|
||||
wordsHTML += renderWord(savedWord, isPublic);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('entries').innerHTML = wordsHTML;
|
||||
|
||||
if (openEditForms.length > 0) {
|
||||
// Clone the dom nodes
|
||||
openEditForms.forEach(editForm => {
|
||||
const entryElement = document.getElementById(editForm.id);
|
||||
entryElement.parentNode.replaceChild(editForm, entryElement);
|
||||
});
|
||||
setupWordEditFormButtons();
|
||||
}
|
||||
|
||||
setupWordOptionButtons();
|
||||
setupWordOptionSelections();
|
||||
|
||||
// Show Search Results
|
||||
const searchTerm = getSearchTerm();
|
||||
const filters = getSearchFilters();
|
||||
let resultsText = searchTerm !== '' || !filters.allPartsOfSpeechChecked ? (words ? words.length : 0).toString() + ' Results' : '';
|
||||
resultsText += !filters.allPartsOfSpeechChecked ? ' (Filtered)' : '';
|
||||
document.getElementById('searchResults').innerHTML = resultsText;
|
||||
|
||||
// renderPagination(words);
|
||||
}
|
||||
|
||||
// export function renderPagination(filteredWords) {
|
||||
// const paginationData = getPaginationData(filteredWords);
|
||||
|
||||
// if (paginationData.pages > 0) {
|
||||
// let paginationHTML = (paginationData.currentPage > 0 ? '<span class="button prev-button">« Previous</span>' : '')
|
||||
// + '<select class="page-selector">';
|
||||
// for (let i = 0; i < paginationData.pages; i++) {
|
||||
// paginationHTML += `<option value="${i}"${paginationData.currentPage === i ? ' selected' : ''}>Page ${i + 1}</option>`;
|
||||
// }
|
||||
// paginationHTML += '</select>'
|
||||
// + (paginationData.currentPage < paginationData.pages - 1 ? '<span class="button next-button">Next »</span>' : '');
|
||||
|
||||
// Array.from(document.getElementsByClassName('pagination')).forEach(pagination => {
|
||||
// pagination.innerHTML = paginationHTML;
|
||||
// });
|
||||
|
||||
// setupPagination();
|
||||
// }
|
||||
// }
|
||||
|
||||
export function renderEditForm(wordId = false) {
|
||||
wordId = typeof wordId.target === 'undefined' ? wordId : parseInt(this.id.replace('edit_', ''));
|
||||
const word = window.currentDictionary.words.find(w => w.wordId === wordId);
|
||||
if (word) {
|
||||
const escapeQuotes = (value) => value.replace(/"/g, '"');
|
||||
const wordHasAdvancedFields = (word.hasOwnProperty('etymology') && word.etymology)
|
||||
|| (word.hasOwnProperty('related') && word.related) || (word.hasOwnProperty('principalParts') && word.principalParts);
|
||||
const ipaPronunciationField = `<input id="wordPronunciation_${wordId}" class="ipa-field" maxlength="200" value="${escapeQuotes(word.pronunciation)}"><br>
|
||||
<a class="label-help-button ipa-field-help-button">Field Help</a>`;
|
||||
const plainPronunciationField = `<input id="wordPronunciation_${wordId}" maxlength="200" value="${escapeQuotes(word.pronunciation)}">`;
|
||||
const editForm = `<form id="editForm_${wordId}" class="edit-form">
|
||||
<label>Word<span class="red">*</span><br>
|
||||
<input id="wordName_${wordId}" maxlength="200" value="${escapeQuotes(word.name)}">
|
||||
</label>
|
||||
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
|
||||
${window.settings.useIPAPronunciationField ? ipaPronunciationField : plainPronunciationField}
|
||||
</label>
|
||||
<label>Part of Speech<br>
|
||||
<select id="wordPartOfSpeech_${wordId}" class="part-of-speech-select">
|
||||
<option value="${escapeQuotes(word.partOfSpeech)}" selected>${word.partOfSpeech}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Definition<span class="red">*</span><br>
|
||||
<input id="wordDefinition_${wordId}" maxlength="2500" value="${escapeQuotes(word.definition)}" placeholder="Equivalent words">
|
||||
</label>
|
||||
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="wordDetails_${wordId}" placeholder="Markdown formatting allowed">${word.details}</textarea>
|
||||
</label>
|
||||
<label>
|
||||
<a id="expandAdvancedForm_${wordId}" class="small button expand-advanced-form">${wordHasAdvancedFields || window.settings.showAdvanced ? 'Hide' : 'Show'} Advanced Fields</a>
|
||||
</label>
|
||||
<div id="advancedForm_${wordId}" class="advanced-word-form" style="display:${wordHasAdvancedFields || window.settings.showAdvanced ? 'block' : 'none'};">
|
||||
<label>Details Field Templates
|
||||
<select id="templateSelect_${wordId}" class="template-select">
|
||||
</select>
|
||||
<small>Choose one to fill the details field. (Note: Will erase anything currently there.)</small>
|
||||
</label>
|
||||
<label>Etymology / Root Words<br>
|
||||
<input id="wordEtymology_${wordId}" maxlength="2500" placeholder="comma,separated,root,words" value="${word.hasOwnProperty('etymology') ? escapeQuotes(word.etymology.toString()) : ''}">
|
||||
</label>
|
||||
<label>Related Words<br>
|
||||
<input id="wordRelated_${wordId}" maxlength="2500" placeholder="comma,separated,related,words" value="${word.hasOwnProperty('related') ? escapeQuotes(word.related.toString()) : ''}">
|
||||
</label>
|
||||
<label>Principal Parts<a href="https://en.wikipedia.org/wiki/Principal_parts" target="_blank" class="label-button">What's This?</a><br>
|
||||
<input id="wordPrincipalParts_${wordId}" maxlength="2500" placeholder="comma,separated,principal,parts" value="${word.hasOwnProperty('principalParts') ? escapeQuotes(word.principalParts.toString()) : ''}">
|
||||
</label>
|
||||
</div>
|
||||
<div id="wordErrorMessage_${wordId}"></div>
|
||||
<a class="button edit-save-changes" id="editWordButton_${wordId}">Save Changes</a>
|
||||
<a class="button edit-cancel">Cancel Edit</a>
|
||||
</form>`;
|
||||
|
||||
document.getElementById(wordId.toString()).innerHTML = editForm;
|
||||
setupWordEditFormButtons();
|
||||
renderPartsOfSpeech(true);
|
||||
renderTemplateSelectOptions();
|
||||
}
|
||||
}
|
109
src/js/search.js
109
src/js/search.js
|
@ -1,6 +1,7 @@
|
|||
import { cloneObject, getIndicesOf } from "../helpers";
|
||||
import removeDiacritics from "./StackOverflow/removeDiacritics";
|
||||
import { renderWords } from "./render";
|
||||
import { renderWords } from "./render/words";
|
||||
import { translateOrthography, parseReferences } from "./wordManagement";
|
||||
|
||||
export function showSearchModal() {
|
||||
document.getElementById('searchModal').style.display = 'block';
|
||||
|
@ -22,6 +23,7 @@ export function getSearchFilters() {
|
|||
caseSensitive: document.getElementById('searchCaseSensitive').checked,
|
||||
ignoreDiacritics: document.getElementById('searchIgnoreDiacritics').checked,
|
||||
exact: document.getElementById('searchExactWords').checked,
|
||||
orthography: document.getElementById('searchOrthography').checked,
|
||||
name: document.getElementById('searchIncludeName').checked,
|
||||
definition: document.getElementById('searchIncludeDefinition').checked,
|
||||
details: document.getElementById('searchIncludeDetails').checked,
|
||||
|
@ -40,39 +42,72 @@ export function getSearchFilters() {
|
|||
return filters;
|
||||
}
|
||||
|
||||
function wordMatchesPartsOfSpeechFilter(word, filters) {
|
||||
if (!filters.allPartsOfSpeechChecked) {
|
||||
const partOfSpeech = word.partOfSpeech === '' ? 'Unclassified' : word.partOfSpeech;
|
||||
return filters.partsOfSpeech.hasOwnProperty(partOfSpeech) && filters.partsOfSpeech[partOfSpeech];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function wordMatchesSearchTermAndOptions(word, searchTerm, filters) {
|
||||
if (searchTerm === '') return true; // If searchTerm is blank, don't process word.
|
||||
|
||||
searchTerm = filters.ignoreDiacritics ? removeDiacritics(searchTerm) : searchTerm;
|
||||
searchTerm = filters.caseSensitive ? searchTerm : searchTerm.toLowerCase();
|
||||
let name = filters.orthography ? translateOrthography(word.name) : word.name;
|
||||
name = filters.ignoreDiacritics ? removeDiacritics(name) : name;
|
||||
name = filters.caseSensitive ? name : name.toLowerCase();
|
||||
let definition = filters.ignoreDiacritics ? removeDiacritics(word.definition) : word.definition;
|
||||
definition = filters.caseSensitive ? definition : definition.toLowerCase();
|
||||
let details = filters.orthography ? parseReferences(word.details) : word.details;
|
||||
details = filters.ignoreDiacritics ? removeDiacritics(details) : details;
|
||||
details = filters.caseSensitive ? details : details.toLowerCase();
|
||||
let principalParts = typeof word.principalParts === 'undefined' ? [] : word.principalParts;
|
||||
principalParts = filters.orthography ? principalParts.map(part => translateOrthography(part)) : principalParts;
|
||||
principalParts = filters.ignoreDiacritics ? principalParts.map(part => removeDiacritics(part)) : principalParts;
|
||||
principalParts = filters.caseSensitive ? principalParts : principalParts.map(part => part.toLowerCase());
|
||||
|
||||
const isInName = filters.name && (filters.exact
|
||||
? searchTerm == name
|
||||
: new RegExp(searchTerm, 'g').test(name)
|
||||
);
|
||||
const isInDefinition = filters.definition && (filters.exact
|
||||
? searchTerm == definition
|
||||
: new RegExp(searchTerm, 'g').test(definition)
|
||||
);
|
||||
const isInDetails = filters.details && new RegExp(searchTerm, 'g').test(details);
|
||||
const isInPrincipalParts = filters.name && (filters.exact
|
||||
? principalParts.includes(searchTerm)
|
||||
: principalParts.some(part => new RegExp(searchTerm, 'g').test(part))
|
||||
);
|
||||
return isInName || isInDefinition || isInDetails || isInPrincipalParts;
|
||||
}
|
||||
|
||||
export function getMatchingSearchWords() {
|
||||
let searchTerm = getSearchTerm();
|
||||
const searchTerm = getSearchTerm();
|
||||
const filters = getSearchFilters();
|
||||
if (searchTerm !== '' || !filters.allPartsOfSpeechChecked) {
|
||||
const matchingWords = window.currentDictionary.words.slice().filter(word => {
|
||||
if (!filters.allPartsOfSpeechChecked) {
|
||||
const partOfSpeech = word.partOfSpeech === '' ? 'Unclassified' : word.partOfSpeech;
|
||||
return filters.partsOfSpeech.hasOwnProperty(partOfSpeech) && filters.partsOfSpeech[partOfSpeech];
|
||||
}
|
||||
return true;
|
||||
}).filter(word => {
|
||||
searchTerm = filters.ignoreDiacritics ? removeDiacritics(searchTerm) : searchTerm;
|
||||
searchTerm = filters.caseSensitive ? searchTerm : searchTerm.toLowerCase();
|
||||
let name = filters.ignoreDiacritics ? removeDiacritics(word.name) : word.name;
|
||||
name = filters.caseSensitive ? name : name.toLowerCase();
|
||||
let definition = filters.ignoreDiacritics ? removeDiacritics(word.definition) : word.definition;
|
||||
definition = filters.caseSensitive ? definition : definition.toLowerCase();
|
||||
let details = filters.ignoreDiacritics ? removeDiacritics(word.details) : word.details;
|
||||
details = filters.caseSensitive ? details : details.toLowerCase();
|
||||
|
||||
const isInName = filters.name && (filters.exact
|
||||
? searchTerm == name
|
||||
: new RegExp(searchTerm, 'g').test(name));
|
||||
const isInDefinition = filters.definition && (filters.exact
|
||||
? searchTerm == definition
|
||||
: new RegExp(searchTerm, 'g').test(definition));
|
||||
const isInDetails = filters.details && new RegExp(searchTerm, 'g').test(details);
|
||||
return searchTerm === '' || isInName || isInDefinition || isInDetails;
|
||||
});
|
||||
const matchingWords = window.currentDictionary.words.slice()
|
||||
.filter(word => wordMatchesPartsOfSpeechFilter(word, filters))
|
||||
.filter(word => wordMatchesSearchTermAndOptions(word, searchTerm, filters));
|
||||
return matchingWords;
|
||||
}
|
||||
|
||||
return window.currentDictionary.words
|
||||
return window.currentDictionary.words;
|
||||
}
|
||||
|
||||
export function wordMatchesSearch(word) {
|
||||
const searchTerm = getSearchTerm();
|
||||
const filters = getSearchFilters();
|
||||
if (searchTerm !== '' || !filters.allPartsOfSpeechChecked) {
|
||||
if (searchTerm === '') {
|
||||
return wordMatchesPartsOfSpeechFilter(word, filters);
|
||||
}
|
||||
return wordMatchesPartsOfSpeechFilter(word, filters)
|
||||
&& wordMatchesSearchTermAndOptions(word, searchTerm, filters);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function highlightSearchTerm(word) {
|
||||
|
@ -80,6 +115,10 @@ export function highlightSearchTerm(word) {
|
|||
if (searchTerm) {
|
||||
const filters = getSearchFilters();
|
||||
const markedUpWord = cloneObject(word);
|
||||
if (filters.orthography) {
|
||||
markedUpWord.name = translateOrthography(markedUpWord.name);
|
||||
markedUpWord.details = parseReferences(markedUpWord.details);
|
||||
}
|
||||
if (filters.ignoreDiacritics) {
|
||||
const searchTermLength = searchTerm.length;
|
||||
searchTerm = removeDiacritics(searchTerm);
|
||||
|
@ -91,6 +130,16 @@ export function highlightSearchTerm(word) {
|
|||
+ '<mark>' + markedUpWord.name.substr(wordIndex, searchTermLength) + '</mark>'
|
||||
+ markedUpWord.name.substr(wordIndex + searchTermLength);
|
||||
});
|
||||
|
||||
if (markedUpWord.principalParts !== null) {
|
||||
const part = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.principalParts), filters.caseSensitive);
|
||||
part.forEach((wordIndex, i) => {
|
||||
wordIndex += '<mark></mark>'.length * i;
|
||||
markedUpWord.principalParts = markedUpWord.principalParts.substring(0, wordIndex)
|
||||
+ '<mark>' + markedUpWord.principalParts.substr(wordIndex, searchTermLength) + '</mark>'
|
||||
+ markedUpWord.principalParts.substr(wordIndex + searchTermLength);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (filters.definition) {
|
||||
const definitionMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.definition), filters.caseSensitive);
|
||||
|
@ -114,6 +163,10 @@ export function highlightSearchTerm(word) {
|
|||
const regexMethod = 'g' + (filters.caseSensitive ? '' : 'i');
|
||||
if (filters.name) {
|
||||
markedUpWord.name = markedUpWord.name.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
|
||||
|
||||
if (markedUpWord.principalParts !== null) {
|
||||
markedUpWord.principalParts = markedUpWord.principalParts.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
|
||||
}
|
||||
}
|
||||
if (filters.definition) {
|
||||
markedUpWord.definition = markedUpWord.definition.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { SETTINGS_KEY, DEFAULT_SETTINGS } from "../constants";
|
||||
import { cloneObject, removeTags } from "../helpers";
|
||||
import { usePhondueDigraphs } from "./KeyboardFire/phondue/ipaField";
|
||||
import { renderWords } from "./render";
|
||||
import { addMessage, hasToken } from "./utilities";
|
||||
import { renderWords } from "./render/words";
|
||||
import { addMessage, hasToken, objectValuesAreDifferent } from "./utilities";
|
||||
import { enableHotKeys, disableHotKeys } from "./hotkeys";
|
||||
import { showTemplateEditor, renderTemplateSelectOptions, showSelectedTemplate } from "./render/settings";
|
||||
|
||||
export function loadSettings() {
|
||||
const storedSettings = window.localStorage.getItem(SETTINGS_KEY);
|
||||
window.settings = storedSettings ? JSON.parse(storedSettings) : cloneObject(DEFAULT_SETTINGS);
|
||||
toggleIPAPronunciationFields(false);
|
||||
toggleShowAdvancedFields();
|
||||
}
|
||||
|
||||
export function saveSettings() {
|
||||
|
@ -17,19 +19,99 @@ export function saveSettings() {
|
|||
}
|
||||
|
||||
export function openSettingsModal() {
|
||||
const { useIPAPronunciationField, useHotkeys, defaultTheme } = window.settings;
|
||||
const { useIPAPronunciationField, useHotkeys, showAdvanced, defaultTheme, templates } = window.settings;
|
||||
|
||||
document.getElementById('settingsUseIPA').checked = useIPAPronunciationField;
|
||||
document.getElementById('settingsUseHotkeys').checked = useHotkeys;
|
||||
document.getElementById('settingsShowAdvanced').checked = showAdvanced;
|
||||
document.getElementById('settingsDefaultTheme').value = defaultTheme;
|
||||
|
||||
renderTemplateSelectOptions();
|
||||
showTemplateEditor(false);
|
||||
|
||||
document.getElementById('settingsModal').style.display = '';
|
||||
document.querySelector('#settingsModal .modal-content section').scrollTop = 0;
|
||||
}
|
||||
|
||||
export function updateTemplateSelects() {
|
||||
const { templates } = window.settings;
|
||||
|
||||
if (typeof templates !== 'undefined') {
|
||||
const templatesOptionsHTML = templates.map((template, index) => {
|
||||
return `<option value="${index.toString()}">${template.name}</options>`;
|
||||
}).join('');
|
||||
document.getElementById('savedDetailsTemplates').innerHTML = templatesOptionsHTML;
|
||||
|
||||
const templateSelects = document.getElementsByClassName('template-select');
|
||||
Array.from(templateSelects).forEach(select => {
|
||||
select.removeEventListener('click', showWordOptions);
|
||||
select.innerHTML = templatesOptionsHTML;
|
||||
select.addEventListener('click', showWordOptions);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function createTemplate() {
|
||||
window.settings.templates.push({ name: 'New Blank Template', template: '' });
|
||||
const newTemplateIndex = window.settings.templates.length - 1;
|
||||
|
||||
renderTemplateSelectOptions();
|
||||
document.getElementById('savedDetailsTemplates').value = (newTemplateIndex).toString();
|
||||
|
||||
editSavedTemplate(newTemplateIndex);
|
||||
}
|
||||
|
||||
export function saveTemplate() {
|
||||
const nameField = document.getElementById('templateNameField');
|
||||
const name = nameField.value.trim();
|
||||
const index = parseInt(nameField.getAttribute('template'));
|
||||
const template = document.getElementById('templateTextarea').value;
|
||||
window.settings.templates[index] = { name, template };
|
||||
|
||||
let storedSettings = window.localStorage.getItem(SETTINGS_KEY);
|
||||
storedSettings = storedSettings ? JSON.parse(storedSettings) : cloneObject(DEFAULT_SETTINGS);
|
||||
storedSettings.templates = window.settings.templates;
|
||||
window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(storedSettings));
|
||||
addMessage('Templates Saved!');
|
||||
|
||||
showTemplateEditor(false);
|
||||
|
||||
renderTemplateSelectOptions();
|
||||
}
|
||||
|
||||
export function deleteSelectedTemplate() {
|
||||
const nameField = document.getElementById('templateNameField');
|
||||
const index = nameField.getAttribute('template');
|
||||
const name = window.settings.templates[index].name;
|
||||
if (confirm(`Are you sure you want to delete the "${name}" template? This cannot be undone!`)) {
|
||||
delete window.settings.templates[index];
|
||||
window.settings.templates = window.settings.templates.filter(template => template != null);
|
||||
|
||||
saveSettings();
|
||||
|
||||
showTemplateEditor(false);
|
||||
|
||||
renderTemplateSelectOptions();
|
||||
}
|
||||
}
|
||||
|
||||
export function editSavedTemplate(selectEvent) {
|
||||
const { templates } = window.settings;
|
||||
const selectedIndex = typeof selectEvent.target !== 'undefined' ? selectEvent.target.value : selectEvent;
|
||||
|
||||
if (selectedIndex !== '') {
|
||||
showSelectedTemplate(templates[selectedIndex], selectedIndex);
|
||||
} else {
|
||||
showTemplateEditor(false);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSettingsModal() {
|
||||
window.settings.useIPAPronunciationField = document.getElementById('settingsUseIPA').checked;
|
||||
window.settings.useHotkeys = document.getElementById('settingsUseHotkeys').checked;
|
||||
window.settings.defaultTheme = document.getElementById('settingsDefaultTheme').value;
|
||||
const updatedSettings = cloneObject(window.settings);
|
||||
updatedSettings.useIPAPronunciationField = document.getElementById('settingsUseIPA').checked;
|
||||
updatedSettings.useHotkeys = document.getElementById('settingsUseHotkeys').checked;
|
||||
updatedSettings.showAdvanced = document.getElementById('settingsShowAdvanced').checked;
|
||||
updatedSettings.defaultTheme = document.getElementById('settingsDefaultTheme').value;
|
||||
|
||||
if (hasToken()) {
|
||||
import('./account/index.js').then(account => {
|
||||
|
@ -40,19 +122,31 @@ export function saveSettingsModal() {
|
|||
email = window.account.email;
|
||||
emailField.value = email;
|
||||
}
|
||||
window.account.email = email;
|
||||
window.account.publicName = removeTags(publicName.value).trim();
|
||||
window.account.allowEmails = document.getElementById('accountSettingsAllowEmails').checked;
|
||||
const updatedAccount = cloneObject(window.account);
|
||||
updatedAccount.email = email;
|
||||
updatedAccount.publicName = removeTags(publicName.value).trim();
|
||||
updatedAccount.allowEmails = document.getElementById('accountSettingsAllowEmails').checked;
|
||||
|
||||
const newPassword = document.getElementById('accountSettingsNewPassword').value;
|
||||
|
||||
account.editAccount(Object.assign({ newPassword }, window.account));
|
||||
if (objectValuesAreDifferent(updatedAccount, window.account)) {
|
||||
window.account = Object.assign(window.account, updatedAccount);
|
||||
account.editAccount(Object.assign({ newPassword }, window.account));
|
||||
} else {
|
||||
addMessage('No changes made to Account.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveSettings();
|
||||
toggleHotkeysEnabled();
|
||||
toggleIPAPronunciationFields();
|
||||
if (objectValuesAreDifferent(updatedSettings, window.settings)) {
|
||||
window.settings = Object.assign(window.settings, updatedSettings);
|
||||
saveSettings();
|
||||
toggleHotkeysEnabled();
|
||||
toggleIPAPronunciationFields();
|
||||
toggleShowAdvancedFields();
|
||||
} else {
|
||||
addMessage('No changes made to Settings.');
|
||||
}
|
||||
}
|
||||
|
||||
export function saveAndCloseSettingsModal() {
|
||||
|
@ -89,3 +183,30 @@ export function toggleIPAPronunciationFields(render = true) {
|
|||
renderWords();
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleShowAdvancedFields() {
|
||||
const buttons = document.getElementsByClassName('expand-advanced-form'),
|
||||
forms = document.getElementsByClassName('advanced-word-form');
|
||||
const formsWithFilledFields = [];
|
||||
|
||||
Array.from(forms).forEach(form => {
|
||||
const fields = form.querySelectorAll('input, textarea');
|
||||
const formHasFieldFilled = Array.from(fields).some(field => field.value.trim() !== '');
|
||||
if (window.settings.showAdvanced || formHasFieldFilled) {
|
||||
form.style.display = 'block';
|
||||
} else {
|
||||
form.style.display = 'none';
|
||||
}
|
||||
if (formHasFieldFilled) {
|
||||
formsWithFilledFields.push(form.id.replace('advancedForm', ''));
|
||||
}
|
||||
});
|
||||
Array.from(buttons).forEach(button => {
|
||||
const formHasFilledField = formsWithFilledFields.includes(button.id.replace('expandAdvancedForm', ''));
|
||||
if (window.settings.showAdvanced || formHasFilledField) {
|
||||
button.innerText = 'Hide Advanced Fields';
|
||||
} else {
|
||||
button.innerText = 'Show Advanced Fields';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,391 +0,0 @@
|
|||
import {showSection, hideDetailsPanel} from './displayToggles';
|
||||
import { renderWords, renderEditForm, renderMaximizedTextbox, renderInfoModal, renderIPATable, renderIPAHelp } from './render';
|
||||
import { confirmEditWord, cancelEditWord, confirmDeleteWord, submitWordForm } from './wordManagement';
|
||||
import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords, confirmDeleteDictionary } from './dictionaryManagement';
|
||||
import { goToNextPage, goToPreviousPage, goToPage } from './pagination';
|
||||
import { insertAtCursor, getInputSelection, setSelectionRange } from './StackOverflow/inputCursorManagement';
|
||||
import { usePhondueDigraphs } from './KeyboardFire/phondue/ipaField';
|
||||
import { openSettingsModal, saveSettingsModal, saveAndCloseSettingsModal } from './settings';
|
||||
import { enableHotKeys } from './hotkeys';
|
||||
import { showSearchModal, clearSearchText, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from './search';
|
||||
import helpFile from '../markdown/help.md';
|
||||
import termsFile from '../markdown/terms.md';
|
||||
import privacyFile from '../markdown/privacy.md';
|
||||
import { dismiss, isDismissed } from './announcements';
|
||||
import { fadeOutElement } from './utilities';
|
||||
|
||||
export default function setupListeners() {
|
||||
setupAnnouncements();
|
||||
setupDetailsTabs();
|
||||
setupHeaderButtons();
|
||||
setupWordForm();
|
||||
setupMobileWordFormButton();
|
||||
setupInfoButtons();
|
||||
if (window.settings.useHotkeys) {
|
||||
enableHotKeys();
|
||||
}
|
||||
}
|
||||
|
||||
export function setupHeaderButtons() {
|
||||
setupSearchBar();
|
||||
setupSettingsModal();
|
||||
|
||||
document.getElementById('loginCreateAccountButton').addEventListener('click', () => {
|
||||
import('./account/index.js').then(account => {
|
||||
account.showLoginForm();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupAnnouncements() {
|
||||
const announcements = document.querySelectorAll('.announcement');
|
||||
Array.from(announcements).forEach(announcement => {
|
||||
if (announcement.id && isDismissed(announcement.id)) {
|
||||
fadeOutElement(announcement);
|
||||
} else {
|
||||
announcement.querySelector('.close-button').addEventListener('click', () => dismiss(announcement));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupDetailsTabs() {
|
||||
const tabs = document.querySelectorAll('#detailsSection nav li');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const section = tab.innerText.toLowerCase();
|
||||
if (section === 'edit') {
|
||||
openEditModal();
|
||||
// import('../test.js').then(function (test) {
|
||||
// // Render page
|
||||
// test.aaa();
|
||||
// });
|
||||
} else {
|
||||
const isActive = tab.classList.contains('active');
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
if (isActive) {
|
||||
hideDetailsPanel();
|
||||
} else {
|
||||
tab.classList.add('active');
|
||||
showSection(section);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
setupEditFormTabs();
|
||||
setupEditFormInteractions();
|
||||
setupEditFormButtons();
|
||||
}
|
||||
|
||||
function setupEditFormTabs() {
|
||||
const tabs = document.querySelectorAll('#editModal nav li');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
tabs.forEach(t => {
|
||||
t.classList.remove('active');
|
||||
document.getElementById('edit' + t.innerText + 'Tab').style.display = 'none';
|
||||
});
|
||||
tab.classList.add('active');
|
||||
document.getElementById('edit' + tab.innerText + 'Tab').style.display = '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupEditFormInteractions() {
|
||||
const preventDuplicatesBox = document.getElementById('editPreventDuplicates');
|
||||
preventDuplicatesBox.addEventListener('change', () => {
|
||||
const caseSensitiveBox = document.getElementById('editCaseSensitive');
|
||||
if (preventDuplicatesBox.checked) {
|
||||
caseSensitiveBox.disabled = false;
|
||||
} else {
|
||||
caseSensitiveBox.disabled = true;
|
||||
caseSensitiveBox.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupEditFormButtons() {
|
||||
document.getElementById('editSave').addEventListener('click', saveEditModal);
|
||||
document.getElementById('editSaveAndClose').addEventListener('click', saveAndCloseEditModal);
|
||||
document.getElementById('importDictionaryFile').addEventListener('change', importDictionary);
|
||||
document.getElementById('importWordsCSV').addEventListener('change', importWords);
|
||||
document.getElementById('exportDictionaryButton').addEventListener('click', exportDictionary);
|
||||
document.getElementById('exportWordsButton').addEventListener('click', exportWords);
|
||||
document.getElementById('deleteDictionaryButton').addEventListener('click', confirmDeleteDictionary);
|
||||
|
||||
setupMaximizeButtons();
|
||||
}
|
||||
|
||||
function setupSearchBar() {
|
||||
const searchBox = document.getElementById('searchBox'),
|
||||
clearSearchButton = document.getElementById('clearSearchButton'),
|
||||
openSearchModal = document.getElementById('openSearchModal'),
|
||||
searchIgnoreDiacritics = document.getElementById('searchIgnoreDiacritics'),
|
||||
searchExactWords = document.getElementById('searchExactWords'),
|
||||
searchIncludeDetails = document.getElementById('searchIncludeDetails');
|
||||
searchBox.addEventListener('change', () => {
|
||||
renderWords();
|
||||
});
|
||||
searchBox.addEventListener('input', event => {
|
||||
openSearchModal.value = event.target.value;
|
||||
});
|
||||
clearSearchButton.addEventListener('click', clearSearchText);
|
||||
openSearchModal.addEventListener('click', showSearchModal);
|
||||
|
||||
const toggleDetailsCheck = function() {
|
||||
if (searchExactWords.checked) {
|
||||
searchIncludeDetails.checked = false;
|
||||
searchIncludeDetails.disabled = true;
|
||||
} else {
|
||||
searchIncludeDetails.disabled = false;
|
||||
searchIncludeDetails.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
searchIgnoreDiacritics.addEventListener('change', () => {
|
||||
if (searchIgnoreDiacritics.checked) {
|
||||
searchExactWords.checked = false;
|
||||
searchExactWords.disabled = true;
|
||||
} else {
|
||||
searchExactWords.disabled = false;
|
||||
}
|
||||
toggleDetailsCheck();
|
||||
});
|
||||
|
||||
searchExactWords.addEventListener('change', () => toggleDetailsCheck());
|
||||
}
|
||||
|
||||
export function setupSearchFilters() {
|
||||
const searchFilters = document.querySelectorAll('#searchOptions input[type="checkbox"]'),
|
||||
searchBox = document.getElementById('searchBox');
|
||||
Array.from(searchFilters).concat([searchBox]).forEach(filter => {
|
||||
filter.removeEventListener('change', renderWords);
|
||||
filter.addEventListener('change', renderWords);
|
||||
});
|
||||
document.getElementById('checkAllFilters').removeEventListener('click', checkAllPartsOfSpeechFilters);
|
||||
document.getElementById('checkAllFilters').addEventListener('click', checkAllPartsOfSpeechFilters);
|
||||
document.getElementById('uncheckAllFilters').removeEventListener('click', uncheckAllPartsOfSpeechFilters);
|
||||
document.getElementById('uncheckAllFilters').addEventListener('click', uncheckAllPartsOfSpeechFilters);
|
||||
}
|
||||
|
||||
function setupWordForm() {
|
||||
const wordForm = document.getElementById('wordForm'),
|
||||
addWordButton = document.getElementById('addWordButton');
|
||||
wordForm.addEventListener('submit', event => {
|
||||
// Allow semantic form and prevent it from getting submitted
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
addWordButton.addEventListener('click', submitWordForm);
|
||||
|
||||
setupIPAFields();
|
||||
setupMaximizeButtons();
|
||||
}
|
||||
|
||||
export function setupWordOptionButtons() {
|
||||
const wordOptionButtons = document.getElementsByClassName('word-option-button');
|
||||
const showWordOptions = function() {
|
||||
this.parentElement.querySelector('.word-option-list').style.display = '';
|
||||
}
|
||||
const hideWordOptions = function(e) {
|
||||
if (!e.target.classList.contains('word-option-button')) {
|
||||
const allWordOptions = document.querySelectorAll('.word-option-list');
|
||||
Array.from(allWordOptions).forEach(wordOptionList => {
|
||||
wordOptionList.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Array.from(wordOptionButtons).forEach(button => {
|
||||
button.removeEventListener('click', showWordOptions);
|
||||
button.addEventListener('click', showWordOptions);
|
||||
});
|
||||
|
||||
document.removeEventListener('click', hideWordOptions);
|
||||
document.addEventListener('click', hideWordOptions);
|
||||
|
||||
}
|
||||
|
||||
export function setupWordOptionSelections() {
|
||||
const wordOptions = document.getElementsByClassName('word-option');
|
||||
Array.from(wordOptions).forEach(option => {
|
||||
switch (option.innerText) {
|
||||
case 'Edit': {
|
||||
option.removeEventListener('click', renderEditForm);
|
||||
option.addEventListener('click', renderEditForm);
|
||||
break;
|
||||
}
|
||||
case 'Delete': {
|
||||
option.removeEventListener('click', confirmDeleteWord);
|
||||
option.addEventListener('click', confirmDeleteWord);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function setupSettingsModal() {
|
||||
document.getElementById('settingsButton').addEventListener('click', openSettingsModal);
|
||||
document.getElementById('settingsSave').addEventListener('click', saveSettingsModal);
|
||||
document.getElementById('settingsSaveAndClose').addEventListener('click', saveAndCloseSettingsModal);
|
||||
}
|
||||
|
||||
export function setupWordEditFormButtons() {
|
||||
const saveChangesButtons = document.getElementsByClassName('edit-save-changes'),
|
||||
cancelChangesButtons = document.getElementsByClassName('edit-cancel');
|
||||
Array.from(saveChangesButtons).forEach(button => {
|
||||
button.removeEventListener('click', confirmEditWord);
|
||||
button.addEventListener('click', confirmEditWord);
|
||||
});
|
||||
Array.from(cancelChangesButtons).forEach(button => {
|
||||
button.removeEventListener('click', cancelEditWord);
|
||||
button.addEventListener('click', cancelEditWord);
|
||||
});
|
||||
|
||||
setupIPAFields();
|
||||
setupMaximizeButtons();
|
||||
}
|
||||
|
||||
export function setupMobileWordFormButton() {
|
||||
const mobileButton = document.getElementById('mobileWordFormShow'),
|
||||
wordForm = document.getElementById('wordForm');
|
||||
|
||||
mobileButton.addEventListener('click', () => {
|
||||
if (mobileButton.innerText === '+') {
|
||||
wordForm.style.display = 'block';
|
||||
mobileButton.innerHTML = '×︎';
|
||||
} else {
|
||||
wordForm.style.display = '';
|
||||
mobileButton.innerHTML = '+';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function setupPagination() {
|
||||
const nextButtons = document.getElementsByClassName('next-button'),
|
||||
prevButtons = document.getElementsByClassName('prev-button'),
|
||||
pageSelectors = document.getElementsByClassName('page-selector');
|
||||
|
||||
Array.from(nextButtons).forEach(nextButton => {
|
||||
nextButton.removeEventListener('click', goToNextPage);
|
||||
nextButton.addEventListener('click', goToNextPage);
|
||||
});
|
||||
Array.from(prevButtons).forEach(prevButton => {
|
||||
prevButton.removeEventListener('click', goToPreviousPage);
|
||||
prevButton.addEventListener('click', goToPreviousPage);
|
||||
});
|
||||
|
||||
Array.from(pageSelectors).forEach(pageSelector => {
|
||||
pageSelector.removeEventListener('change', goToPage);
|
||||
pageSelector.addEventListener('change', goToPage);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupIPAFields() {
|
||||
if (window.settings.useIPAPronunciationField) {
|
||||
const ipaFields = document.getElementsByClassName('ipa-field');
|
||||
Array.from(ipaFields).forEach(field => {
|
||||
field.removeEventListener('keypress', usePhondueDigraphs);
|
||||
field.addEventListener('keypress', usePhondueDigraphs);
|
||||
});
|
||||
}
|
||||
|
||||
setupIPAButtons();
|
||||
}
|
||||
|
||||
export function setupIPAButtons() {
|
||||
const ipaTableButtons = document.getElementsByClassName('ipa-table-button'),
|
||||
ipaFieldHelpButtons = document.getElementsByClassName('ipa-field-help-button');
|
||||
|
||||
Array.from(ipaTableButtons).forEach(button => {
|
||||
button.removeEventListener('click', renderIPATable);
|
||||
button.addEventListener('click', renderIPATable);
|
||||
});
|
||||
|
||||
Array.from(ipaFieldHelpButtons).forEach(button => {
|
||||
button.removeEventListener('click', renderIPAHelp);
|
||||
button.addEventListener('click', renderIPAHelp);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupIPATable(modal, textBox) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
|
||||
headerTextBox = modal.querySelector('header input'),
|
||||
ipaButtons = modal.querySelectorAll('.td-btn button');
|
||||
Array.from(closeElements).forEach(close => {
|
||||
close.addEventListener('click', () => {
|
||||
textBox.focus();
|
||||
const endOfTextbox = textBox.value.length;
|
||||
setSelectionRange(textBox, endOfTextbox, endOfTextbox);
|
||||
modal.parentElement.removeChild(modal);
|
||||
});
|
||||
});
|
||||
|
||||
headerTextBox.addEventListener('change', () => {
|
||||
textBox.value = headerTextBox.value;
|
||||
});
|
||||
|
||||
Array.from(ipaButtons).forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
insertAtCursor(headerTextBox, button.innerText);
|
||||
textBox.value = headerTextBox.value;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
headerTextBox.focus();
|
||||
const endOfTextbox = headerTextBox.value.length;
|
||||
setSelectionRange(headerTextBox, endOfTextbox, endOfTextbox);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
export function setupMaximizeButtons() {
|
||||
const maximizeButtons = document.getElementsByClassName('maximize-button');
|
||||
Array.from(maximizeButtons).forEach(button => {
|
||||
button.removeEventListener('click', renderMaximizedTextbox);
|
||||
button.addEventListener('click', renderMaximizedTextbox);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupMaximizeModal(modal, textBox) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
|
||||
maximizedTextBox = modal.querySelector('textarea');
|
||||
Array.from(closeElements).forEach(close => {
|
||||
close.addEventListener('click', () => {
|
||||
const selection = getInputSelection(maximizedTextBox);
|
||||
textBox.focus();
|
||||
setSelectionRange(textBox, selection.start, selection.end);
|
||||
modal.parentElement.removeChild(modal);
|
||||
});
|
||||
});
|
||||
|
||||
maximizedTextBox.addEventListener('change', () => {
|
||||
textBox.value = maximizedTextBox.value;
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
const selection = getInputSelection(textBox);
|
||||
maximizedTextBox.focus();
|
||||
setSelectionRange(maximizedTextBox, selection.start, selection.end);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
export function setupInfoButtons() {
|
||||
document.getElementById('helpInfoButton').addEventListener('click', () => {
|
||||
renderInfoModal(helpFile);
|
||||
});
|
||||
document.getElementById('termsInfoButton').addEventListener('click', () => {
|
||||
renderInfoModal(termsFile);
|
||||
});
|
||||
document.getElementById('privacyInfoButton').addEventListener('click', () => {
|
||||
renderInfoModal(privacyFile);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupInfoModal(modal) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
|
||||
Array.from(closeElements).forEach(close => {
|
||||
close.addEventListener('click', () => {
|
||||
modal.parentElement.removeChild(modal);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { renderMaximizedTextbox, renderInfoModal, renderIPATable, renderIPAHelp } from '../render/modals';
|
||||
import helpFile from '../../markdown/help.md';
|
||||
import termsFile from '../../markdown/terms.md';
|
||||
import privacyFile from '../../markdown/privacy.md';
|
||||
import { setupSearchBar } from './search';
|
||||
import { setupSettingsModal } from './modals';
|
||||
|
||||
export function setupHeaderButtons() {
|
||||
setupSearchBar();
|
||||
setupSettingsModal();
|
||||
|
||||
document.getElementById('loginCreateAccountButton').addEventListener('click', () => {
|
||||
import('../account/index.js').then(account => {
|
||||
account.showLoginForm();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function setupIPAButtons() {
|
||||
const ipaTableButtons = document.getElementsByClassName('ipa-table-button'),
|
||||
ipaFieldHelpButtons = document.getElementsByClassName('ipa-field-help-button');
|
||||
|
||||
Array.from(ipaTableButtons).forEach(button => {
|
||||
button.removeEventListener('click', renderIPATable);
|
||||
button.addEventListener('click', renderIPATable);
|
||||
});
|
||||
|
||||
Array.from(ipaFieldHelpButtons).forEach(button => {
|
||||
button.removeEventListener('click', renderIPAHelp);
|
||||
button.addEventListener('click', renderIPAHelp);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupMaximizeButtons() {
|
||||
const maximizeButtons = document.getElementsByClassName('maximize-button');
|
||||
Array.from(maximizeButtons).forEach(button => {
|
||||
button.removeEventListener('click', renderMaximizedTextbox);
|
||||
button.addEventListener('click', renderMaximizedTextbox);
|
||||
});
|
||||
}
|
||||
|
||||
export function setupInfoButtons() {
|
||||
document.getElementById('helpInfoButton').addEventListener('click', () => {
|
||||
renderInfoModal(helpFile);
|
||||
});
|
||||
document.getElementById('termsInfoButton').addEventListener('click', () => {
|
||||
renderInfoModal(termsFile);
|
||||
});
|
||||
document.getElementById('privacyInfoButton').addEventListener('click', () => {
|
||||
renderInfoModal(privacyFile);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { showSection, hideDetailsPanel } from '../displayToggles';
|
||||
import { openEditModal, saveEditModal, saveAndCloseEditModal, exportDictionary, exportWords, importDictionary, importWords, confirmDeleteDictionary } from '../dictionaryManagement';
|
||||
import { setupMaximizeButtons } from './buttons';
|
||||
|
||||
export function setupDetailsTabs() {
|
||||
const tabs = document.querySelectorAll('#detailsSection nav li');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const section = tab.innerText.toLowerCase();
|
||||
if (section === 'edit') {
|
||||
openEditModal();
|
||||
} else {
|
||||
const isActive = tab.classList.contains('active');
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
if (isActive) {
|
||||
hideDetailsPanel();
|
||||
} else {
|
||||
tab.classList.add('active');
|
||||
showSection(section);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
setupEditFormTabs();
|
||||
setupEditFormInteractions();
|
||||
setupEditFormButtons();
|
||||
}
|
||||
|
||||
function setupEditFormTabs() {
|
||||
const tabs = document.querySelectorAll('#editModal nav li');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
tabs.forEach(t => {
|
||||
t.classList.remove('active');
|
||||
document.getElementById('edit' + t.innerText + 'Tab').style.display = 'none';
|
||||
});
|
||||
tab.classList.add('active');
|
||||
const tabSection = document.getElementById('edit' + tab.innerText + 'Tab');
|
||||
tabSection.style.display = '';
|
||||
tabSection.scrollTop = 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupEditFormInteractions() {
|
||||
const preventDuplicatesBox = document.getElementById('editPreventDuplicates');
|
||||
preventDuplicatesBox.addEventListener('change', () => {
|
||||
const caseSensitiveBox = document.getElementById('editCaseSensitive');
|
||||
if (preventDuplicatesBox.checked) {
|
||||
caseSensitiveBox.disabled = false;
|
||||
} else {
|
||||
caseSensitiveBox.disabled = true;
|
||||
caseSensitiveBox.checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupEditFormButtons() {
|
||||
document.getElementById('editSave').addEventListener('click', saveEditModal);
|
||||
document.getElementById('editSaveAndClose').addEventListener('click', saveAndCloseEditModal);
|
||||
document.getElementById('importDictionaryFile').addEventListener('change', importDictionary);
|
||||
document.getElementById('importWordsCSV').addEventListener('change', importWords);
|
||||
document.getElementById('exportDictionaryButton').addEventListener('click', exportDictionary);
|
||||
document.getElementById('exportWordsButton').addEventListener('click', exportWords);
|
||||
document.getElementById('deleteDictionaryButton').addEventListener('click', confirmDeleteDictionary);
|
||||
|
||||
setupMaximizeButtons();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { usePhondueDigraphs } from '../KeyboardFire/phondue/ipaField';
|
||||
import { enableHotKeys } from '../hotkeys';
|
||||
import { dismiss, isDismissed } from '../announcements';
|
||||
import { fadeOutElement } from '../utilities';
|
||||
import { setupDetailsTabs } from './details';
|
||||
import { setupWordForm, setupMobileWordFormButton } from './words';
|
||||
import { setupIPAButtons, setupHeaderButtons, setupInfoButtons } from './buttons';
|
||||
import { setupTemplateForm, setupTemplateSelectOptions } from './settings';
|
||||
|
||||
export default function setupListeners() {
|
||||
setupAnnouncements();
|
||||
setupDetailsTabs();
|
||||
setupTemplateForm();
|
||||
setupHeaderButtons();
|
||||
setupWordForm();
|
||||
setupMobileWordFormButton();
|
||||
setupInfoButtons();
|
||||
setupTemplateSelectOptions();
|
||||
if (window.settings.useHotkeys) {
|
||||
enableHotKeys();
|
||||
}
|
||||
}
|
||||
|
||||
function setupAnnouncements() {
|
||||
const announcements = document.querySelectorAll('.announcement');
|
||||
Array.from(announcements).forEach(announcement => {
|
||||
if (announcement.id && isDismissed(announcement.id)) {
|
||||
fadeOutElement(announcement);
|
||||
} else {
|
||||
announcement.querySelector('.close-button').addEventListener('click', () => dismiss(announcement));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function setupIPAFields() {
|
||||
if (window.settings.useIPAPronunciationField) {
|
||||
const ipaFields = document.getElementsByClassName('ipa-field');
|
||||
Array.from(ipaFields).forEach(field => {
|
||||
field.removeEventListener('keypress', usePhondueDigraphs);
|
||||
field.addEventListener('keypress', usePhondueDigraphs);
|
||||
});
|
||||
}
|
||||
|
||||
setupIPAButtons();
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import { insertAtCursor, getInputSelection, setSelectionRange } from '../StackOverflow/inputCursorManagement';
|
||||
import {
|
||||
openSettingsModal,
|
||||
saveSettingsModal,
|
||||
saveAndCloseSettingsModal,
|
||||
createTemplate,
|
||||
saveTemplate
|
||||
} from '../settings';
|
||||
|
||||
export function setupSettingsModal() {
|
||||
document.getElementById('createTemplateButton').addEventListener('click', createTemplate);
|
||||
document.getElementById('saveTemplateButton').addEventListener('click', saveTemplate);
|
||||
|
||||
document.getElementById('settingsButton').addEventListener('click', openSettingsModal);
|
||||
document.getElementById('settingsSave').addEventListener('click', saveSettingsModal);
|
||||
document.getElementById('settingsSaveAndClose').addEventListener('click', saveAndCloseSettingsModal);
|
||||
}
|
||||
|
||||
export function setupIPATable(modal, textBox) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
|
||||
headerTextBox = modal.querySelector('header input'),
|
||||
ipaButtons = modal.querySelectorAll('.td-btn button');
|
||||
Array.from(closeElements).forEach(close => {
|
||||
close.addEventListener('click', () => {
|
||||
textBox.focus();
|
||||
const endOfTextbox = textBox.value.length;
|
||||
setSelectionRange(textBox, endOfTextbox, endOfTextbox);
|
||||
modal.parentElement.removeChild(modal);
|
||||
});
|
||||
});
|
||||
|
||||
headerTextBox.addEventListener('change', () => {
|
||||
textBox.value = headerTextBox.value;
|
||||
});
|
||||
|
||||
Array.from(ipaButtons).forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
insertAtCursor(headerTextBox, button.innerText);
|
||||
textBox.value = headerTextBox.value;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
headerTextBox.focus();
|
||||
const endOfTextbox = headerTextBox.value.length;
|
||||
setSelectionRange(headerTextBox, endOfTextbox, endOfTextbox);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
export function setupMaximizeModal(modal, textBox) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button, .done-button'),
|
||||
maximizedTextBox = modal.querySelector('textarea');
|
||||
Array.from(closeElements).forEach(close => {
|
||||
close.addEventListener('click', () => {
|
||||
const selection = getInputSelection(maximizedTextBox);
|
||||
textBox.focus();
|
||||
setSelectionRange(textBox, selection.start, selection.end);
|
||||
modal.parentElement.removeChild(modal);
|
||||
});
|
||||
});
|
||||
|
||||
maximizedTextBox.addEventListener('change', () => {
|
||||
textBox.value = maximizedTextBox.value;
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
const selection = getInputSelection(textBox);
|
||||
maximizedTextBox.focus();
|
||||
setSelectionRange(maximizedTextBox, selection.start, selection.end);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
export function setupInfoModal(modal) {
|
||||
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
|
||||
Array.from(closeElements).forEach(close => {
|
||||
close.addEventListener('click', () => {
|
||||
modal.parentElement.removeChild(modal);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import { renderWords } from '../render/words';
|
||||
import { showSearchModal, clearSearchText, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from '../search';
|
||||
|
||||
export function setupSearchBar() {
|
||||
const searchBox = document.getElementById('searchBox'),
|
||||
clearSearchButton = document.getElementById('clearSearchButton'),
|
||||
openSearchModal = document.getElementById('openSearchModal'),
|
||||
searchIgnoreDiacritics = document.getElementById('searchIgnoreDiacritics'),
|
||||
searchExactWords = document.getElementById('searchExactWords'),
|
||||
searchIncludeDetails = document.getElementById('searchIncludeDetails');
|
||||
searchBox.addEventListener('change', () => {
|
||||
renderWords();
|
||||
});
|
||||
searchBox.addEventListener('input', event => {
|
||||
openSearchModal.value = event.target.value;
|
||||
});
|
||||
clearSearchButton.addEventListener('click', clearSearchText);
|
||||
openSearchModal.addEventListener('click', showSearchModal);
|
||||
|
||||
const toggleDetailsCheck = function() {
|
||||
if (searchExactWords.checked) {
|
||||
searchIncludeDetails.checked = false;
|
||||
searchIncludeDetails.disabled = true;
|
||||
} else {
|
||||
searchIncludeDetails.disabled = false;
|
||||
searchIncludeDetails.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
searchIgnoreDiacritics.addEventListener('change', () => {
|
||||
if (searchIgnoreDiacritics.checked) {
|
||||
searchExactWords.checked = false;
|
||||
searchExactWords.disabled = true;
|
||||
} else {
|
||||
searchExactWords.disabled = false;
|
||||
}
|
||||
toggleDetailsCheck();
|
||||
});
|
||||
|
||||
searchExactWords.addEventListener('change', () => toggleDetailsCheck());
|
||||
}
|
||||
|
||||
export function setupSearchFilters() {
|
||||
const searchFilters = document.querySelectorAll('#searchOptions input[type="checkbox"]'),
|
||||
searchBox = document.getElementById('searchBox');
|
||||
Array.from(searchFilters).concat([searchBox]).forEach(filter => {
|
||||
filter.removeEventListener('change', renderWords);
|
||||
filter.addEventListener('change', renderWords);
|
||||
});
|
||||
document.getElementById('checkAllFilters').removeEventListener('click', checkAllPartsOfSpeechFilters);
|
||||
document.getElementById('checkAllFilters').addEventListener('click', checkAllPartsOfSpeechFilters);
|
||||
document.getElementById('uncheckAllFilters').removeEventListener('click', uncheckAllPartsOfSpeechFilters);
|
||||
document.getElementById('uncheckAllFilters').addEventListener('click', uncheckAllPartsOfSpeechFilters);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { createTemplate, saveTemplate, editSavedTemplate, deleteSelectedTemplate } from "../settings";
|
||||
|
||||
export function setupTemplateForm() {
|
||||
document.getElementById('createTemplateButton').addEventListener('click', createTemplate);
|
||||
document.getElementById('saveTemplateButton').addEventListener('click', saveTemplate);
|
||||
document.getElementById('deleteTemplateButton').addEventListener('click', deleteSelectedTemplate);
|
||||
setupSavedTemplatesSelect();
|
||||
}
|
||||
|
||||
export function setupSavedTemplatesSelect() {
|
||||
const savedTemplatesSelect = document.getElementById('savedDetailsTemplates');
|
||||
|
||||
savedTemplatesSelect.removeEventListener('change', editSavedTemplate);
|
||||
savedTemplatesSelect.addEventListener('change', editSavedTemplate);
|
||||
}
|
||||
|
||||
export function setupTemplateSelectOptions() {
|
||||
const fillDetailsWithTemplate = function (e) {
|
||||
if (e.target.value !== '') {
|
||||
const template = window.settings.templates[parseInt(e.target.value)];
|
||||
let detailsId = 'wordDetails' + e.target.id.replace('templateSelect', '');
|
||||
document.getElementById(detailsId).value = template.template;
|
||||
}
|
||||
}
|
||||
Array.from(document.getElementsByClassName('template-select')).forEach(select => {
|
||||
if (select.id !== 'savedDetailsTemplates') {
|
||||
select.removeEventListener('change', fillDetailsWithTemplate);
|
||||
select.addEventListener('change', fillDetailsWithTemplate);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
import { renderEditForm } from '../render/words';
|
||||
import { confirmEditWord, cancelEditWord, confirmDeleteWord, expandAdvancedForm, submitWordForm } from '../wordManagement';
|
||||
// import { goToNextPage, goToPreviousPage, goToPage } from '../pagination';
|
||||
import { setupMaximizeButtons } from './buttons';
|
||||
import { setupIPAFields } from '.';
|
||||
|
||||
export function setupWordForm() {
|
||||
const wordForm = document.getElementById('wordForm'),
|
||||
expandAdvancedFormButton = document.getElementById('expandAdvancedForm'),
|
||||
addWordButton = document.getElementById('addWordButton');
|
||||
wordForm.addEventListener('submit', event => {
|
||||
// Allow semantic form and prevent it from getting submitted
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
expandAdvancedFormButton.addEventListener('click', expandAdvancedForm);
|
||||
addWordButton.addEventListener('click', submitWordForm);
|
||||
|
||||
setupIPAFields();
|
||||
setupMaximizeButtons();
|
||||
}
|
||||
|
||||
export function setupWordOptionButtons() {
|
||||
const wordOptionButtons = document.getElementsByClassName('word-option-button');
|
||||
const showWordOptions = function() {
|
||||
this.parentElement.querySelector('.word-option-list').style.display = '';
|
||||
}
|
||||
const hideWordOptions = function(e) {
|
||||
if (!e.target.classList.contains('word-option-button')) {
|
||||
const allWordOptions = document.querySelectorAll('.word-option-list');
|
||||
Array.from(allWordOptions).forEach(wordOptionList => {
|
||||
wordOptionList.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Array.from(wordOptionButtons).forEach(button => {
|
||||
button.removeEventListener('click', showWordOptions);
|
||||
button.addEventListener('click', showWordOptions);
|
||||
});
|
||||
|
||||
document.removeEventListener('click', hideWordOptions);
|
||||
document.addEventListener('click', hideWordOptions);
|
||||
|
||||
}
|
||||
|
||||
export function setupWordOptionSelections() {
|
||||
const wordOptions = document.getElementsByClassName('word-option');
|
||||
Array.from(wordOptions).forEach(option => {
|
||||
switch (option.innerText) {
|
||||
case 'Edit': {
|
||||
option.removeEventListener('click', renderEditForm);
|
||||
option.addEventListener('click', renderEditForm);
|
||||
break;
|
||||
}
|
||||
case 'Delete': {
|
||||
option.removeEventListener('click', confirmDeleteWord);
|
||||
option.addEventListener('click', confirmDeleteWord);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function setupWordEditFormButtons() {
|
||||
const expandAdvancedFormButtons = document.getElementsByClassName('expand-advanced-form'),
|
||||
saveChangesButtons = document.getElementsByClassName('edit-save-changes'),
|
||||
cancelChangesButtons = document.getElementsByClassName('edit-cancel');
|
||||
Array.from(expandAdvancedFormButtons).forEach(button => {
|
||||
button.removeEventListener('click', expandAdvancedForm);
|
||||
button.addEventListener('click', expandAdvancedForm);
|
||||
});
|
||||
Array.from(saveChangesButtons).forEach(button => {
|
||||
button.removeEventListener('click', confirmEditWord);
|
||||
button.addEventListener('click', confirmEditWord);
|
||||
});
|
||||
Array.from(cancelChangesButtons).forEach(button => {
|
||||
button.removeEventListener('click', cancelEditWord);
|
||||
button.addEventListener('click', cancelEditWord);
|
||||
});
|
||||
|
||||
setupIPAFields();
|
||||
setupMaximizeButtons();
|
||||
}
|
||||
|
||||
export function setupMobileWordFormButton() {
|
||||
const mobileButton = document.getElementById('mobileWordFormShow'),
|
||||
wordForm = document.getElementById('wordForm');
|
||||
|
||||
mobileButton.addEventListener('click', () => {
|
||||
if (mobileButton.innerText === '+') {
|
||||
wordForm.style.display = 'block';
|
||||
mobileButton.innerHTML = '×︎';
|
||||
} else {
|
||||
wordForm.style.display = '';
|
||||
mobileButton.innerHTML = '+';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// export function setupPagination() {
|
||||
// const nextButtons = document.getElementsByClassName('next-button'),
|
||||
// prevButtons = document.getElementsByClassName('prev-button'),
|
||||
// pageSelectors = document.getElementsByClassName('page-selector');
|
||||
|
||||
// Array.from(nextButtons).forEach(nextButton => {
|
||||
// nextButton.removeEventListener('click', goToNextPage);
|
||||
// nextButton.addEventListener('click', goToNextPage);
|
||||
// });
|
||||
// Array.from(prevButtons).forEach(prevButton => {
|
||||
// prevButton.removeEventListener('click', goToPreviousPage);
|
||||
// prevButton.addEventListener('click', goToPreviousPage);
|
||||
// });
|
||||
|
||||
// Array.from(pageSelectors).forEach(pageSelector => {
|
||||
// pageSelector.removeEventListener('change', goToPage);
|
||||
// pageSelector.addEventListener('change', goToPage);
|
||||
// });
|
||||
// }
|
|
@ -1,4 +1,3 @@
|
|||
import { addWord } from './wordManagement';
|
||||
import { getCookie } from './StackOverflow/cookie';
|
||||
|
||||
export function getNextId() {
|
||||
|
@ -176,3 +175,19 @@ export function hideAllModals() {
|
|||
export function hasToken() {
|
||||
return window.isOffline !== true && getCookie('token') !== '';
|
||||
}
|
||||
|
||||
export function objectValuesAreDifferent(newObject, oldObject) {
|
||||
let valuesAreDifferent = false;
|
||||
for (let property in newObject) {
|
||||
if (!oldObject.hasOwnProperty(property) || JSON.stringify(newObject[property]) !== JSON.stringify(oldObject[property])) {
|
||||
valuesAreDifferent = true;
|
||||
}
|
||||
if (typeof newObject[property] === 'object' && !Array.isArray(newObject[property])) {
|
||||
valuesAreDifferent = objectValuesAreDifferent(newObject[property], oldObject[property]);
|
||||
}
|
||||
|
||||
if (valuesAreDifferent) break;
|
||||
}
|
||||
|
||||
return valuesAreDifferent;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { renderAll } from './render';
|
||||
import setupListeners from './setupListeners';
|
||||
import { setupAds } from '../ads';
|
||||
|
||||
function initialize() {
|
||||
setupAds();
|
||||
renderAll();
|
||||
setupListeners();
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import md from 'marked';
|
||||
import { removeTags, slugify } from '../../helpers';
|
||||
import { getWordsStats, getHomonymnNumber } from './utilities';
|
||||
import { getHomonymnNumber } from './utilities';
|
||||
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from './search';
|
||||
import { showSection } from './displayToggles';
|
||||
import { setupSearchFilters, setupInfoModal } from './setupListeners';
|
||||
import { parseReferences } from './wordManagement';
|
||||
import { renderAd } from '../ads';
|
||||
import { sortWords } from './wordManagement';
|
||||
import { setupInfoModal } from '../setupListeners/modals';
|
||||
import { setupSearchFilters } from '../setupListeners/search';
|
||||
|
||||
export function renderAll() {
|
||||
renderTheme();
|
||||
renderCustomCSS();
|
||||
renderDictionaryDetails();
|
||||
renderPartsOfSpeech();
|
||||
sortWords();
|
||||
renderWords();
|
||||
}
|
||||
|
||||
|
@ -21,15 +19,23 @@ export function renderTheme() {
|
|||
document.body.id = theme + 'Theme';
|
||||
}
|
||||
|
||||
export function renderCustomCSS() {
|
||||
const { customCSS } = window.currentDictionary.settings;
|
||||
const stylingId = 'customCSS';
|
||||
const stylingElement = document.getElementById(stylingId);
|
||||
if (!stylingElement) {
|
||||
const styling = document.createElement('style');
|
||||
styling.id = stylingId;
|
||||
styling.innerHTML = customCSS;
|
||||
document.body.appendChild(styling);
|
||||
} else {
|
||||
stylingElement.innerHTML = customCSS;
|
||||
}
|
||||
}
|
||||
|
||||
export function renderDictionaryDetails() {
|
||||
renderName();
|
||||
|
||||
const tabs = document.querySelectorAll('#detailsSection nav li');
|
||||
const shownTab = Array.from(tabs).find(tab => tab.classList.contains('active'));
|
||||
if (shownTab) {
|
||||
const tabName = shownTab.innerText.toLowerCase();
|
||||
showSection(tabName);
|
||||
}
|
||||
showSection('description');
|
||||
}
|
||||
|
||||
export function renderName() {
|
||||
|
@ -41,52 +47,74 @@ export function renderName() {
|
|||
}
|
||||
|
||||
export function renderDescription() {
|
||||
const descriptionHTML = md(removeTags(window.currentDictionary.description));
|
||||
const descriptionHTML = md(window.currentDictionary.description);
|
||||
|
||||
document.getElementById('detailsPanel').innerHTML = '<div class="content">' + descriptionHTML + '</div>';
|
||||
}
|
||||
|
||||
export function renderDetails() {
|
||||
const { partsOfSpeech, alphabeticalOrder } = window.currentDictionary;
|
||||
const { phonology, orthography, grammar } = window.currentDictionary.details;
|
||||
const partsOfSpeechHTML = `<p><strong>Parts of Speech:</strong> ${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</p>`;
|
||||
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order:</strong> ${
|
||||
const { phonology, phonotactics, orthography, grammar } = window.currentDictionary.details;
|
||||
const partsOfSpeechHTML = `<p><strong>Parts of Speech</strong><br>${partsOfSpeech.map(partOfSpeech => '<span class="tag">' + partOfSpeech + '</span>').join(' ')}</div>`;
|
||||
const alphabeticalOrderHTML = `<p><strong>Alphabetical Order</strong><br>${
|
||||
(alphabeticalOrder.length > 0 ? alphabeticalOrder : ['English Alphabet']).map(letter => `<span class="tag">${letter}</span>`).join(' ')
|
||||
}</p>`;
|
||||
}</div>`;
|
||||
const generalHTML = `<h3>General</h3>${partsOfSpeechHTML}${alphabeticalOrderHTML}`;
|
||||
|
||||
const { consonants, vowels, blends, phonotactics } = phonology
|
||||
const consonantHTML = `<p><strong>Consonants:</strong> ${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const vowelHTML = `<p><strong>Vowels:</strong> ${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs / Blends:</strong> ${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
|
||||
const { consonants, vowels, blends } = phonology
|
||||
const consonantHTML = `<p><strong>Consonants</strong><br>${consonants.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const vowelHTML = `<p><strong>Vowels</strong><br>${vowels.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const blendHTML = blends.length > 0 ? `<p><strong>Polyphthongs / Blends</strong><br>${blends.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>` : '';
|
||||
const phonologyNotesHTML = phonology.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(phonology.notes) + '</div>' : '';
|
||||
const phonologyHTML = `<h3>Phonology</h3>
|
||||
<div class="split two">
|
||||
<div>${consonantHTML}</div>
|
||||
<div>${vowelHTML}</div>
|
||||
</div>
|
||||
${blendHTML}`;
|
||||
${blendHTML}
|
||||
${phonologyNotesHTML}`;
|
||||
|
||||
const { onset, nucleus, coda, exceptions } = phonotactics;
|
||||
const onsetHTML = `<p><strong>Onset:</strong> ${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const nucleusHTML = `<p><strong>Nucleus:</strong> ${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const codaHTML = `<p><strong>Coda:</strong> ${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const exceptionsHTML = exceptions.trim().length > 0 ? '<p><strong>Exceptions:</strong></p><div>' + md(removeTags(exceptions)) + '</div>' : '';
|
||||
const phonotacticsHTML = `<h3>Phonotactics</h3>
|
||||
<div class="split three">
|
||||
<div>${onsetHTML}</div>
|
||||
<div>${nucleusHTML}</div>
|
||||
<div>${codaHTML}</div>
|
||||
</div>
|
||||
${exceptionsHTML}`;
|
||||
const { onset, nucleus, coda } = phonotactics;
|
||||
const onsetHTML = `<p><strong>Onset</strong><br>${onset.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const nucleusHTML = `<p><strong>Nucleus</strong><br>${nucleus.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const codaHTML = `<p><strong>Coda</strong><br>${coda.map(letter => `<span class="tag">${letter}</span>`).join(' ')}</p>`;
|
||||
const phonotacticsNotesHTML = phonotactics.notes.trim().length > 0 ? '<p><strong>Notes</strong></p><div>' + md(phonotactics.notes) + '</div>' : '';
|
||||
const phonotacticsHTML = onset.length + nucleus.length + coda.length + phonotacticsNotesHTML.length > 0
|
||||
? `<h3>Phonotactics</h3>
|
||||
${onset.length > 0 || nucleus.length > 0 || coda.length > 0
|
||||
? `<div class="split three">
|
||||
<div>${onsetHTML}</div>
|
||||
<div>${nucleusHTML}</div>
|
||||
<div>${codaHTML}</div>
|
||||
</div>` : ''}
|
||||
${phonotacticsNotesHTML}`
|
||||
: '';
|
||||
|
||||
const orthographyHTML = '<h3>Orthography</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(orthography.notes)) + '</div>';
|
||||
const grammarHTML = '<h3>Grammar</h3><p><strong>Notes:</strong></p><div>' + md(removeTags(grammar.notes)) + '</div>';
|
||||
const { translations } = orthography;
|
||||
const translationsHTML = translations.length > 0 ? `<p><strong>Translations</strong><br>${translations.map(translation => {
|
||||
translation = translation.split('=').map(value => value.trim());
|
||||
if (translation.length > 1 && translation[0] !== '' && translation[1] !== '') {
|
||||
return `<span><span class="tag">${translation[0]}</span><span class="tag orthographic-translation">${translation[1]}</span></span>`;
|
||||
}
|
||||
return false;
|
||||
}).filter(html => html !== false).join(' ')}</p>` : '';
|
||||
const orthographyNotesHTML = orthography.notes.trim().length > 0 ? '<p><strong>Notes</strong><br>' + md(orthography.notes) + '</div>' : '';
|
||||
const orthographyHTML = translations.length + orthographyNotesHTML.length > 0
|
||||
? `<h3>Orthography</h3>
|
||||
${translationsHTML}
|
||||
${orthographyNotesHTML}`
|
||||
: '';
|
||||
const grammarHTML = grammar.notes.trim().length > 0
|
||||
? '<h3>Grammar</h3><div>'
|
||||
+ (grammar.notes.trim().length > 0 ? md(grammar.notes) : '')
|
||||
+ '</div>'
|
||||
: '';
|
||||
|
||||
detailsPanel.innerHTML = generalHTML + phonologyHTML + phonotacticsHTML + orthographyHTML + grammarHTML;
|
||||
}
|
||||
|
||||
export function renderStats() {
|
||||
const wordStats = getWordsStats();
|
||||
const { wordStats } = window.currentDictionary;
|
||||
const numberOfWordsHTML = `<p><strong>Number of Words</strong><br>${wordStats.numberOfWords.map(stat => `<span><span class="tag">${stat.name}</span><span class="tag">${stat.value}</span></span>`).join(' ')}</p>`;
|
||||
const wordLengthHTML = `<p><strong>Word Length</strong><br><span><span class="tag">Shortest</span><span class="tag">${wordStats.wordLength.shortest}</span></span>
|
||||
<span><span class="tag">Longest</span><span class="tag">${wordStats.wordLength.longest}</span></span>
|
||||
|
@ -102,7 +130,7 @@ export function renderPartsOfSpeech(onlyOptions = false) {
|
|||
searchHTML = '<label>Unclassified <input type="checkbox" checked id="searchPartOfSpeech__None"></label>';
|
||||
window.currentDictionary.partsOfSpeech.forEach(partOfSpeech => {
|
||||
partOfSpeech = removeTags(partOfSpeech);
|
||||
optionsHTML += `<option value="${partOfSpeech}">${partOfSpeech}</option>`;
|
||||
optionsHTML += `<option value="${partOfSpeech.replace(/"/g, '"')}">${partOfSpeech}</option>`;
|
||||
searchHTML += `<label>${partOfSpeech} <input type="checkbox" checked id="searchPartOfSpeech_${slugify(partOfSpeech)}"></label>`;
|
||||
});
|
||||
searchHTML += `<a class="small button" id="checkAllFilters">Check All</a> <a class="small button" id="uncheckAllFilters">Uncheck All</a>`;
|
||||
|
@ -146,24 +174,29 @@ export function renderWords() {
|
|||
</article>`;
|
||||
}
|
||||
|
||||
words.forEach((originalWord, displayIndex) => {
|
||||
words.forEach(originalWord => {
|
||||
const word = highlightSearchTerm({
|
||||
name: removeTags(originalWord.name),
|
||||
pronunciation: removeTags(originalWord.pronunciation),
|
||||
partOfSpeech: removeTags(originalWord.partOfSpeech),
|
||||
definition: removeTags(originalWord.definition),
|
||||
details: parseReferences(removeTags(originalWord.details)),
|
||||
details: originalWord.details,
|
||||
etymology: typeof originalWord.etymology === 'undefined' || originalWord.etymology.length < 1 ? null
|
||||
: originalWord.etymology.join(', '),
|
||||
related: typeof originalWord.related === 'undefined' || originalWord.related.length < 1 ? null
|
||||
: originalWord.related.join(', '),
|
||||
principalParts: typeof originalWord.principalParts === 'undefined' || originalWord.principalParts.length < 1 ? null
|
||||
: originalWord.principalParts.join(', '),
|
||||
wordId: originalWord.wordId,
|
||||
});
|
||||
|
||||
const homonymnNumber = getHomonymnNumber(originalWord);
|
||||
const shareLink = window.location.pathname + (window.location.pathname.match(new RegExp(word.wordId + '$')) ? '' : '/' + word.wordId);
|
||||
|
||||
wordsHTML += renderAd(displayIndex);
|
||||
|
||||
wordsHTML += `<article class="entry" id="${word.wordId}">
|
||||
<header>
|
||||
<h4 class="word">${word.name}${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
|
||||
<h4 class="word"><span class="orthographic-translation">${word.name}</span>${homonymnNumber > 0 ? ' <sub>' + homonymnNumber.toString() + '</sub>' : ''}</h4>
|
||||
${word.principalParts === null ? '' : `<span class="principalParts">(${word.principalParts})</span>`}
|
||||
<span class="pronunciation">${word.pronunciation}</span>
|
||||
<span class="part-of-speech">${word.partOfSpeech}</span>
|
||||
<a href="${shareLink}" target="_blank" class="small button word-option-button" title="Link to Word">➦</a>
|
||||
|
@ -173,6 +206,15 @@ export function renderWords() {
|
|||
<dd class="details">
|
||||
${md(word.details)}
|
||||
</dd>
|
||||
${word.etymology === null && word.related === null ? '' : `<hr>`}
|
||||
${word.etymology === null ? '' : `<dt>Etymology <small>(Root Word${originalWord.etymology.length !== 1 ? 's' : ''})</small></dt>
|
||||
<dd class="etymology">
|
||||
${md(word.etymology).replace(/<\/?p>/g, '')}
|
||||
</dd>`}
|
||||
${word.related === null ? '' : `<dt>Related Word${originalWord.related.length !== 1 ? 's' : ''}</dt>
|
||||
<dd class="related">
|
||||
${md(word.related).replace(/<\/?p>/g, '')}
|
||||
</dd>`}
|
||||
</dl>
|
||||
</article>`;
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ export function getSearchFilters() {
|
|||
caseSensitive: document.getElementById('searchCaseSensitive').checked,
|
||||
ignoreDiacritics: document.getElementById('searchIgnoreDiacritics').checked,
|
||||
exact: document.getElementById('searchExactWords').checked,
|
||||
// orthography is removed my default because it is already rendered on the backend.
|
||||
name: document.getElementById('searchIncludeName').checked,
|
||||
definition: document.getElementById('searchIncludeDefinition').checked,
|
||||
details: document.getElementById('searchIncludeDetails').checked,
|
||||
|
@ -59,15 +60,24 @@ export function getMatchingSearchWords() {
|
|||
definition = filters.caseSensitive ? definition : definition.toLowerCase();
|
||||
let details = filters.ignoreDiacritics ? removeDiacritics(word.details) : word.details;
|
||||
details = filters.caseSensitive ? details : details.toLowerCase();
|
||||
let principalParts = typeof word.principalParts === 'undefined' ? [] : word.principalParts;
|
||||
principalParts = filters.ignoreDiacritics ? principalParts.map(part => removeDiacritics(part)) : principalParts;
|
||||
principalParts = filters.caseSensitive ? principalParts : principalParts.map(part => part.toLowerCase());
|
||||
|
||||
const isInName = filters.name && (filters.exact
|
||||
? searchTerm == name
|
||||
: new RegExp(searchTerm, 'g').test(name));
|
||||
? searchTerm == name
|
||||
: new RegExp(searchTerm, 'g').test(name)
|
||||
);
|
||||
const isInDefinition = filters.definition && (filters.exact
|
||||
? searchTerm == definition
|
||||
: new RegExp(searchTerm, 'g').test(definition));
|
||||
? searchTerm == definition
|
||||
: new RegExp(searchTerm, 'g').test(definition)
|
||||
);
|
||||
const isInDetails = filters.details && new RegExp(searchTerm, 'g').test(details);
|
||||
return searchTerm === '' || isInName || isInDefinition || isInDetails;
|
||||
const isInPrincipalParts = filters.name && (filters.exact
|
||||
? principalParts.includes(searchTerm)
|
||||
: principalParts.some(part => new RegExp(searchTerm, 'g').test(part))
|
||||
);
|
||||
return searchTerm === '' || isInName || isInDefinition || isInDetails || isInPrincipalParts;
|
||||
});
|
||||
return matchingWords;
|
||||
}
|
||||
|
@ -91,6 +101,16 @@ export function highlightSearchTerm(word) {
|
|||
+ '<mark>' + markedUpWord.name.substr(wordIndex, searchTermLength) + '</mark>'
|
||||
+ markedUpWord.name.substr(wordIndex + searchTermLength);
|
||||
});
|
||||
|
||||
if (markedUpWord.principalParts !== null) {
|
||||
const part = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.principalParts), filters.caseSensitive);
|
||||
part.forEach((wordIndex, i) => {
|
||||
wordIndex += '<mark></mark>'.length * i;
|
||||
markedUpWord.principalParts = markedUpWord.principalParts.substring(0, wordIndex)
|
||||
+ '<mark>' + markedUpWord.principalParts.substr(wordIndex, searchTermLength) + '</mark>'
|
||||
+ markedUpWord.principalParts.substr(wordIndex + searchTermLength);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (filters.definition) {
|
||||
const definitionMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.definition), filters.caseSensitive);
|
||||
|
@ -114,6 +134,10 @@ export function highlightSearchTerm(word) {
|
|||
const regexMethod = 'g' + (filters.caseSensitive ? '' : 'i');
|
||||
if (filters.name) {
|
||||
markedUpWord.name = markedUpWord.name.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
|
||||
|
||||
if (markedUpWord.principalParts !== null) {
|
||||
markedUpWord.principalParts = markedUpWord.principalParts.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
|
||||
}
|
||||
}
|
||||
if (filters.definition) {
|
||||
markedUpWord.definition = markedUpWord.definition.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
|
||||
|
|
|
@ -1,93 +1,3 @@
|
|||
export function getWordsStats() {
|
||||
const {words, partsOfSpeech} = window.currentDictionary;
|
||||
const {caseSensitive} = window.currentDictionary.settings;
|
||||
|
||||
const wordStats = {
|
||||
numberOfWords: [
|
||||
{
|
||||
name: 'Total',
|
||||
value: words.length,
|
||||
},
|
||||
],
|
||||
wordLength: {
|
||||
shortest: 0,
|
||||
longest: 0,
|
||||
average: 0,
|
||||
},
|
||||
letterDistribution: [
|
||||
/* {
|
||||
letter: '',
|
||||
number: 0,
|
||||
percentage: 0.00,
|
||||
} */
|
||||
],
|
||||
totalLetters: 0,
|
||||
};
|
||||
|
||||
partsOfSpeech.forEach(partOfSpeech => {
|
||||
const wordsWithPartOfSpeech = words.filter(word => word.partOfSpeech === partOfSpeech);
|
||||
wordStats.numberOfWords.push({
|
||||
name: partOfSpeech,
|
||||
value: wordsWithPartOfSpeech.length,
|
||||
});
|
||||
});
|
||||
|
||||
wordStats.numberOfWords.push({
|
||||
name: 'Unclassified',
|
||||
value: words.filter(word => !partsOfSpeech.includes(word.partOfSpeech)).length,
|
||||
});
|
||||
|
||||
let totalLetters = 0;
|
||||
const numberOfLetters = {};
|
||||
|
||||
words.forEach(word => {
|
||||
const shortestWord = wordStats.wordLength.shortest;
|
||||
const longestWord = wordStats.wordLength.longest;
|
||||
const wordLetters = word.name.split('');
|
||||
const lettersInWord = wordLetters.length;
|
||||
|
||||
totalLetters += lettersInWord;
|
||||
|
||||
if (shortestWord === 0 || lettersInWord < shortestWord) {
|
||||
wordStats.wordLength.shortest = lettersInWord;
|
||||
}
|
||||
|
||||
if (longestWord === 0 || lettersInWord > longestWord) {
|
||||
wordStats.wordLength.longest = lettersInWord;
|
||||
}
|
||||
|
||||
wordLetters.forEach(letter => {
|
||||
const letterToUse = caseSensitive ? letter : letter.toLowerCase();
|
||||
if (!numberOfLetters.hasOwnProperty(letterToUse)) {
|
||||
numberOfLetters[letterToUse] = 1;
|
||||
} else {
|
||||
numberOfLetters[letterToUse]++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
wordStats.totalLetters = totalLetters;
|
||||
wordStats.wordLength.average = words.length > 0 ? Math.round(totalLetters / words.length) : 0;
|
||||
|
||||
for (const letter in numberOfLetters) {
|
||||
if (numberOfLetters.hasOwnProperty(letter)) {
|
||||
const number = numberOfLetters[letter];
|
||||
wordStats.letterDistribution.push({
|
||||
letter,
|
||||
number,
|
||||
percentage: number / totalLetters,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
wordStats.letterDistribution.sort((a, b) => {
|
||||
if (a.percentage === b.percentage) return 0;
|
||||
return (a.percentage > b.percentage) ? -1 : 1;
|
||||
});
|
||||
|
||||
return wordStats;
|
||||
}
|
||||
|
||||
export function getHomonymnIndexes(word) {
|
||||
const { currentDictionary } = window;
|
||||
const { caseSensitive } = currentDictionary.settings;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { wordExists, getHomonymnIndexes } from "./utilities";
|
||||
import removeDiacritics from "../StackOverflow/removeDiacritics";
|
||||
|
||||
export function sortWords() {
|
||||
|
@ -10,46 +9,3 @@ export function sortWords() {
|
|||
return removeDiacritics(wordA[sortBy]).toLowerCase() > removeDiacritics(wordB[sortBy]).toLowerCase() ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
export function parseReferences(detailsMarkdown) {
|
||||
const references = detailsMarkdown.match(/\{\{.+?\}\}/g);
|
||||
if (references && Array.isArray(references)) {
|
||||
new Set(references).forEach(reference => {
|
||||
let wordToFind = reference.replace(/\{\{|\}\}/g, '');
|
||||
let homonymn = 0;
|
||||
|
||||
if (wordToFind.includes(':')) {
|
||||
const separator = wordToFind.indexOf(':');
|
||||
homonymn = wordToFind.substr(separator + 1);
|
||||
wordToFind = wordToFind.substring(0, separator);
|
||||
if (homonymn && homonymn.trim()
|
||||
&& !isNaN(parseInt(homonymn.trim())) && parseInt(homonymn.trim()) > 0) {
|
||||
homonymn = parseInt(homonymn.trim());
|
||||
} else {
|
||||
homonymn = false;
|
||||
}
|
||||
}
|
||||
|
||||
let existingWordId = false;
|
||||
const homonymnIndexes = getHomonymnIndexes({ name: wordToFind, wordId: -1 });
|
||||
|
||||
if (homonymn !== false && homonymn > 0) {
|
||||
if (typeof homonymnIndexes[homonymn - 1] !== 'undefined') {
|
||||
existingWordId = window.currentDictionary.words[homonymnIndexes[homonymn - 1]].wordId;
|
||||
}
|
||||
} else if (homonymn !== false) {
|
||||
existingWordId = wordExists(wordToFind, true);
|
||||
}
|
||||
|
||||
if (existingWordId !== false) {
|
||||
if (homonymn < 1 && homonymnIndexes.length > 0) {
|
||||
homonymn = 1;
|
||||
}
|
||||
const homonymnSubHTML = homonymn > 0 ? '<sub>' + homonymn.toString() + '</sub>' : '';
|
||||
const wordMarkdownLink = `[${wordToFind}${homonymnSubHTML}](#${existingWordId})`;
|
||||
detailsMarkdown = detailsMarkdown.replace(new RegExp(reference, 'g'), wordMarkdownLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
return detailsMarkdown;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { renderWords } from "./render";
|
||||
import { renderWords, renderWord } from "./render/words";
|
||||
import { wordExists, addMessage, getNextId, hasToken, getHomonymnIndexes } from "./utilities";
|
||||
import removeDiacritics from "./StackOverflow/removeDiacritics";
|
||||
import { removeTags, getTimestampInSeconds } from "../helpers";
|
||||
import { saveDictionary } from "./dictionaryManagement";
|
||||
import { setupWordOptionButtons, setupWordOptionSelections } from "./setupListeners/words";
|
||||
import { wordMatchesSearch } from "./search";
|
||||
|
||||
export function validateWord(word, wordId = false) {
|
||||
const errorElementId = wordId === false ? 'wordErrorMessage' : 'wordErrorMessage_' + wordId,
|
||||
|
@ -32,12 +34,68 @@ export function validateWord(word, wordId = false) {
|
|||
|
||||
export function sortWords(render) {
|
||||
const { sortByDefinition } = window.currentDictionary.settings;
|
||||
const { alphabeticalOrder } = window.currentDictionary;
|
||||
const sortBy = sortByDefinition ? 'definition' : 'name';
|
||||
|
||||
window.currentDictionary.words.sort((wordA, wordB) => {
|
||||
if (removeDiacritics(wordA[sortBy]).toLowerCase() === removeDiacritics(wordB[sortBy]).toLowerCase()) return 0;
|
||||
return removeDiacritics(wordA[sortBy]).toLowerCase() > removeDiacritics(wordB[sortBy]).toLowerCase() ? 1 : -1;
|
||||
// Sort normally first
|
||||
return wordA[sortBy].localeCompare(wordB[sortBy], 'en', { sensitivity: 'base' }); // This is the smart way to do the below!
|
||||
// if (removeDiacritics(wordA[sortBy]).toLowerCase() === removeDiacritics(wordB[sortBy]).toLowerCase()) return 0;
|
||||
// return removeDiacritics(wordA[sortBy]).toLowerCase() > removeDiacritics(wordB[sortBy]).toLowerCase() ? 1 : -1;
|
||||
});
|
||||
|
||||
if (alphabeticalOrder.length > 0) {
|
||||
// If there's an alphabetical order specified, sort by that after! Any letters not in the alphabet will be unsorted, keeping them in ASCII order
|
||||
const ordering = {}; // map for efficient lookup of sortIndex
|
||||
for (let i = 0; i < alphabeticalOrder.length; i++) {
|
||||
ordering[alphabeticalOrder[i]] = i + 1; // Add 1 to prevent 0 from resolving to false
|
||||
}
|
||||
|
||||
window.currentDictionary.words.sort((wordA, wordB) => {
|
||||
// console.log(alphabeticalOrder, ordering);
|
||||
// console.log('comparing:', wordA[sortBy], wordB[sortBy]);
|
||||
if (wordA[sortBy] === wordB[sortBy]) return 0;
|
||||
|
||||
const aLetters = wordA[sortBy].split('');
|
||||
const bLetters = wordB[sortBy].split('');
|
||||
|
||||
for (let i = 0; i < aLetters.length; i++) {
|
||||
const a = aLetters[i];
|
||||
const b = bLetters[i];
|
||||
// console.log('comparing letters', a, b);
|
||||
if (!b) {
|
||||
// console.log('no b, ', wordA[sortBy], 'is longer than', wordB[sortBy]);
|
||||
return 1;
|
||||
}
|
||||
if (!ordering[a] && !ordering[b]) {
|
||||
// console.log('a and b not in dictionary:', a, b, 'continuing to the next letter');
|
||||
continue;
|
||||
}
|
||||
if (!ordering[a]) {
|
||||
// console.log('a is not in dictionary:', a, 'moving back:', wordA[sortBy]);
|
||||
return 1;
|
||||
}
|
||||
if (!ordering[b]) {
|
||||
// console.log('b is not in dictionary:', b, 'moving forward:', wordA[sortBy]);
|
||||
return -1;
|
||||
}
|
||||
if (ordering[a] === ordering[b]) {
|
||||
// console.log('letters are the same order:', a, b);
|
||||
if (aLetters.length < bLetters.length && i === aLetters.length - 1) {
|
||||
// console.log(a, 'is shorter than', b);
|
||||
return -1;
|
||||
}
|
||||
// console.log(a, 'is the same as', b, 'continuing to the next letter');
|
||||
continue;
|
||||
}
|
||||
// console.log('comparing order:', a, b, 'result:', ordering[a] - ordering[b]);
|
||||
return ordering[a] - ordering[b];
|
||||
}
|
||||
|
||||
// console.log('all of the letters were dumb, no sort');
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
saveDictionary(false);
|
||||
|
||||
|
@ -46,42 +104,22 @@ export function sortWords(render) {
|
|||
}
|
||||
}
|
||||
|
||||
export function translateOrthography(word) {
|
||||
window.currentDictionary.details.orthography.translations.forEach(translation => {
|
||||
translation = translation.split('=').map(value => value.trim());
|
||||
if (translation.length > 1 && translation[0] !== '' && translation[1] !== '') {
|
||||
word = word.replace(new RegExp(translation[0], 'g'), translation[1]);
|
||||
}
|
||||
});
|
||||
return word;
|
||||
}
|
||||
|
||||
export function parseReferences(detailsMarkdown) {
|
||||
const references = detailsMarkdown.match(/\{\{.+?\}\}/g);
|
||||
if (references && Array.isArray(references)) {
|
||||
new Set(references).forEach(reference => {
|
||||
let wordToFind = reference.replace(/\{\{|\}\}/g, '');
|
||||
let homonymn = 0;
|
||||
|
||||
if (wordToFind.includes(':')) {
|
||||
const separator = wordToFind.indexOf(':');
|
||||
homonymn = wordToFind.substr(separator + 1);
|
||||
wordToFind = wordToFind.substring(0, separator);
|
||||
if (homonymn && homonymn.trim()
|
||||
&& !isNaN(parseInt(homonymn.trim())) && parseInt(homonymn.trim()) > 0) {
|
||||
homonymn = parseInt(homonymn.trim());
|
||||
} else {
|
||||
homonymn = false;
|
||||
}
|
||||
}
|
||||
|
||||
let existingWordId = false;
|
||||
const homonymnIndexes = getHomonymnIndexes({ name: wordToFind, wordId: -1 });
|
||||
|
||||
if (homonymn !== false && homonymn > 0) {
|
||||
if (typeof homonymnIndexes[homonymn - 1] !== 'undefined') {
|
||||
existingWordId = window.currentDictionary.words[homonymnIndexes[homonymn - 1]].wordId;
|
||||
}
|
||||
} else if (homonymn !== false) {
|
||||
existingWordId = wordExists(wordToFind, true);
|
||||
}
|
||||
|
||||
if (existingWordId !== false) {
|
||||
if (homonymn < 1 && homonymnIndexes.length > 0) {
|
||||
homonymn = 1;
|
||||
}
|
||||
const homonymnSubHTML = homonymn > 0 ? '<sub>' + homonymn.toString() + '</sub>' : '';
|
||||
const wordMarkdownLink = `[${wordToFind}${homonymnSubHTML}](#${existingWordId})`;
|
||||
const wordMarkdownLink = getWordReferenceMarkdown(reference);
|
||||
if (wordMarkdownLink !== reference) {
|
||||
detailsMarkdown = detailsMarkdown.replace(new RegExp(reference, 'g'), wordMarkdownLink);
|
||||
}
|
||||
});
|
||||
|
@ -89,12 +127,66 @@ export function parseReferences(detailsMarkdown) {
|
|||
return detailsMarkdown;
|
||||
}
|
||||
|
||||
export function getWordReferenceMarkdown(reference) {
|
||||
let wordToFind = reference.replace(/\{\{|\}\}/g, '');
|
||||
let homonymn = 0;
|
||||
|
||||
if (wordToFind.includes(':')) {
|
||||
const separator = wordToFind.indexOf(':');
|
||||
homonymn = wordToFind.substr(separator + 1);
|
||||
wordToFind = wordToFind.substring(0, separator);
|
||||
if (homonymn && homonymn.trim()
|
||||
&& !isNaN(parseInt(homonymn.trim())) && parseInt(homonymn.trim()) > 0) {
|
||||
homonymn = parseInt(homonymn.trim());
|
||||
} else {
|
||||
homonymn = false;
|
||||
}
|
||||
}
|
||||
|
||||
let existingWordId = false;
|
||||
const homonymnIndexes = getHomonymnIndexes({ name: wordToFind, wordId: -1 });
|
||||
|
||||
if (homonymn !== false && homonymn > 0) {
|
||||
if (typeof homonymnIndexes[homonymn - 1] !== 'undefined') {
|
||||
existingWordId = window.currentDictionary.words[homonymnIndexes[homonymn - 1]].wordId;
|
||||
}
|
||||
} else if (homonymn !== false) {
|
||||
existingWordId = wordExists(wordToFind, true);
|
||||
}
|
||||
|
||||
if (existingWordId !== false) {
|
||||
if (homonymn < 1 && homonymnIndexes.length > 0) {
|
||||
homonymn = 1;
|
||||
}
|
||||
const homonymnSubHTML = homonymnIndexes.length > 1 && homonymn - 1 >= 0 ? '<sub>' + homonymn.toString() + '</sub>' : '';
|
||||
return `<span class="word-reference">[<span class="orthographic-translation">${translateOrthography(wordToFind)}</span>${homonymnSubHTML}](#${existingWordId})</span>`;
|
||||
}
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
export function expandAdvancedForm(id = false) {
|
||||
const wordId = typeof id.target !== 'undefined' ? this.id.replace('expandAdvancedForm', '') : id;
|
||||
const button = typeof id.target !== 'undefined' ? this : document.getElementById('expandAdvancedForm' + (!wordId ? '' : wordId)),
|
||||
form = document.getElementById('advancedForm' + (!wordId ? '' : wordId));
|
||||
if (form.style.display !== 'block') {
|
||||
button.innerText = 'Hide Advanced Fields';
|
||||
form.style.display = 'block';
|
||||
} else {
|
||||
button.innerText = 'Show Advanced Fields';
|
||||
form.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
export function submitWordForm() {
|
||||
const name = document.getElementById('wordName').value,
|
||||
pronunciation = document.getElementById('wordPronunciation').value,
|
||||
partOfSpeech = document.getElementById('wordPartOfSpeech').value,
|
||||
definition = document.getElementById('wordDefinition').value,
|
||||
details = document.getElementById('wordDetails').value;
|
||||
details = document.getElementById('wordDetails').value,
|
||||
etymology = document.getElementById('wordEtymology').value,
|
||||
related = document.getElementById('wordRelated').value,
|
||||
principalParts = document.getElementById('wordPrincipalParts').value;
|
||||
|
||||
const word = {
|
||||
name: removeTags(name).trim(),
|
||||
|
@ -105,8 +197,28 @@ export function submitWordForm() {
|
|||
wordId: getNextId(),
|
||||
};
|
||||
|
||||
if (removeTags(etymology).trim() !== '') {
|
||||
word.etymology = removeTags(etymology).split(',').map(w => w.trim()).filter(w => w.length > 0);
|
||||
}
|
||||
|
||||
if (removeTags(related).trim() !== '') {
|
||||
word.related = removeTags(related).split(',').map(w => w.trim()).filter(w => w.length > 0);
|
||||
}
|
||||
|
||||
if (removeTags(principalParts).trim() !== '') {
|
||||
word.principalParts = removeTags(principalParts).split(',').map(w => w.trim()).filter(w => w.length > 0);
|
||||
}
|
||||
|
||||
if (validateWord(word)) {
|
||||
addWord(word);
|
||||
sortWords(true);
|
||||
|
||||
if (hasToken()) {
|
||||
import('./account/index.js').then(account => {
|
||||
account.uploadWord(word);
|
||||
});
|
||||
}
|
||||
|
||||
clearWordForm();
|
||||
}
|
||||
}
|
||||
|
@ -118,10 +230,15 @@ export function clearWordForm() {
|
|||
document.getElementById('wordDefinition').value = '';
|
||||
document.getElementById('wordDetails').value = '';
|
||||
|
||||
document.getElementById('templateSelect').value = '';
|
||||
document.getElementById('wordEtymology').value = '';
|
||||
document.getElementById('wordRelated').value = '';
|
||||
document.getElementById('wordPrincipalParts').value = '';
|
||||
|
||||
document.getElementById('wordName').focus();
|
||||
}
|
||||
|
||||
export function addWord(word, render = true, message = true, upload = true) {
|
||||
export function addWord(word, message = true) {
|
||||
const timestamp = getTimestampInSeconds();
|
||||
word.lastUpdated = timestamp;
|
||||
word.createdOn = timestamp;
|
||||
|
@ -129,13 +246,6 @@ export function addWord(word, render = true, message = true, upload = true) {
|
|||
if (message) {
|
||||
addMessage(`<a href="#${word.wordId}">${word.name}</a> Created Successfully`, 30000);
|
||||
}
|
||||
sortWords(render);
|
||||
|
||||
if (upload && hasToken()) {
|
||||
import('./account/index.js').then(account => {
|
||||
account.uploadWord(word);
|
||||
});
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
@ -165,11 +275,30 @@ export function updateWord(word, wordId) {
|
|||
console.error('Could not find word to update');
|
||||
addMessage('Could not find word to update. Please refresh your browser and try again.', 10000, 'error');
|
||||
} else {
|
||||
const isPublic = hasToken() && window.currentDictionary.settings.isPublic;
|
||||
const { sortByDefinition } = window.currentDictionary.settings;
|
||||
const existingWord = window.currentDictionary.words[wordIndex];
|
||||
const needsReRender = (sortByDefinition && word.definition !== existingWord.definition)
|
||||
|| (!sortByDefinition && word.name !== existingWord.name);
|
||||
word.lastUpdated = getTimestampInSeconds();
|
||||
word.createdOn = window.currentDictionary.words[wordIndex].createdOn;
|
||||
word.createdOn = existingWord.createdOn;
|
||||
window.currentDictionary.words[wordIndex] = word;
|
||||
addMessage('Word Updated Successfully');
|
||||
sortWords(true);
|
||||
|
||||
if (needsReRender) {
|
||||
sortWords(true);
|
||||
} else {
|
||||
saveDictionary(false);
|
||||
const entry = document.getElementById(wordId.toString());
|
||||
if (!wordMatchesSearch(word)) {
|
||||
entry.parentElement.removeChild(entry);
|
||||
} else {
|
||||
console.log('matches search, updating in place');
|
||||
document.getElementById(wordId.toString()).outerHTML = renderWord(window.currentDictionary.words[wordIndex], isPublic);
|
||||
setupWordOptionButtons();
|
||||
setupWordOptionSelections();
|
||||
}
|
||||
}
|
||||
|
||||
if (hasToken()) {
|
||||
import('./account/index.js').then(account => {
|
||||
|
@ -185,7 +314,10 @@ export function confirmEditWord(id) {
|
|||
pronunciation = document.getElementById('wordPronunciation_' + wordId).value,
|
||||
partOfSpeech = document.getElementById('wordPartOfSpeech_' + wordId).value,
|
||||
definition = document.getElementById('wordDefinition_' + wordId).value,
|
||||
details = document.getElementById('wordDetails_' + wordId).value;
|
||||
details = document.getElementById('wordDetails_' + wordId).value,
|
||||
etymology = document.getElementById('wordEtymology_' + wordId).value,
|
||||
related = document.getElementById('wordRelated_' + wordId).value,
|
||||
principalParts = document.getElementById('wordPrincipalParts_' + wordId).value;
|
||||
|
||||
const word = {
|
||||
name: removeTags(name).trim(),
|
||||
|
@ -196,6 +328,18 @@ export function confirmEditWord(id) {
|
|||
wordId,
|
||||
};
|
||||
|
||||
if (removeTags(etymology).trim() !== '') {
|
||||
word.etymology = removeTags(etymology).split(',').map(w => w.trim()).filter(w => w.length > 0);
|
||||
}
|
||||
|
||||
if (removeTags(related).trim() !== '') {
|
||||
word.related = removeTags(related).split(',').map(w => w.trim()).filter(w => w.length > 0);
|
||||
}
|
||||
|
||||
if (removeTags(principalParts).trim() !== '') {
|
||||
word.principalParts = removeTags(principalParts).split(',').map(w => w.trim()).filter(w => w.length > 0);
|
||||
}
|
||||
|
||||
if (validateWord(word, wordId)) {
|
||||
if (confirm(`Are you sure you want to save changes to "${word.name}"?`)) {
|
||||
document.getElementById('editForm_' + wordId).classList.add('done');
|
||||
|
|
|
@ -18,21 +18,4 @@
|
|||
@import 'scss/themes/yellow';
|
||||
@import 'scss/themes/red';
|
||||
@import 'scss/themes/mint';
|
||||
@import 'scss/themes/grape';
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
font-family: $font;
|
||||
transition: all 0.2s ease-in;
|
||||
|
||||
* {
|
||||
transition: all 0.2s ease-in;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]),
|
||||
select,
|
||||
textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
@import 'scss/themes/grape';
|
|
@ -9,6 +9,7 @@
|
|||
* [Referencing Other Words](#referencing-other-words)
|
||||
* [Maximizing Large Text Boxes](#maximizing-large-text-boxes)
|
||||
* [IPA Auto-Fill Fields](#ipa-auto-fill-fields)
|
||||
* [Advanced Fields](#advanced-fields)
|
||||
* [Entry Management](#entry-management)
|
||||
* [Search/Filter](#searchfilter)
|
||||
* [The Settings Window](#the-settings-window)
|
||||
|
@ -18,20 +19,20 @@
|
|||
* [Creating An Account](#creating-an-account)
|
||||
* [Logging In](#logging-in)
|
||||
* [Differences](#differences)
|
||||
* [Settings](#settings)
|
||||
* [Settings](#settings-1)
|
||||
* [Public Dictionaries](#public-dictionaries)
|
||||
* [Forgot Your Password?](#forgot-your-password)
|
||||
* [Lockout](#lockout)
|
||||
* [Problems or Requests](#problems-or-requests)
|
||||
* [Update Log](#update-log)
|
||||
* [Open Source](#open-source)
|
||||
* [Thanks](#thanks-)
|
||||
* [Thanks](#thanks)
|
||||
|
||||
## What is Lexiconga?
|
||||
|
||||
Lexiconga is a tool intended to help you build constructed language (conlang) dictionaries/lexicons.
|
||||
Lexiconga is a tool built to help you build constructed language (conlang) dictionaries/lexicons quickly and easily.
|
||||
|
||||
You can enter words and definitions, and they will appear nicely formatted and in alphabetical order by name under your dictionary's title and details. You can also set your dicitonary to display your words by definition if you prefer that view. If the default parts of speech are not adequate for your conlang, you can change them to whatever you might need. You can also enter a description and full set of language rules that you can toggle on and off below the dictionary's title!
|
||||
You can enter words and definitions, and they will appear nicely formatted and in alphabetical order by name under your dictionary's title and details. You can also set your dicitonary to sort your words by definition if you prefer that view or even specify a fully custom alphabetical order. If the default parts of speech are not adequate for your conlang, you can change them to whatever you might need. You can also enter a description and full set of language rules that you can toggle on and off below the dictionary's title!
|
||||
|
||||
Lexiconga accepts Unicode characters so you can utilize whatever typable characters you might need and [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for formatting long text entries, and if you want to share or even just make a backup of your dictionary, you can export it to a single convenient file that can be easily re-imported. Your dictionary is saved to your browser's [localStorage](https://www.w3schools.com/html/html5_webstorage.asp) every time you make a change, which means as long as you use the same browser and don't deliberately delete it by clearing your cache, your dictionary will always be there when you come back.
|
||||
|
||||
|
@ -62,13 +63,29 @@ If you have more than one word with the same spelling, the duplicate words will
|
|||
If you need more space to see what you are entering into a word's Details field or any other long text field with a "Maximize" button, clicking "Maximize" will give you a larger view of the text box to enter text in. When you're done writing, click either the "Done" or × button or any of the darker space outside of the larger view, and your text will be in the original text area. It will even preserve your cursor position or highlighted text so you don't lose your place moving from the larger view back to the small (and vice-versa)!
|
||||
|
||||
### IPA Auto-Fill Fields
|
||||
|
||||
You may notice some buttons around the Pronunciation field in the main word form and some other fields in the Details tab of your Dictionary Settings menu. This indicates that the field is using a special feature that generates [International Phonetic Alphabet](https://en.wikipedia.org/wiki/International_Phonetic_Alphabet) (IPA) characters when typing certain combinations of characters. Click the "Field Help" button below the field for instructions on how to use those fields.
|
||||
|
||||
You can also click the "IPA Table" button to display a maximized table that shows all of the characters in the IPA. Clicking them will add it to the pronunciation field wherever you position the cursor in the field. If you hover over the button for an IPA character that is not on a standard keyboard, you can what character combinations will produce that character when you type it in the field.
|
||||
|
||||
If you do not want to use the IPA field feaure, you can turn it off in the Settings. Click the "Settings" button in the top right side of the website, uncheck "Use IPA Auto-Fill", and click "Save" or "Save & Close". The "IPA Table" and "Field Help" buttons will be removed from all fields they were around, and you can use those fields as normal.
|
||||
|
||||
### Advanced Fields
|
||||
Clicking the button labeled "Show Advanced Fields" underneath the **Details** field on any word form will expand the "Advanced Fields" section for that word list. Clicking the button again when it says "Hide Advanced Fields" will hide the section again to make it so you don't have to scroll down to reach the "Add Word" button. The fields in the Advanced Fields section are detailed below:
|
||||
|
||||
- **Details Field Templates:** A dropdown box with any templates you have previously created (see [Templates for Details Fields](#templates-for-details-fields) in the Settings section below to learn how to create a template). Selecting one of the templates will put that template into the Details field for you, _overwriting **anything** that might have already been written there_.
|
||||
- If there are no templates created, this field will not be displayed.
|
||||
- Each time you select a different template (other than the "None Selected" option), it will overwrite anything in the Details field with the template without warning, so please be careful.
|
||||
- Selecting the "None Selected" option at the top of the list will _not_ change the Details field.
|
||||
- **Etymology / Root Words:** Any words that the current word might have stemmed from originally.
|
||||
- Words written here are treated as word references (see [Referencing Other Words](#referencing-other-words) for how to reference a word or a duplicate word) without requiring the \{\{double-curly-braces\}\} as in the Details field. The words referenced here will appear below the word's details in the list.
|
||||
- Separate each individual root word with a comma.
|
||||
- **Related Words:** Any words that might be related to the current word.
|
||||
- Words written here are treated as word references (see [Referencing Other Words](#referencing-other-words) for how to reference a word or a duplicate word) without requiring the \{\{double-curly-braces\}\} as in the Details field. The words referenced here will appear below the word's details in the list.
|
||||
- Separate each individual related word with a comma.
|
||||
- **Principal Parts:** Any words that someone must know in order to conjugate the current word (see the [Wikipedia entry](https://en.wikipedia.org/wiki/Principal_parts) for more details).
|
||||
- Words written here are treated as word references (see [Referencing Other Words](#referencing-other-words) for how to reference a word or a duplicate word) without requiring the \{\{double-curly-braces\}\} as in the Details field. The words referenced here will appear below the word's details in the list.
|
||||
- Separate each individual principal part with a comma.
|
||||
|
||||
### Entry Management
|
||||
After adding some words to your dictionary, you'll see an "Options" button on each entry. Clicking the button reveals Edit and Delete buttons.
|
||||
|
||||
|
@ -89,6 +106,7 @@ You can refine your search by clicking the "Toggle Options" button and using the
|
|||
- **Case-Sensitive:** When checked, Lexiconga finds entries matching the letter case in the entered text. When unchecked, it will find any case as long as the letters match.
|
||||
- **Ignore Diacritics/Accents:** When checked, Lexiconga will ignore accented letters and diacritics and identify them as their equivalent unaccented letter and vice-versa, in case you want to find a word with a diacritic without entering the diacritic in the search box. When unchecked, it will only find diacritics and accented letters if they are specifically entered in the search box.
|
||||
- **Exact Words:** When checked, the search term will find entries with _exact matches_ in only the Word or Definition field. If Word or Definition has _any_ text aside from exactly what was entered in the search bar, it will not be displayed.
|
||||
- **Translation:** When checked, Lexiconga will translate all words and references by any specified orthographic translations and compare your search term with that instead of the words as entered.
|
||||
- **Include in Search**
|
||||
- **Word**: When checked, Lexiconga searches your dictionary's "Word" entries for the entered text. When unchecked, it ignores it.
|
||||
- **Definition**: When checked, Lexiconga searches your dictionary's "Definition/Equivalent Word(s)" entries for the entered text. When unchecked, it ignores it.
|
||||
|
@ -101,16 +119,25 @@ When you have a search term or filter applied, you can see the number of results
|
|||
To display _all_ of your words again, clear your search bar and ensure all the "Include Only" checkboxes are checked.
|
||||
|
||||
### The Settings Window
|
||||
|
||||
Clicking the "Settings" button in the top-right side of Lexiconga will show the Settings window with some options.
|
||||
|
||||
- **Use IPA Auto-Fill:** Check this to use character combinations to input International Phonetic Alphabet characters into Pronunciation fields. Use the "Field Help" button for instructions on how to use it and the "IPA Table" to display available characters. Uncheck it to disable the feature and hide the buttons.
|
||||
- **Use Hotkeys:** Check this to enable keyboard combinations to perform different helpful actions (see [Keyboard Shortcuts](#keyboard-shortcuts) below). Unchecking this disables the feature.
|
||||
- Note: If your browser does not support required features, this will be disabled automatically.
|
||||
- **Show Advanced Fields By Default:** Check this to make the advanced fields show on word forms without needing to click the "Show Advanced Fields" button (see [Advanced Fields](#advanced-fields) above). Unchecking this makes it so you need to click the "Show Advanced Fields" button to show the fields each time you edit a word.
|
||||
- **Default Theme:** Choose what color theme new dictionaries will use when they are created.
|
||||
|
||||
After making changes, click the "Save" or "Save & Close" button to save your changes.
|
||||
|
||||
#### Templates for Details Fields
|
||||
Below the "Default Theme" selector is the template editor. Either click "Create New Template" or choose one from the "Saved Templates" dropdown to begin editing the template. Once a new template is created or a saved template is selected, new fields will appear labeled "Template Name" and "Template," plus a "Save Template" and "Delete Template" button.
|
||||
|
||||
You can modify the template's name by editing the "Template Name" field, and whatever you set in the "Template" text area will be used as the template in the Details field of a word form if it is selected (see [Advanced Fields](#advanced-fields) above for more about how this is used).
|
||||
|
||||
After making any changes to a template, click the "Save Template" button below the "Template" field to save it to your browser. Templates are only stored in the browser they were created on and _do not_ get uploaded to your account (if you have one). If you create a template, it will _not be available on any other web browser_ unless you re-create it!
|
||||
|
||||
Clicking the "Delete Template" button will prompt you to confirm that you want to delete it, and if you confirm, it will permanently delete the currently selected template from your browser. There is no way to recover deleted templates!
|
||||
|
||||
### The Dictionary Settings Window
|
||||
|
||||
Clicking the "Edit" button under your dictionary's name will display a window with tabs that each contain different fields and options.
|
||||
|
@ -124,17 +151,19 @@ Clicking the "Edit" button under your dictionary's name will display a window wi
|
|||
#### Details
|
||||
|
||||
- **Parts of Speech:** The parts of speech available in the dropdown box on word forms. Separate each individual part of speech with a comma.
|
||||
- **Alphabetical Order:** This feature has not been implemented yet.
|
||||
- **Alphabetical Order:** The order that your words will be sorted by. Include every letter and different capitalization used in your dictionary to sort your words in whatever order you want—any letters in your words that are not sorted here are sorted by the default ASCII/Unicode order (i.e. English Alphabetical) _after_ any custom-sorted words. Lexiconga can only sort by single characters (rather than sets of characters) and will sort the words _as entered_, not using orthographic translations. Separate each character with a _space_.
|
||||
- **Phonology**
|
||||
- **Consonants:** The IPA characters representing the consonants present in your language. Uses the IPA Auto-Fill feature unless it is turned off. Separate each consonant with a _space_ so they will be displayed correctly under the Details section of your dictionary.
|
||||
- **Vowels:** The IPA characters representing the vowels present in your language. Uses the IPA Auto-Fill feature unless it is turned off. Separate each vowel with a _space_ so they will be displayed correctly under the Details section of your dictionary.
|
||||
- **Polyphthongs/Blends:** The IPA characters representing the polyphthongs or blends present in your language. Uses the IPA Auto-Fill feature unless it is turned off. Separate each one with a _space_ so they will be displayed correctly under the Details section of your dictionary.
|
||||
- **Notes:** Any notes about your constructed language's phonology that you want to share. Uses Markdown.
|
||||
- **Phonotactics**
|
||||
- **Onset:** What phonological characters can appear at the beginning of a syllable. Separate each with a comma.
|
||||
- **Nucleus:** What phonological characters can appear in the middle of a syllable. Separate each with a comma.
|
||||
- **Coda:** What phonological characters can appear at the end of a syllable. Separate each with a comma.
|
||||
- **Exceptions:** Any exceptions to the phonotactical rules laid out above. This is a Markdown-enable text area that you can use however you'd like.
|
||||
- **Onset:** What phonological characters can appear at the beginning of a syllable. Separate each with a _comma_.
|
||||
- **Nucleus:** What phonological characters can appear in the middle of a syllable. Separate each with a _comma_.
|
||||
- **Coda:** What phonological characters can appear at the end of a syllable. Separate each with a _comma_.
|
||||
- **Notes:** Any notes about your phonotactical rules laid out above. Uses Markdown.
|
||||
- **Orthography**
|
||||
- **Translations:** The specification for how Lexiconga should translate certain character sequences into other character sequences. Use the format "original=new" where "original" is the old letter or sequence of letters and "new" is what you want those letters to change into separated by an equal sign. Put each translation on a _separate line_.
|
||||
- **Notes:** Any notes about your constructed language's writing system that you want to share. Uses Markdown.
|
||||
- **Grammar**
|
||||
- **Notes:** Any notes about your constructed language's grammar that you want to share. Uses Markdown.
|
||||
|
@ -144,6 +173,7 @@ Clicking the "Edit" button under your dictionary's name will display a window wi
|
|||
- **Words are Case-Sensitive:** Only available when "Prevent Duplicate Words" is checked. Checking this box will allow the creation of words with the exact same spelling if their capitalization is different.
|
||||
- **Sort by Definition:** Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.
|
||||
- **Theme:** Set the color theme for the current dictionary.
|
||||
- **Custom Styling:** Specify custom CSS to change the styling of your dictionary. You can use custom fonts by specifying them here and setting the `font-family` style of the `.orthographic-translation` class!
|
||||
- **Make Public:** Only visible if logged in with a Lexiconga account. Checking this box will make your dictionary public via a link you can share with others. The link will appear below this checkbox after it is checked.
|
||||
|
||||
#### Actions
|
||||
|
@ -170,9 +200,11 @@ After making any changes, be sure to click "Save" or "Save & Close" to ensure th
|
|||
- **M:** Maximize/Minimize Full Screen textbox when typing in the boxes that have the Maximize button.
|
||||
- **S:** Open the Search panel.
|
||||
- **Shift + S:** Open the Settings window.
|
||||
- **X:** Clear the Search box.
|
||||
- **Backspace/Delete:** Clear the Search box.
|
||||
|
||||
## Accounts
|
||||
**Note:** Lexiconga is 100% functional _without_ creating an account! Using an account only adds additional syncing features that enable you to store more than one dictionary at a time, access your dictionaries from any computer, and optionally share dicitonaries publicly with a link. _An account is not required_ to build your conlang on your local browser.
|
||||
|
||||
If you are using an account with Lexiconga, your experience should remain essentially the same, but you will see some additional options in the Settings menu and you might notice some slight changes in performance as it saves to and loads from the database. This saving/loading process prioritizes your local dictionary, so if you ever lose connection, it will keep retrying the upload until connection is re-established. It also attempts to sync every time you load Lexiconga, so please be aware of that if you refresh the page.
|
||||
|
||||
### Creating An Account
|
||||
|
|
|
@ -11,7 +11,7 @@ We may collect personal identification information from Users in a variety of wa
|
|||
We may collect non-personal identification information about Users whenever they interact with our Site through website analytics tools. Non-personal identification information may include the browser name, the type of computer and technical information about Users means of connection to our Site, such as the operating system and the Internet service providers utilized and other similar information.
|
||||
|
||||
### Web browser cookies
|
||||
Our Site stores exactly one "cookie" that is used to keep Users logged in to their Accounts, and it does use "local storage". User's web browser places local storage on their hard drive for record-keeping purposes and sometimes to track information about them, but we only use this to store your current dictionary. User may choose to set their web browser to refuse local storage usage, but if they do so, the Site will not function properly.
|
||||
Our Site stores manually-specified "cookies": one that is used to keep Users logged in to their Accounts and others that track what announcements have been dismissed. Our site does use and require "local storage" to function: the User's web browser places local storage on their hard drive, and our Site uses it for the sole purpose of storing your current dictionary. User may choose to set their web browser to refuse local storage usage, but if they do so, the Site will not function properly.
|
||||
|
||||
### How we use collected information
|
||||
Lexiconga may collect and use Users personal information for the following purposes:
|
||||
|
@ -37,7 +37,7 @@ If User decides to opt-in to our mailing list, they will receive emails that may
|
|||
Users may find advertising or other content on our Site that link to the sites and services of our partners, suppliers, advertisers, sponsors, licencors and other third parties. While we manually curate what links we allow in our advertisements, we do not control the content that appears on these sites and are not responsible for the practices employed by websites linked to or from our Site. In addition, these sites or services, including their content and links, may be constantly changing. These sites and services may have their own privacy policies and customer service policies. Browsing and interaction on any other website, including websites which have a link to our Site, is subject to that website's own terms and policies.
|
||||
|
||||
### Advertising
|
||||
Ads appearing on our site are manually chosen to by the Lexiconga's administrator, only appear as text, and never set cookies. You can see all active advertisements at any time in the [source code on Github](https://github.com/Alamantus/Lexiconga/blob/master/ads.json).
|
||||
No advertisements exist in the Lexiconga source code. Any advertisements you might see when viewing a Lexiconga dictionary will only have been added by a user for their own reasons.
|
||||
|
||||
### Changes to this privacy policy
|
||||
Lexiconga has the discretion to update this privacy policy at any time. When we do, we will post a notification on the main page of our Site. We encourage Users to frequently check this page for any changes to stay informed about how we are helping to protect the personal information we collect. You acknowledge and agree that it is your responsibility to review this privacy policy periodically and become aware of modifications.
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# To resolve the issue with "Your connection to this site is not secure" message
|
||||
Header set Content-Security-Policy: upgrade-insecure-requests env=HTTPS
|
||||
|
||||
RewriteEngine On # Turn on the rewriting engine
|
||||
|
||||
RewriteRule ^view/([0-9]+)/([0-9]+)/?$ router.php?view=word&dict=$1&word=$2 [NC,L] # Handle word ids.
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
[
|
||||
{
|
||||
"header": "Welcome to Lexiconga 2.0!",
|
||||
"body": "<p>Lexiconga has been rewritten from the ground up!</p><p>Check the <a href=\"https://github.com/Alamantus/Lexiconga/releases\" target=\"_blank\" rel=\"noopener\">Updates page</a> for all the new features, or click Help to get a refresher on how to use Lexiconga!</p>",
|
||||
"expire": "January 1, 2020",
|
||||
"dismissId": "welcome"
|
||||
"header": "Lexiconga is now fully ad-free",
|
||||
"body": "<p><em>August 26, 2022</em> – Out of the goodness of my heart, I've decided to completely remove advertisements from Lexiconga. They were kind of pointless anyway. If you want to support Lexiconga's developer (who keeps Lexiconga online with his own money), we thank you for using the \"Support Lexiconga\" button in the website footer!</p>",
|
||||
"expire": "December 31, 2022",
|
||||
"dismissId": "adFreeAnnouncement"
|
||||
},
|
||||
{
|
||||
"header": "Warning to Safari Users",
|
||||
"body": "<p><em>December 16, 2021</em> – It has come to our attention that Apple is working on updates to their Safari browser (the default browser engine for iPhones, iPads, and MacBooks) that automatically deletes local content from browsers after 7 days. <a href=\"https://blog.lexicon.ga/post/670777320117764096/important-warning-for-safari-users\" target=\"_blank\">Read our blog post</a> to learn what that means for you.",
|
||||
"expire": "December 31, 2022",
|
||||
"dismissId": "iOSWarning"
|
||||
}
|
||||
]
|
|
@ -11,7 +11,7 @@ class Dictionary {
|
|||
$this->token = new Token();
|
||||
|
||||
$this->defaults = array(
|
||||
'partsOfSpeech' => 'Noun,Adjective,Verb',
|
||||
'partsOfSpeech' => 'Noun,Adjective,Verb,Adverb,Preposition,Pronoun,Conjunction',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -33,13 +33,15 @@ class Dictionary {
|
|||
$insert_dictionary = $this->db->execute($insert_dictionary_query, array($new_id, $user, 'A new dictionary.', time()));
|
||||
|
||||
if ($insert_dictionary === true) {
|
||||
$insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, exceptions, orthography_notes, grammar_notes)
|
||||
VALUES ($new_id, ?, ?, ?, ?)";
|
||||
$insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, phonology_notes, phonotactics_notes, translations, orthography_notes, grammar_notes)
|
||||
VALUES ($new_id, ?, ?, ?, ?, ?, ?)";
|
||||
$insert_linguistics = $this->db->execute($insert_linguistics_query, array(
|
||||
$this->defaults['partsOfSpeech'],
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
));
|
||||
|
||||
if ($insert_linguistics === true) {
|
||||
|
@ -59,7 +61,7 @@ VALUES ($new_id, ?, ?, ?, ?)";
|
|||
public function changeCurrent ($user, $dictionary) {
|
||||
$update_query = 'UPDATE users SET current_dictionary=? WHERE id=?';
|
||||
$update = $this->db->query($update_query, array($dictionary, $user));
|
||||
if ($update->rowCount() > 0) {
|
||||
if (trim($this->db->last_error_info[2]) == '') {
|
||||
return $dictionary;
|
||||
}
|
||||
return false;
|
||||
|
@ -88,165 +90,12 @@ VALUES ($new_id, ?, ?, ?, ?)";
|
|||
return array();
|
||||
}
|
||||
|
||||
public function getPublicDictionaryDetails ($dictionary) {
|
||||
if (is_numeric($dictionary)) {
|
||||
$query = "SELECT d.*, dl.*, u.public_name FROM dictionaries d JOIN dictionary_linguistics dl ON dl.dictionary = d.id JOIN users u ON u.id = d.user WHERE d.id=? AND d.is_public=1";
|
||||
$result = $this->db->query($query, array($dictionary))->fetch();
|
||||
if ($result) {
|
||||
// Default json values in case they are somehow not created by front end first
|
||||
$partsOfSpeech = $result['parts_of_speech'] !== '' ? $result['parts_of_speech'] : $this->defaults['partsOfSpeech'];
|
||||
|
||||
return array(
|
||||
'externalID' => $result['id'],
|
||||
'name' => $result['name'],
|
||||
'specification' => $result['specification'],
|
||||
'description' => $result['description'],
|
||||
'createdBy' => $result['public_name'],
|
||||
'partsOfSpeech' => explode(',', $partsOfSpeech),
|
||||
'alphabeticalOrder' => array(),
|
||||
'details' => array(
|
||||
'phonology' => array(
|
||||
'consonants' => $result['consonants'] !== '' ? explode(' ', $result['consonants']) : array(),
|
||||
'vowels' => $result['vowels'] !== '' ? explode(' ', $result['vowels']) : array(),
|
||||
'blends' => $result['blends'] !== '' ? explode(' ', $result['blends']) : array(),
|
||||
'phonotactics' => array(
|
||||
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
|
||||
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
|
||||
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
|
||||
'exceptions' => $result['exceptions'],
|
||||
),
|
||||
),
|
||||
'orthography' => array(
|
||||
'notes' => $result['orthography_notes'],
|
||||
),
|
||||
'grammar' => array(
|
||||
'notes' => $result['grammar_notes'],
|
||||
),
|
||||
),
|
||||
'settings' => array(
|
||||
'allowDuplicates' => $result['allow_duplicates'] === '1' ? true : false,
|
||||
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
|
||||
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
|
||||
'theme' => $result['theme'],
|
||||
'isPublic' => $result['is_public'] === '1' ? true : false,
|
||||
),
|
||||
'lastUpdated' => is_null($result['last_updated']) ? $result['created_on'] : $result['last_updated'],
|
||||
'createdOn' => $result['created_on'],
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPublicDictionaryWords ($dictionary) {
|
||||
if (is_numeric($dictionary)) {
|
||||
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=? AND is_public=1";
|
||||
$results = $this->db->query($query, array($dictionary))->fetchAll();
|
||||
if ($results) {
|
||||
return array_map(function ($row) use ($dictionary) {
|
||||
return array(
|
||||
'name' => $row['name'],
|
||||
'pronunciation' => $row['pronunciation'],
|
||||
'partOfSpeech' => $row['part_of_speech'],
|
||||
'definition' => $row['definition'],
|
||||
'details' => $this->parseReferences($row['details'], $dictionary),
|
||||
'lastUpdated' => is_null($row['last_updated']) ? intval($row['created_on']) : intval($row['last_updated']),
|
||||
'createdOn' => intval($row['created_on']),
|
||||
'wordId' => intval($row['word_id']),
|
||||
);
|
||||
}, $results);
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getSpecificPublicDictionaryWord ($dictionary, $word) {
|
||||
if (is_numeric($dictionary) && is_numeric($word)) {
|
||||
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=? AND word_id=? AND is_public=1";
|
||||
$result = $this->db->query($query, array($dictionary, $word))->fetch();
|
||||
if ($result) {
|
||||
return array(
|
||||
'name' => $result['name'],
|
||||
'pronunciation' => $result['pronunciation'],
|
||||
'partOfSpeech' => $result['part_of_speech'],
|
||||
'definition' => $result['definition'],
|
||||
'details' => $this->parseReferences($result['details'], $dictionary),
|
||||
'lastUpdated' => is_null($result['last_updated']) ? intval($result['created_on']) : intval($result['last_updated']),
|
||||
'createdOn' => intval($result['created_on']),
|
||||
'wordId' => intval($result['word_id']),
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function parseReferences($details, $dictionary_id) {
|
||||
$details = strip_tags($details);
|
||||
if (preg_match_all('/\{\{.+?\}\}/', $details, $references) !== false) {
|
||||
$references = array_unique($references[0]);
|
||||
foreach($references as $reference) {
|
||||
$word_to_find = preg_replace('/\{\{|\}\}/', '', $reference);
|
||||
$homonymn = 0;
|
||||
|
||||
if (strpos($word_to_find, ':') !== false) {
|
||||
$separator = strpos($word_to_find, ':');
|
||||
$homonymn = substr($word_to_find, $separator + 1);
|
||||
$word_to_find = substr($word_to_find, 0, $separator);
|
||||
if ($homonymn && trim($homonymn) && intval(trim($homonymn)) > 0) {
|
||||
$homonymn = intval(trim($homonymn));
|
||||
} else {
|
||||
$homonymn = false;
|
||||
}
|
||||
}
|
||||
|
||||
$target_id = false;
|
||||
$reference_ids = $this->getWordIdsWithName($dictionary_id, $word_to_find);
|
||||
|
||||
if (count($reference_ids) > 0) {
|
||||
if ($homonymn !== false && $homonymn > 0) {
|
||||
if (isset($reference_ids[$homonymn - 1])) {
|
||||
$target_id = $reference_ids[$homonymn - 1];
|
||||
}
|
||||
} else if ($homonymn !== false) {
|
||||
$target_id = $reference_ids[0];
|
||||
}
|
||||
|
||||
if ($target_id !== false) {
|
||||
if ($homonymn < 1) {
|
||||
$homonymn = 1;
|
||||
}
|
||||
$homonymn_sub_html = $homonymn > 0 ? '<sub>' . $homonymn . '</sub>' : '';
|
||||
$site_root = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], $dictionary_id));
|
||||
$markdown_link = '<a href="' . $site_root . $dictionary_id . '/' . $target_id .'" target="_blank" title="Link to Reference">'
|
||||
. $word_to_find . $homonymn_sub_html . '</a>';
|
||||
$details = str_replace($reference, $markdown_link, $details);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
private function getWordIdsWithName($dictionary, $word_name) {
|
||||
if (is_numeric($dictionary)) {
|
||||
$query = "SELECT word_id FROM words WHERE dictionary=? AND name=?";
|
||||
$results = $this->db->query($query, array($dictionary, $word_name))->fetchAll();
|
||||
if ($results) {
|
||||
return array_map(function ($row) {
|
||||
return intval($row['word_id']);
|
||||
}, $results);
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getDetails ($user, $dictionary) {
|
||||
$query = "SELECT * FROM dictionaries JOIN dictionary_linguistics ON dictionary = id WHERE user=$user AND id=$dictionary";
|
||||
$result = $this->db->query($query)->fetch();
|
||||
if ($result) {
|
||||
// Default json values in case they are somehow not created by front end first
|
||||
$partsOfSpeech = $result['parts_of_speech'] !== '' ? $result['parts_of_speech'] : $this->defaults['partsOfSpeech'];
|
||||
$partsOfSpeech = isset($result['parts_of_speech']) && $result['parts_of_speech'] !== '' ? $result['parts_of_speech'] : $this->defaults['partsOfSpeech'];
|
||||
|
||||
return array(
|
||||
'externalID' => $result['id'],
|
||||
|
@ -254,20 +103,22 @@ VALUES ($new_id, ?, ?, ?, ?)";
|
|||
'specification' => $result['specification'],
|
||||
'description' => $result['description'],
|
||||
'partsOfSpeech' => explode(',', $partsOfSpeech),
|
||||
'alphabeticalOrder' => array(),
|
||||
'alphabeticalOrder' => $result['alphabetical_order'] !== '' ? explode(' ', $result['alphabetical_order']) : array(),
|
||||
'details' => array(
|
||||
'phonology' => array(
|
||||
'consonants' => $result['consonants'] !== '' ? explode(' ', $result['consonants']) : array(),
|
||||
'vowels' => $result['vowels'] !== '' ? explode(' ', $result['vowels']) : array(),
|
||||
'blends' => $result['blends'] !== '' ? explode(' ', $result['blends']) : array(),
|
||||
'phonotactics' => array(
|
||||
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
|
||||
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
|
||||
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
|
||||
'exceptions' => $result['exceptions'],
|
||||
),
|
||||
'notes' => $result['phonology_notes'],
|
||||
),
|
||||
'phonotactics' => array(
|
||||
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
|
||||
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
|
||||
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
|
||||
'notes' => $result['phonotactics_notes'],
|
||||
),
|
||||
'orthography' => array(
|
||||
'translations' => $result['translations'] !== '' ? explode(PHP_EOL, $result['translations']) : array(),
|
||||
'notes' => $result['orthography_notes'],
|
||||
),
|
||||
'grammar' => array(
|
||||
|
@ -279,6 +130,7 @@ VALUES ($new_id, ?, ?, ?, ?)";
|
|||
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
|
||||
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
|
||||
'theme' => $result['theme'],
|
||||
'customCSS' => $result['custom_css'],
|
||||
'isPublic' => $result['is_public'] === '1' ? true : false,
|
||||
),
|
||||
'lastUpdated' => is_null($result['last_updated']) ? $result['created_on'] : $result['last_updated'],
|
||||
|
@ -297,6 +149,7 @@ SET name=:name,
|
|||
case_sensitive=:case_sensitive,
|
||||
sort_by_definition=:sort_by_definition,
|
||||
theme=:theme,
|
||||
custom_css=:custom_css,
|
||||
is_public=:is_public,
|
||||
last_updated=:last_updated,
|
||||
created_on=:created_on
|
||||
|
@ -311,6 +164,7 @@ WHERE user=$user AND id=$dictionary";
|
|||
':case_sensitive' => $dictionary_object['settings']['caseSensitive'] ? 1 : 0,
|
||||
':sort_by_definition' => $dictionary_object['settings']['sortByDefinition'] ? 1 : 0,
|
||||
':theme' => $dictionary_object['settings']['theme'],
|
||||
':custom_css' => $dictionary_object['settings']['customCSS'],
|
||||
':is_public' => $dictionary_object['settings']['isPublic'] ? 1 : 0,
|
||||
':last_updated' => $dictionary_object['lastUpdated'],
|
||||
':created_on' => $dictionary_object['createdOn'],
|
||||
|
@ -320,13 +174,16 @@ WHERE user=$user AND id=$dictionary";
|
|||
$linguistics = $dictionary_object['details'];
|
||||
$query2 = "UPDATE dictionary_linguistics
|
||||
SET parts_of_speech=:parts_of_speech,
|
||||
alphabetical_order=:alphabetical_order,
|
||||
consonants=:consonants,
|
||||
vowels=:vowels,
|
||||
blends=:blends,
|
||||
phonology_notes=:phonology_notes,
|
||||
onset=:onset,
|
||||
nucleus=:nucleus,
|
||||
coda=:coda,
|
||||
exceptions=:exceptions,
|
||||
phonotactics_notes=:phonotactics_notes,
|
||||
translations=:translations,
|
||||
orthography_notes=:orthography_notes,
|
||||
grammar_notes=:grammar_notes
|
||||
WHERE dictionary=$dictionary";
|
||||
|
@ -334,13 +191,16 @@ WHERE dictionary=$dictionary";
|
|||
// $result2 = $this->db->query($query2, array(
|
||||
$result2 = $this->db->execute($query2, array(
|
||||
':parts_of_speech' => implode(',', $dictionary_object['partsOfSpeech']),
|
||||
':alphabetical_order' => implode(' ', $dictionary_object['alphabeticalOrder']),
|
||||
':consonants' => implode(' ', $linguistics['phonology']['consonants']),
|
||||
':vowels' => implode(' ', $linguistics['phonology']['vowels']),
|
||||
':blends' => implode(' ', $linguistics['phonology']['blends']),
|
||||
':onset' => implode(',', $linguistics['phonology']['phonotactics']['onset']),
|
||||
':nucleus' => implode(',', $linguistics['phonology']['phonotactics']['nucleus']),
|
||||
':coda' => implode(',', $linguistics['phonology']['phonotactics']['coda']),
|
||||
':exceptions' => $linguistics['phonology']['phonotactics']['exceptions'],
|
||||
':phonology_notes' => $linguistics['phonology']['notes'],
|
||||
':onset' => implode(',', $linguistics['phonotactics']['onset']),
|
||||
':nucleus' => implode(',', $linguistics['phonotactics']['nucleus']),
|
||||
':coda' => implode(',', $linguistics['phonotactics']['coda']),
|
||||
':phonotactics_notes' => $linguistics['phonotactics']['notes'],
|
||||
':translations' => implode(PHP_EOL, $linguistics['orthography']['translations']),
|
||||
':orthography_notes' => $linguistics['orthography']['notes'],
|
||||
':grammar_notes' => $linguistics['grammar']['notes'],
|
||||
));
|
||||
|
@ -355,11 +215,14 @@ WHERE dictionary=$dictionary";
|
|||
}
|
||||
|
||||
public function getWords ($user, $dictionary) {
|
||||
$query = "SELECT words.* FROM words JOIN dictionaries ON id = dictionary WHERE dictionary=$dictionary AND user=$user";
|
||||
$query = "SELECT words.*, wa.etymology, wa.related, wa.principal_parts FROM words
|
||||
LEFT JOIN words_advanced wa ON wa.dictionary = words.dictionary AND wa.word_id = words.word_id
|
||||
JOIN dictionaries ON dictionaries.id = words.dictionary
|
||||
WHERE words.dictionary=$dictionary AND dictionaries.user=$user";
|
||||
$results = $this->db->query($query)->fetchAll();
|
||||
if ($results) {
|
||||
return array_map(function ($row) {
|
||||
return array(
|
||||
$word = array(
|
||||
'name' => $row['name'],
|
||||
'pronunciation' => $row['pronunciation'],
|
||||
'partOfSpeech' => $row['part_of_speech'],
|
||||
|
@ -369,6 +232,20 @@ WHERE dictionary=$dictionary";
|
|||
'createdOn' => intval($row['created_on']),
|
||||
'wordId' => intval($row['word_id']),
|
||||
);
|
||||
|
||||
if (!is_null($row['etymology']) && $row['etymology'] !== '') {
|
||||
$word['etymology'] = explode(',', $row['etymology']);
|
||||
}
|
||||
|
||||
if (!is_null($row['related']) && $row['related'] !== '') {
|
||||
$word['related'] = explode(',', $row['related']);
|
||||
}
|
||||
|
||||
if (!is_null($row['principal_parts']) && $row['principal_parts'] !== '') {
|
||||
$word['principalParts'] = explode(',', $row['principal_parts']);
|
||||
}
|
||||
|
||||
return $word;
|
||||
}, $results);
|
||||
}
|
||||
return array();
|
||||
|
@ -393,28 +270,37 @@ WHERE dictionary=$dictionary";
|
|||
return true;
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO words (dictionary, word_id, name, pronunciation, part_of_speech, definition, details, last_updated, created_on) VALUES ';
|
||||
$params = array();
|
||||
$query1 = 'INSERT INTO words (dictionary, word_id, name, pronunciation, part_of_speech, definition, details, last_updated, created_on) VALUES ';
|
||||
$query2 = 'INSERT INTO words_advanced (dictionary, word_id, etymology, related, principal_parts) VALUES ';
|
||||
$params1 = array();
|
||||
$params2 = array();
|
||||
$word_ids = array();
|
||||
$most_recent_word_update = 0;
|
||||
foreach($words as $word) {
|
||||
$last_updated = isset($word['lastUpdated']) ? $word['createdOn'] : $word['lastUpdated'];
|
||||
$last_updated = isset($word['lastUpdated']) ? $word['lastUpdated'] : $word['createdOn'];
|
||||
if ($most_recent_word_update < $last_updated) {
|
||||
$most_recent_word_update = $last_updated;
|
||||
}
|
||||
$word_ids[] = $word['wordId'];
|
||||
$query .= "(?, ?, ?, ?, ?, ?, ?, ?, ?), ";
|
||||
$params[] = $dictionary;
|
||||
$params[] = $word['wordId'];
|
||||
$params[] = $word['name'];
|
||||
$params[] = $word['pronunciation'];
|
||||
$params[] = $word['partOfSpeech'];
|
||||
$params[] = $word['definition'];
|
||||
$params[] = $word['details'];
|
||||
$params[] = $last_updated;
|
||||
$params[] = $word['createdOn'];
|
||||
$query1 .= "(?, ?, ?, ?, ?, ?, ?, ?, ?), ";
|
||||
$params1[] = $dictionary;
|
||||
$params1[] = $word['wordId'];
|
||||
$params1[] = $word['name'];
|
||||
$params1[] = $word['pronunciation'];
|
||||
$params1[] = $word['partOfSpeech'];
|
||||
$params1[] = $word['definition'];
|
||||
$params1[] = $word['details'];
|
||||
$params1[] = $last_updated;
|
||||
$params1[] = $word['createdOn'];
|
||||
|
||||
$query2 .= "(?, ?, ?, ?, ?), ";
|
||||
$params2[] = $dictionary;
|
||||
$params2[] = $word['wordId'];
|
||||
$params2[] = isset($word['etymology']) ? implode(',', $word['etymology']) : '';
|
||||
$params2[] = isset($word['related']) ? implode(',', $word['related']) : '';
|
||||
$params2[] = isset($word['principalParts']) ? implode(',', $word['principalParts']) : '';
|
||||
}
|
||||
$query = trim($query, ', ') . ' ON DUPLICATE KEY UPDATE
|
||||
$query1 = trim($query1, ', ') . ' ON DUPLICATE KEY UPDATE
|
||||
name=VALUES(name),
|
||||
pronunciation=VALUES(pronunciation),
|
||||
part_of_speech=VALUES(part_of_speech),
|
||||
|
@ -422,10 +308,14 @@ definition=VALUES(definition),
|
|||
details=VALUES(details),
|
||||
last_updated=VALUES(last_updated),
|
||||
created_on=VALUES(created_on)';
|
||||
$query2 = trim($query2, ', ') . ' ON DUPLICATE KEY UPDATE
|
||||
etymology=VALUES(etymology),
|
||||
related=VALUES(related),
|
||||
principal_parts=VALUES(principal_parts)';
|
||||
|
||||
$results = $this->db->execute($query, $params);
|
||||
$results1 = $this->db->execute($query1, $params1);
|
||||
|
||||
// if ($results) {
|
||||
// if ($results1) {
|
||||
// $database_words = $this->getWords($user, $dictionary);
|
||||
// $database_ids = array_map(function($database_word) { return $database_word['id']; }, $database_words);
|
||||
// $words_to_delete = array_filter($database_ids, function($database_id) use($word_ids) { return !in_array($database_id, $word_ids); });
|
||||
|
@ -435,8 +325,11 @@ created_on=VALUES(created_on)';
|
|||
// }
|
||||
// }
|
||||
|
||||
if ($results) {
|
||||
return $results;
|
||||
if ($results1 === true) {
|
||||
$results2 = $this->db->execute($query2, $params2);
|
||||
if ($results2 === true) {
|
||||
return $results1 && $results2;
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'error' => $this->db->last_error_info,
|
||||
|
|
|
@ -0,0 +1,418 @@
|
|||
<?php
|
||||
require_once(realpath(dirname(__FILE__) . '/./Db.php'));
|
||||
require_once(realpath(dirname(__FILE__) . '/./Token.php'));
|
||||
|
||||
class PublicDictionary {
|
||||
private $db;
|
||||
private $token;
|
||||
private $defaults;
|
||||
private $original_words;
|
||||
public $details;
|
||||
public $words;
|
||||
|
||||
function __construct ($dictionary_id, $details_only = false) {
|
||||
$this->db = new Db();
|
||||
$this->token = new Token();
|
||||
|
||||
$this->defaults = array(
|
||||
'partsOfSpeech' => 'Noun,Adjective,Verb',
|
||||
);
|
||||
|
||||
$this->details = $this->getPublicDictionaryDetails($dictionary_id);
|
||||
$this->words = $details_only ? [] : $this->getPublicDictionaryWords($dictionary_id);
|
||||
if ($this->details !== false) {
|
||||
$this->details['wordStats'] = $this->getWordStats();
|
||||
}
|
||||
}
|
||||
|
||||
public function getPublicDictionaryDetails ($dictionary) {
|
||||
if (is_numeric($dictionary)) {
|
||||
$query = "SELECT d.*, dl.*, u.public_name FROM dictionaries d JOIN dictionary_linguistics dl ON dl.dictionary = d.id JOIN users u ON u.id = d.user WHERE d.id=? AND d.is_public=1";
|
||||
$result = $this->db->query($query, array($dictionary))->fetch();
|
||||
if ($result) {
|
||||
// Default json values in case they are somehow not created by front end first
|
||||
$partsOfSpeech = isset($result['parts_of_speech']) && $result['parts_of_speech'] !== '' ? $result['parts_of_speech'] : $this->defaults['partsOfSpeech'];
|
||||
|
||||
return array(
|
||||
'externalID' => $result['id'],
|
||||
'name' => $result['name'],
|
||||
'specification' => $result['specification'],
|
||||
'description' => $this->parseReferences(strip_tags($result['description']), $result['id']),
|
||||
'createdBy' => $result['public_name'],
|
||||
'partsOfSpeech' => explode(',', $partsOfSpeech),
|
||||
'alphabeticalOrder' => $result['alphabetical_order'] !== '' ? explode(' ', $result['alphabetical_order']) : array(),
|
||||
'details' => array(
|
||||
'phonology' => array(
|
||||
'consonants' => $result['consonants'] !== '' ? explode(' ', $result['consonants']) : array(),
|
||||
'vowels' => $result['vowels'] !== '' ? explode(' ', $result['vowels']) : array(),
|
||||
'blends' => $result['blends'] !== '' ? explode(' ', $result['blends']) : array(),
|
||||
'notes' => $this->parseReferences(strip_tags($result['phonology_notes']), $result['id']),
|
||||
),
|
||||
'phonotactics' => array(
|
||||
'onset' => $result['onset'] !== '' ? explode(',', $result['onset']) : array(),
|
||||
'nucleus' => $result['nucleus'] !== '' ? explode(',', $result['nucleus']) : array(),
|
||||
'coda' => $result['coda'] !== '' ? explode(',', $result['coda']) : array(),
|
||||
'notes' => $this->parseReferences(strip_tags($result['phonotactics_notes']), $result['id']),
|
||||
),
|
||||
'orthography' => array(
|
||||
'translations' => $result['translations'] !== '' ? explode(PHP_EOL, $result['translations']) : array(),
|
||||
'notes' => $this->parseReferences(strip_tags($result['orthography_notes']), $result['id']),
|
||||
),
|
||||
'grammar' => array(
|
||||
'notes' => $this->parseReferences(strip_tags($result['grammar_notes']), $result['id']),
|
||||
),
|
||||
),
|
||||
'settings' => array(
|
||||
'allowDuplicates' => $result['allow_duplicates'] === '1' ? true : false,
|
||||
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
|
||||
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
|
||||
'theme' => $result['theme'],
|
||||
'customCSS' => $result['custom_css'],
|
||||
'isPublic' => $result['is_public'] === '1' ? true : false,
|
||||
),
|
||||
'lastUpdated' => is_null($result['last_updated']) ? $result['created_on'] : $result['last_updated'],
|
||||
'createdOn' => $result['created_on'],
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPublicDictionaryWords ($dictionary) {
|
||||
if (is_numeric($dictionary)) {
|
||||
$words = $this->getWordsAsEntered();
|
||||
if ($words) {
|
||||
return array_map(function ($row) use ($dictionary) {
|
||||
$word = array(
|
||||
'name' => $this->translateOrthography($row['name'], $dictionary),
|
||||
'pronunciation' => $row['pronunciation'],
|
||||
'partOfSpeech' => $row['part_of_speech'],
|
||||
'definition' => $row['definition'],
|
||||
'details' => $this->parseReferences(strip_tags($row['details']), $dictionary, false),
|
||||
'lastUpdated' => is_null($row['last_updated']) ? intval($row['created_on']) : intval($row['last_updated']),
|
||||
'createdOn' => intval($row['created_on']),
|
||||
'wordId' => intval($row['word_id']),
|
||||
);
|
||||
|
||||
if (!is_null($row['etymology'])) {
|
||||
if (strlen($row['etymology']) > 0) {
|
||||
$word['etymology'] = array_map(function ($root) use($dictionary) {
|
||||
return $this->getWordReferenceHTML(strip_tags($root), $dictionary, false);
|
||||
}, explode(',', $row['etymology']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($row['related'])) {
|
||||
if (strlen($row['related']) > 0) {
|
||||
$word['related'] = array_map(function ($root) use($dictionary) {
|
||||
return $this->getWordReferenceHTML(strip_tags($root), $dictionary, false);
|
||||
}, explode(',', $row['related']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($row['principal_parts'])) {
|
||||
if (strlen($row['principal_parts']) > 0) {
|
||||
$word['principalParts'] = explode(',', $row['principal_parts']);
|
||||
}
|
||||
}
|
||||
|
||||
return $word;
|
||||
}, $this->sortWords($words));
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getSpecificPublicDictionaryWord ($dictionary, $word_id) {
|
||||
if (is_numeric($dictionary) && is_numeric($word_id)) {
|
||||
$results = array_filter($this->getWordsAsEntered(), fn ($row) => $row['word_id'] == $word_id);
|
||||
$result = array_values($results)[0] ?? null;
|
||||
if ($result) {
|
||||
$word = array(
|
||||
'name' => $this->translateOrthography($result['name'], $dictionary),
|
||||
'pronunciation' => $result['pronunciation'],
|
||||
'partOfSpeech' => $result['part_of_speech'],
|
||||
'definition' => $result['definition'],
|
||||
'details' => $this->parseReferences(strip_tags($result['details']), $dictionary),
|
||||
'lastUpdated' => is_null($result['last_updated']) ? intval($result['created_on']) : intval($result['last_updated']),
|
||||
'createdOn' => intval($result['created_on']),
|
||||
'wordId' => intval($result['word_id']),
|
||||
);
|
||||
|
||||
if (!is_null($result['etymology'])) {
|
||||
if (strlen($result['etymology']) > 0) {
|
||||
$word['etymology'] = array_map(function ($root) use($dictionary) {
|
||||
return $this->getWordReferenceHTML(strip_tags($root), $dictionary);
|
||||
}, explode(',', $result['etymology']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($result['related'])) {
|
||||
if (strlen($result['related']) > 0) {
|
||||
$word['related'] = array_map(function ($root) use($dictionary) {
|
||||
return $this->getWordReferenceHTML(strip_tags($root), $dictionary);
|
||||
}, explode(',', $result['related']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($result['principal_parts'])) {
|
||||
if (strlen($result['principal_parts']) > 0) {
|
||||
$word['principalParts'] = explode(',', $result['principal_parts']);
|
||||
}
|
||||
}
|
||||
|
||||
return $word;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getWordsAsEntered() {
|
||||
if (!isset($this->original_words)) {
|
||||
$query = "SELECT words.*, wa.etymology, wa.related, wa.principal_parts FROM words
|
||||
LEFT JOIN words_advanced wa ON wa.dictionary = words.dictionary AND wa.word_id = words.word_id
|
||||
JOIN dictionaries ON dictionaries.id = words.dictionary
|
||||
WHERE words.dictionary=? AND is_public=1";
|
||||
$this->original_words = $this->db->query($query, array($this->details['externalID']))->fetchAll();
|
||||
}
|
||||
return $this->original_words;
|
||||
}
|
||||
|
||||
private function sortWords($words) {
|
||||
$sort_by = isset($this->details['settings']) && isset($this->details['settings']['sortByDefinition']) && $this->details['settings']['sortByDefinition'] === true
|
||||
? 'definition' : 'name';
|
||||
// Transliterator settings from https://stackoverflow.com/a/35178027
|
||||
$transliterator = Transliterator::createFromRules(':: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Lower(); :: NFC;', Transliterator::FORWARD);
|
||||
usort($words, function($a, $b) use($transliterator, $sort_by) {
|
||||
$word_a = $transliterator->transliterate($a[$sort_by]);
|
||||
$word_b = $transliterator->transliterate($b[$sort_by]);
|
||||
return strcasecmp($word_a, $word_b);
|
||||
});
|
||||
|
||||
if (isset($this->details['alphabeticalOrder']) && count($this->details['alphabeticalOrder']) > 0) {
|
||||
$ordering = array();
|
||||
for ($i = 0; $i < count($this->details['alphabeticalOrder']); $i++) {
|
||||
$ordering[$this->details['alphabeticalOrder'][$i]] = $i + 1;
|
||||
}
|
||||
usort($words, function($word_a, $word_b) use($sort_by, $ordering) {
|
||||
if ($word_a[$sort_by] === $word_b[$sort_by]) return 0;
|
||||
|
||||
$a_letters = str_split($word_a[$sort_by]);
|
||||
$b_letters = str_split($word_b[$sort_by]);
|
||||
|
||||
for ($i = 0; $i < count($a_letters); $i++) {
|
||||
$a = $a_letters[$i];
|
||||
if (!isset($b_letters[$i])) {
|
||||
return 1;
|
||||
}
|
||||
$b = $b_letters[$i];
|
||||
if (!isset($ordering[$a]) && !isset($ordering[$b])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($ordering[$a])) {
|
||||
return 1;
|
||||
}
|
||||
if (!isset($ordering[$b])) {
|
||||
return -1;
|
||||
}
|
||||
if ($ordering[$a] === $ordering[$b]) {
|
||||
if (count($a_letters) < count($b_letters) && $i === count($a_letters) - 1) {
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return $ordering[$a] - $ordering[$b];
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
return $words;
|
||||
}
|
||||
|
||||
private function parseReferences($details, $dictionary_id) {
|
||||
$details = strip_tags($details);
|
||||
if (preg_match_all('/\{\{.+?\}\}/', $details, $references) !== false) {
|
||||
$references = array_unique($references[0]);
|
||||
foreach($references as $reference) {
|
||||
$reference_link = $this->getWordReferenceHTML($reference, $dictionary_id);
|
||||
if ($reference_link !== $reference) {
|
||||
$details = str_replace($reference, $reference_link, $details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
private function getWordReferenceHTML($reference, $dictionary_id, $direct_link = true) {
|
||||
$word_to_find = preg_replace('/\{\{|\}\}/', '', $reference);
|
||||
$homonymn = 0;
|
||||
|
||||
if (strpos($word_to_find, ':') !== false) {
|
||||
$separator = strpos($word_to_find, ':');
|
||||
$homonymn = substr($word_to_find, $separator + 1);
|
||||
$word_to_find = substr($word_to_find, 0, $separator);
|
||||
if ($homonymn && trim($homonymn) && intval(trim($homonymn)) > 0) {
|
||||
$homonymn = intval(trim($homonymn));
|
||||
} else {
|
||||
$homonymn = false;
|
||||
}
|
||||
}
|
||||
|
||||
$target_id = false;
|
||||
$reference_ids = $this->getWordIdsFromName($word_to_find);
|
||||
|
||||
if (count($reference_ids) > 0) {
|
||||
if ($homonymn !== false && $homonymn > 0) {
|
||||
if (isset($reference_ids[$homonymn - 1])) {
|
||||
$target_id = $reference_ids[$homonymn - 1];
|
||||
}
|
||||
} else if ($homonymn !== false) {
|
||||
$target_id = $reference_ids[0];
|
||||
}
|
||||
|
||||
if ($target_id !== false) {
|
||||
if ($homonymn < 1) {
|
||||
$homonymn = 1;
|
||||
}
|
||||
$homonymn_sub_html = count($reference_ids) > 1 && $homonymn - 1 >= 0 ? '<sub>' . $homonymn . '</sub>' : '';
|
||||
$site_root = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], $dictionary_id));
|
||||
return '<span class="word-reference"><a href="'
|
||||
. ($direct_link ? $site_root . $dictionary_id . '/' : '#') . $target_id .'" '
|
||||
. ($direct_link ? 'target="_blank" ' : '') . 'title="Link to Reference">'
|
||||
. '<span class="orthographic-translation">' . $this->translateOrthography($word_to_find, $dictionary_id) . '</span>' . $homonymn_sub_html
|
||||
. '</a></span>';
|
||||
}
|
||||
}
|
||||
|
||||
return $reference;
|
||||
}
|
||||
|
||||
private function getWordIdsFromName($word_name) {
|
||||
$results = array_filter($this->getWordsAsEntered(), fn ($row) => $row['name'] == $word_name);
|
||||
$results = array_values($results);
|
||||
if ($results) {
|
||||
return array_map(function ($row) {
|
||||
return intval($row['word_id']);
|
||||
}, $results);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
private function translateOrthography($word, $dictionary) {
|
||||
if (!isset($this->translations)) {
|
||||
$this->translations = $this->getTranslations($dictionary);
|
||||
}
|
||||
foreach($this->translations as $translation) {
|
||||
$translation = array_map('trim', explode('=', $translation));
|
||||
if (count($translation) > 1 && $translation[0] !== '' && $translation[1] !== '') {
|
||||
$word = str_replace($translation[0], $translation[1], $word);
|
||||
}
|
||||
};
|
||||
return $word;
|
||||
}
|
||||
|
||||
private function getTranslations($dictionary) {
|
||||
if (is_numeric($dictionary)) {
|
||||
$query = "SELECT translations FROM dictionary_linguistics WHERE dictionary=?";
|
||||
$result = $this->db->query($query, array($dictionary))->fetch();
|
||||
if ($result) {
|
||||
return explode(PHP_EOL, $result['translations']);
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
private function getWordStats() {
|
||||
$words = $this->getWordsAsEntered();
|
||||
$word_stats = array(
|
||||
'numberOfWords' => array(
|
||||
array(
|
||||
'name' => 'Total',
|
||||
'value' => count($words),
|
||||
),
|
||||
),
|
||||
'wordLength' => array(
|
||||
'shortest' => 0,
|
||||
'longest' => 0,
|
||||
'average' => 0,
|
||||
),
|
||||
'letterDistribution' => array(
|
||||
/* array(
|
||||
'letter' => '',
|
||||
'number' => 0,
|
||||
'percentage' => 0.00,
|
||||
) */
|
||||
),
|
||||
'totalLetters' => 0,
|
||||
);
|
||||
|
||||
foreach($this->details['partsOfSpeech'] as $part_of_speech) {
|
||||
$words_with_part_of_speech = array_filter($words, function ($word) use($part_of_speech) {
|
||||
return $word['part_of_speech'] === $part_of_speech;
|
||||
});
|
||||
$word_stats['numberOfWords'][] = array(
|
||||
'name' => $part_of_speech,
|
||||
'value' => count($words_with_part_of_speech),
|
||||
);
|
||||
};
|
||||
|
||||
$word_stats['numberOfWords'][] = array(
|
||||
'name' => 'Unclassified',
|
||||
'value' => count(array_filter($words, function ($word) {
|
||||
return !in_array($word['part_of_speech'], $this->details['partsOfSpeech']);
|
||||
})),
|
||||
);
|
||||
|
||||
$total_letters = 0;
|
||||
$number_of_letters = array();
|
||||
|
||||
foreach($words as $word) {
|
||||
$shortest_word = $word_stats['wordLength']['shortest'];
|
||||
$longest_word = $word_stats['wordLength']['longest'];
|
||||
$word_letters = str_split($word['name']);
|
||||
$letters_in_word = count($word_letters);
|
||||
|
||||
$total_letters += $letters_in_word;
|
||||
|
||||
if ($shortest_word === 0 || $letters_in_word < $shortest_word) {
|
||||
$word_stats['wordLength']['shortest'] = $letters_in_word;
|
||||
}
|
||||
|
||||
if ($longest_word === 0 || $letters_in_word > $longest_word) {
|
||||
$word_stats['wordLength']['longest'] = $letters_in_word;
|
||||
}
|
||||
|
||||
foreach($word_letters as $letter) {
|
||||
$letterToUse = $this->details['settings']['caseSensitive'] ? $letter : strtolower($letter);
|
||||
if (!isset($number_of_letters[$letterToUse])) {
|
||||
$number_of_letters[$letterToUse] = 1;
|
||||
} else {
|
||||
$number_of_letters[$letterToUse]++;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$word_stats['totalLetters'] = $total_letters;
|
||||
$word_stats['wordLength']['average'] = count($words) > 0 ? round($total_letters / count($words)) : 0;
|
||||
|
||||
foreach ($number_of_letters as $letter => $number) {
|
||||
if (isset($number_of_letters[$letter])) {
|
||||
$word_stats['letterDistribution'][] = array(
|
||||
'letter' => $letter,
|
||||
'number' => $number,
|
||||
'percentage' => $number / $total_letters,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
usort($word_stats['letterDistribution'], function ($a, $b) {
|
||||
if ($a['percentage'] === $b['percentage']) return 0;
|
||||
return ($a['percentage'] > $b['percentage']) ? -1 : 1;
|
||||
});
|
||||
|
||||
return $word_stats;
|
||||
}
|
||||
}
|
|
@ -1,21 +1,41 @@
|
|||
<?php
|
||||
require_once(realpath(dirname(__FILE__) . '/./api/Response.php'));
|
||||
$show_upgrade_screen = false;
|
||||
|
||||
if ($show_upgrade_screen) {
|
||||
$html = '<h1>Code Update in Progress</h1><p>Please refresh the page in 10 minutes.</p>';
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
$view = isset($_GET['view']) ? $_GET['view'] : false;
|
||||
|
||||
function utf8ize($d) {
|
||||
if (is_array($d)) {
|
||||
foreach ($d as $k => $v) {
|
||||
$d[$k] = utf8ize($v);
|
||||
}
|
||||
} else if (is_string ($d)) {
|
||||
return mb_convert_encoding($d, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
return $d;
|
||||
}
|
||||
|
||||
switch ($view) {
|
||||
case 'dictionary': {
|
||||
$html = file_get_contents(realpath(dirname(__FILE__) . '/./template-view.html'));
|
||||
$dict = isset($_GET['dict']) ? $_GET['dict'] : false;
|
||||
if ($dict !== false) {
|
||||
require_once(realpath(dirname(__FILE__) . '/./api/Dictionary.php'));
|
||||
$dictionary = new Dictionary();
|
||||
$dictionary_data = $dictionary->getPublicDictionaryDetails($dict);
|
||||
require_once(realpath(dirname(__FILE__) . '/./api/PublicDictionary.php'));
|
||||
$dictionary = new PublicDictionary($dict);
|
||||
$dictionary_data = $dictionary->details;
|
||||
if ($dictionary_data !== false) {
|
||||
$dictionary_data['words'] = $dictionary->getPublicDictionaryWords($dict);
|
||||
$dictionary_data['words'] = $dictionary->words;
|
||||
$html = str_replace('{{dict}}', $dict, $html);
|
||||
$html = str_replace('{{dict_name}}', $dictionary_data['name'] . ' ' . $dictionary_data['specification'], $html);
|
||||
$html = str_replace('{{public_name}}', $dictionary_data['createdBy'], $html);
|
||||
$dictionary_json = json_encode($dictionary_data);
|
||||
$dictionary_name = (isset($dictionary_data['name']) ? $dictionary_data['name'] : 'Unnamed')
|
||||
. ' ' . (isset($dictionary_data['specification']) ? $dictionary_data['specification'] : 'Dictionary');
|
||||
$html = str_replace('{{dict_name}}', $dictionary_name, $html);
|
||||
$html = str_replace('{{public_name}}', isset($dictionary_data['createdBy']) ? $dictionary_data['createdBy'] : 'Someone', $html);
|
||||
$dictionary_json = json_encode(utf8ize($dictionary_data));
|
||||
$html = str_replace('{{dict_json}}', addslashes($dictionary_json), $html);
|
||||
} else {
|
||||
$html = str_replace('{{dict}}', 'error', $html);
|
||||
|
@ -32,11 +52,12 @@ switch ($view) {
|
|||
$dict = isset($_GET['dict']) ? $_GET['dict'] : false;
|
||||
$word = isset($_GET['word']) ? $_GET['word'] : false;
|
||||
if ($dict !== false && $word !== false) {
|
||||
require_once(realpath(dirname(__FILE__) . '/./api/Dictionary.php'));
|
||||
$dictionary = new Dictionary();
|
||||
$dictionary_data = $dictionary->getPublicDictionaryDetails($dict);
|
||||
require_once(realpath(dirname(__FILE__) . '/./api/PublicDictionary.php'));
|
||||
$dictionary = new PublicDictionary($dict, true);
|
||||
$dictionary_data = $dictionary->details;
|
||||
if ($dictionary_data !== false) {
|
||||
$dictionary_name = $dictionary_data['name'] . ' ' . $dictionary_data['specification'];
|
||||
$dictionary_name = (isset($dictionary_data['name']) ? $dictionary_data['name'] : 'Unnamed')
|
||||
. ' ' . (isset($dictionary_data['specification']) ? $dictionary_data['specification'] : 'Dictionary');
|
||||
$word_data = $dictionary->getSpecificPublicDictionaryWord($dict, $word);
|
||||
if ($word_data === false) {
|
||||
$word_data = array(
|
||||
|
@ -54,7 +75,7 @@ switch ($view) {
|
|||
$html = str_replace('{{dict}}', $dict, $html);
|
||||
$html = str_replace('{{dict_name}}', $word_data['name'] . ' in the ' . $dictionary_name, $html);
|
||||
$html = str_replace('{{public_name}}', $dictionary_data['createdBy'], $html);
|
||||
$dictionary_json = json_encode($dictionary_data);
|
||||
$dictionary_json = json_encode(utf8ize($dictionary_data));
|
||||
$html = str_replace('{{dict_json}}', addslashes($dictionary_json), $html);
|
||||
} else {
|
||||
$html = str_replace('{{dict}}', 'error', $html);
|
||||
|
@ -85,6 +106,7 @@ switch ($view) {
|
|||
$html = str_replace('{{announcements}}', $announcements_html, $html);
|
||||
|
||||
$upup_files = array(
|
||||
'goodie-bag.js',
|
||||
'src.js',
|
||||
'main.css',
|
||||
'help.html',
|
||||
|
@ -112,7 +134,7 @@ switch ($view) {
|
|||
oldLoad && oldLoad();
|
||||
if (UpUp) {
|
||||
UpUp.start({
|
||||
'cache-version': '2.0.0',
|
||||
'cache-version': '2.2.1',
|
||||
'content-url': 'offline.html',
|
||||
'assets': [
|
||||
\"" . implode('","', $files) . "\"
|
||||
|
|
|
@ -15,6 +15,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
|
||||
h4 {
|
||||
display: inline-block;
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.share-link {
|
||||
margin-left: $general-padding !important;
|
||||
line-height: 16px !important;
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
font-family: $font;
|
||||
font-size: 13pt;
|
||||
transition: all 0.2s ease-in;
|
||||
|
||||
* {
|
||||
transition: all 0.2s ease-in;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
display: block;
|
||||
padding: 5px $general-padding;
|
||||
padding: 5px calc($general-padding / 2);
|
||||
margin: 0 0 5px;
|
||||
|
||||
&#top {
|
||||
|
|
|
@ -8,23 +8,30 @@ p, span {
|
|||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]),
|
||||
select,
|
||||
textarea {
|
||||
font-size: 12pt;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
line-height: 120%;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: $general-padding;
|
||||
}
|
||||
margin-bottom: $general-padding;
|
||||
|
||||
small {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"]) {
|
||||
padding-bottom: 2px;
|
||||
line-height: 130%;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"]),
|
||||
|
@ -33,23 +40,27 @@ label {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.label-button {
|
||||
display: inline-block;
|
||||
padding: 3px 9px;
|
||||
border-radius: 3px;
|
||||
line-height: 30px;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 80%;
|
||||
font-weight: normal;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
line-height: 80% !important;
|
||||
line-height: 0.85 !important;
|
||||
padding: 3px 3px 5px;
|
||||
|
||||
&.small {
|
||||
font-size: 80%;
|
||||
line-height: 25px;
|
||||
line-height: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,18 +68,18 @@ label {
|
|||
display: inline-block;
|
||||
padding: 3px 9px;
|
||||
border-radius: 3px;
|
||||
line-height: 30px;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 70%;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
line-height: 70% !important;
|
||||
line-height: 0.8 !important;
|
||||
padding: 2px 2px 4px;
|
||||
|
||||
&.small {
|
||||
font-size: 80%;
|
||||
line-height: 25px;
|
||||
line-height: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,11 +92,15 @@ ul {
|
|||
display: inline-block;
|
||||
padding: 3px 9px;
|
||||
border-radius: 3px;
|
||||
line-height: 30px;
|
||||
line-height: 1.5;
|
||||
|
||||
&.small {
|
||||
font-size: 80%;
|
||||
line-height: 25px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&+.tag {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,24 +110,28 @@ span .tag {
|
|||
display: inline-block;
|
||||
padding: 3px 9px;
|
||||
border-radius: 3px;
|
||||
line-height: 30px;
|
||||
line-height: 1.5;
|
||||
|
||||
&.small {
|
||||
font-size: 80%;
|
||||
line-height: 25px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&+.tag {
|
||||
border-left: none;
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
&+span .tag {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 3px 9px;
|
||||
border-radius: 3px;
|
||||
line-height: 30px;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
|
@ -120,7 +139,11 @@ span .tag {
|
|||
|
||||
&.small {
|
||||
font-size: 80%;
|
||||
line-height: 25px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&+.button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,6 +153,7 @@ span .tag {
|
|||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
|
||||
.modal-background {
|
||||
position: absolute;
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
max-height: unset;
|
||||
margin: 0 auto;
|
||||
width: 90%;
|
||||
padding: $general-padding $general-padding ($general-padding / 2);
|
||||
padding: $general-padding $general-padding calc($general-padding / 2);
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
|
@ -45,7 +45,7 @@
|
|||
left: unset;
|
||||
bottom: unset;
|
||||
right: unset;
|
||||
padding: ($general-padding / 2) $general-padding ($general-padding * 0.25);
|
||||
padding: calc($general-padding / 2) $general-padding ($general-padding * 0.25);
|
||||
font-size: 90%;
|
||||
|
||||
.split {
|
||||
|
@ -109,6 +109,7 @@
|
|||
border-radius: 5px;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
|
@ -196,6 +197,7 @@
|
|||
|
||||
header {
|
||||
position: relative;
|
||||
padding-right: 70px;
|
||||
|
||||
.word-option-button {
|
||||
position: absolute;
|
||||
|
@ -279,7 +281,11 @@ $nav-font-height: 16px;
|
|||
|
||||
#editDescription {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
#editCustomCSS {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +349,7 @@ $nav-font-height: 16px;
|
|||
bottom: $general-padding;
|
||||
right: $general-padding;
|
||||
max-width: 300px;
|
||||
z-index: 10;
|
||||
z-index: 15;
|
||||
|
||||
.message {
|
||||
position: relative;
|
||||
|
@ -396,7 +402,7 @@ $nav-font-height: 16px;
|
|||
right: 0;
|
||||
z-index: 5;
|
||||
text-align: center;
|
||||
padding: $general-padding / 2;
|
||||
padding: calc($general-padding / 2);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@media (max-width: 750px) {
|
||||
|
||||
html, body {
|
||||
font-size: 90%;
|
||||
line-height: 90%;
|
||||
font-size: 12pt;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
header {
|
||||
|
@ -17,7 +17,6 @@ main {
|
|||
#sideColumn {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: block;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
@ -29,7 +28,7 @@ main {
|
|||
|
||||
article {
|
||||
dl {
|
||||
padding: 0 ($general-padding / 2);
|
||||
padding: 0 calc($general-padding / 2);
|
||||
|
||||
dd {
|
||||
margin: 0 ($general-padding * 0.75);
|
||||
|
|
|
@ -23,13 +23,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
z-index: 10;
|
||||
|
||||
.modal-content .close-button {
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
.modal .modal-content .close-button {
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.split {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#top {
|
||||
#title {
|
||||
font-size: 13pt;
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
@ -14,25 +14,26 @@
|
|||
#searchModal {
|
||||
.modal-content {
|
||||
width: 100%;
|
||||
padding: $general-padding ($general-padding / 2) ($general-padding / 4);
|
||||
padding: $general-padding calc($general-padding / 2) calc($general-padding / 4);
|
||||
|
||||
#searchBox {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
section+footer {
|
||||
padding: ($general-padding / 2) 0;
|
||||
padding: calc($general-padding / 2) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$mobile-word-form-size: 32px;
|
||||
#mobileWordFormShow {
|
||||
position: fixed;
|
||||
top: $header-height;
|
||||
top: $header-height - calc($mobile-word-form-size / 2);
|
||||
left: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: $mobile-word-form-size;
|
||||
height: $mobile-word-form-size;
|
||||
display: block;
|
||||
border-radius: 0 3px 3px 0;
|
||||
font-size: 30px;
|
||||
|
@ -48,10 +49,14 @@
|
|||
display: none;
|
||||
width: 95%;
|
||||
max-width: unset;
|
||||
top: $header-height + 32px;
|
||||
max-height: 85%;
|
||||
top: $header-height + calc($mobile-word-form-size / 2);
|
||||
left: 0;
|
||||
right: 3%;
|
||||
z-index: 1;
|
||||
|
||||
label:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#detailsSection {
|
||||
|
@ -60,14 +65,16 @@
|
|||
}
|
||||
|
||||
#detailsPanel {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
padding: 10px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.entry {
|
||||
.pronunciation {
|
||||
margin: 0 ($general-padding / 2);
|
||||
margin: 0 calc($general-padding / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +88,17 @@
|
|||
}
|
||||
#editDescription {
|
||||
width: 100%;
|
||||
height: 260px;
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
#bottom {
|
||||
position: relative;
|
||||
bottom: unset;
|
||||
|
||||
.separator {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
#top {
|
||||
background-color: $header-color;
|
||||
border-bottom: 1px solid darken($header-color, 2);
|
||||
box-shadow: 0px 4px 5px 0px $dark;
|
||||
box-shadow: none;
|
||||
|
||||
#title {
|
||||
#lexi {
|
||||
|
@ -156,13 +156,13 @@
|
|||
#wordForm {
|
||||
background-color: $word-form-color;
|
||||
border: 1px solid darken($word-form-color, 2);
|
||||
box-shadow: 4px 4px 5px 0px $dark;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#mainColumn {
|
||||
background-color: $dictionary-color;
|
||||
border: 1px solid darken($dictionary-color, 2);
|
||||
box-shadow: 4px 4px 5px 0px $dark;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#detailsSection {
|
||||
|
@ -184,7 +184,7 @@
|
|||
|
||||
.announcement {
|
||||
background-color: saturate(lighten($footer-color, 20%), 20%);
|
||||
box-shadow: 4px 4px 5px 0px $dark;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.entry {
|
||||
|
@ -238,7 +238,7 @@
|
|||
#bottom {
|
||||
background-color: $footer-color;
|
||||
border-top: 1px solid darken($footer-color, 2);
|
||||
box-shadow: 0px -4px 5px 0px $dark;
|
||||
box-shadow: none;
|
||||
|
||||
a {
|
||||
color: $light;
|
||||
|
|
|
@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS `dictionaries` (
|
|||
`case_sensitive` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`sort_by_definition` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`theme` varchar(20) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'default',
|
||||
`custom_css` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'CSS',
|
||||
`is_public` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`last_updated` int(11) DEFAULT NULL,
|
||||
`created_on` int(11) NOT NULL,
|
||||
|
@ -34,13 +35,16 @@ DELIMITER ;
|
|||
CREATE TABLE IF NOT EXISTS `dictionary_linguistics` (
|
||||
`dictionary` int(11) NOT NULL,
|
||||
`parts_of_speech` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Comma-separated',
|
||||
`alphabetical_order` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
|
||||
`consonants` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
|
||||
`vowels` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
|
||||
`blends` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
|
||||
`phonology_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
|
||||
`onset` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
|
||||
`nucleus` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
|
||||
`coda` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
|
||||
`exceptions` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
|
||||
`phonotactics_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
|
||||
`translations` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Newline-separated; Translates left character(s) to right character(s)',
|
||||
`orthography_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
|
||||
`grammar_notes` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
|
||||
UNIQUE KEY `dictionary` (`dictionary`)
|
||||
|
@ -86,3 +90,16 @@ CREATE TABLE IF NOT EXISTS `words` (
|
|||
`created_on` int(11) NOT NULL,
|
||||
UNIQUE KEY `unique_index` (`dictionary`,`word_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER IF NOT EXISTS `delete_word_advanced` AFTER DELETE ON `words` FOR EACH ROW DELETE FROM words_advanced WHERE words_advanced.dictionary=old.dictionary AND words_advanced.word_id=old.word_id
|
||||
$$
|
||||
DELIMITER ;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `words_advanced` (
|
||||
`dictionary` int(11) NOT NULL,
|
||||
`word_id` int(11) NOT NULL,
|
||||
`etymology` text NOT NULL COMMENT 'Comma-separated',
|
||||
`related` text NOT NULL COMMENT 'Comma-separated',
|
||||
`principal_parts` text NOT NULL COMMENT 'Comma-separated',
|
||||
UNIQUE KEY `dictionary_word_id` (`dictionary`,`word_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Lexiconga">
|
||||
<meta property="og:description" content="The quick and easy dictionary builder for constructed languages.">
|
||||
<meta property="og:image" content="processedImages/logo.png">
|
||||
<meta property="og:image" content="processedImages/social.jpg">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image:alt" content="Lexiconga logo">
|
||||
|
@ -81,6 +81,9 @@
|
|||
<label>Exact Words
|
||||
<input type="checkbox" id="searchExactWords">
|
||||
</label>
|
||||
<label>Translations
|
||||
<input type="checkbox" id="searchOrthography">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split">
|
||||
|
@ -136,6 +139,25 @@
|
|||
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="wordDetails" placeholder="Markdown formatting allowed"></textarea>
|
||||
</label>
|
||||
<label>
|
||||
<a id="expandAdvancedForm" class="small button expand-advanced-form">Show Advanced Fields</a>
|
||||
</label>
|
||||
<div id="advancedForm" class="advanced-word-form" style="display:none;">
|
||||
<label>Details Field Templates
|
||||
<select id="templateSelect" class="template-select">
|
||||
</select>
|
||||
<small>Choose one to fill the details field. (Note: Will erase anything currently there.)</small>
|
||||
</label>
|
||||
<label>Etymology / Root Words<br>
|
||||
<input id="wordEtymology" maxlength="2500" placeholder="comma,separated,root,words">
|
||||
</label>
|
||||
<label>Related Words<br>
|
||||
<input id="wordRelated" maxlength="2500" placeholder="comma,separated,related,words">
|
||||
</label>
|
||||
<label>Principal Parts<a href="https://en.wikipedia.org/wiki/Principal_parts" target="_blank" class="label-button">What's This?</a><br>
|
||||
<input id="wordPrincipalParts" maxlength="2500" placeholder="comma,separated,principal,parts">
|
||||
</label>
|
||||
</div>
|
||||
<div id="wordErrorMessage"></div>
|
||||
<a class="button" id="addWordButton">Add Word</a>
|
||||
</form>
|
||||
|
@ -144,7 +166,7 @@
|
|||
<section id="mainColumn">
|
||||
{{announcements}}
|
||||
<section id="detailsSection">
|
||||
<h2 id="dictionaryName">Dictionary Name</h2>
|
||||
<h1 id="dictionaryName">Dictionary Name</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>Description</li><li>Details</li><li>Stats</li><li id="editDictionaryButton">Edit</li>
|
||||
|
@ -175,7 +197,6 @@
|
|||
<footer id="bottom">
|
||||
<a href="https://liberapay.com/robbieantenesse" target="_blank" rel="noopener" class="small button">Support Lexiconga</a>
|
||||
<a href="https://blog.lexicon.ga" target="_blank" rel="noopener" class="small button">Blog</a>
|
||||
<a href="./advertising.html" target="_blank" class="small button">Advertise</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/issues" target="_blank" rel="noopener" class="small button">Issues</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/releases" target="_blank" rel="noopener" class="small button">Updates</a>
|
||||
<span class="separator">|</span>
|
||||
|
@ -191,7 +212,7 @@
|
|||
<section>
|
||||
<form class="split two">
|
||||
<div>
|
||||
<h3>General Settings</h3>
|
||||
<h2>General Settings</h2>
|
||||
<label>Use IPA Auto-Fill
|
||||
<input id="settingsUseIPA" type="checkbox" checked><br />
|
||||
<small>Check this to use character combinations to input International Phonetic Alphabet characters into
|
||||
|
@ -202,6 +223,11 @@
|
|||
<input id="settingsUseHotkeys" type="checkbox" checked><br />
|
||||
<small>Check this to enable keyboard combinations to perform different helpful actions.</small>
|
||||
</label>
|
||||
|
||||
<label>Show Advanced Fields By Default
|
||||
<input id="settingsShowAdvanced" type="checkbox"><br />
|
||||
<small>Check this to make the advanced fields show on word forms without needing to click the "Show Advanced Fields" button.</small>
|
||||
</label>
|
||||
|
||||
<label>Default Theme <small>(the theme new dictionaries will use)</small>
|
||||
<select id="settingsDefaultTheme">
|
||||
|
@ -217,9 +243,33 @@
|
|||
<option value="grape">Grape</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<h4>Templates for Details Fields</h4>
|
||||
<p>Templates created here are saved only to your local browser.</p>
|
||||
<label>Saved Templates <a id="createTemplateButton" class="label-button">Create New Template</a>
|
||||
<select id="savedDetailsTemplates" class="template-select">
|
||||
</select>
|
||||
</label>
|
||||
<div id="templateFields" style="display:none;">
|
||||
<label>Template Name
|
||||
<input id="templateNameField"><br />
|
||||
<small>If you have chosen a template above, this will overwrite the chosen template.</small>
|
||||
</label>
|
||||
<label>Template<a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="templateTextarea" placeholder="**Era:**
|
||||
|
||||
**Dialect:**
|
||||
|
||||
etc."></textarea>
|
||||
</label>
|
||||
<a id="saveTemplateButton" class="button">Save Template</a>
|
||||
<a id="deleteTemplateButton" class="red button">Delete Template</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="accountActions"></div>
|
||||
<div id="accountSettings"></div>
|
||||
</div>
|
||||
<div id="accountActions"></div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
|
@ -242,9 +292,11 @@
|
|||
<section id="editDescriptionTab">
|
||||
<label>Name<br>
|
||||
<input id="editName" maxlength="50">
|
||||
<small>Won't update if left blank.</small>
|
||||
</label>
|
||||
<label>Specification<br>
|
||||
<input id="editSpecification" maxlength="50">
|
||||
<small>Won't update if left blank.</small>
|
||||
</label>
|
||||
<label>Description<a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editDescription"></textarea>
|
||||
|
@ -255,10 +307,14 @@
|
|||
<label>Parts of Speech <small>(Comma Separated List)</small><br>
|
||||
<input id="editPartsOfSpeech" maxlength="2500" placeholder="Noun,Adjective,Verb">
|
||||
</label>
|
||||
<label>Alphabetical Order <small>(Comma Separated List. Include every letter!)</small><br>
|
||||
<input id="editAlphabeticalOrder" disabled value="English Alphabet">
|
||||
<label>Alphabetical Order <small>(Space Separated List)</small><br>
|
||||
<input id="editAlphabeticalOrder" placeholder="a A b B c C d D ...">
|
||||
<a class="label-help-button" onclick="alert('Include every letter and case! Any letters used in your words that are not specified will be sorted in the default order below your alphabetically custom-sorted words.\n\nLexiconga can only sort by single characters and will sort by the words AS ENTERED, not using orthographic translation.')">
|
||||
Field Info
|
||||
</a>
|
||||
<small>Leave blank for default (case-insensitive ASCII/Unicode sorting)</small>
|
||||
</label>
|
||||
<h3>Phonology</h3>
|
||||
<h2>Phonology</h2>
|
||||
<div class="split three">
|
||||
<div>
|
||||
<label>Consonants<br>
|
||||
|
@ -285,7 +341,10 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Phonotactics</h3>
|
||||
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editPhonologyNotes"></textarea>
|
||||
</label>
|
||||
<h2>Phonotactics</h2>
|
||||
<div class="split three">
|
||||
<div>
|
||||
<label>Onset<br>
|
||||
|
@ -306,14 +365,21 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label>Exceptions <small>(Markdown-enabled)</small><br>
|
||||
<textarea id="editExceptions"></textarea>
|
||||
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editPhonotacticsNotes"></textarea>
|
||||
</label>
|
||||
<h2>Orthography</h2>
|
||||
<label>Translations <small>(One translation per line)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editTranslations" placeholder="ai=I
|
||||
AA=ay
|
||||
ou=ow"></textarea>
|
||||
<small>Use format: <code>sequence=replacement</code></small><br>
|
||||
<small>Translations occur in the order specified here, so try to avoid double translations!</small>
|
||||
</label>
|
||||
<h3>Orthography</h3>
|
||||
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editOrthography"></textarea>
|
||||
</label>
|
||||
<h3>Grammar</h3>
|
||||
<h2>Grammar</h2>
|
||||
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editGrammar"></textarea>
|
||||
</label>
|
||||
|
@ -346,10 +412,13 @@
|
|||
<option value="grape">Grape</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Custom Styling <small>(CSS Only)</small><a class="label-button maximize-button">Maximize</a><br>
|
||||
<textarea id="editCustomCSS" placeholder=".orthographic-translation {font-family: serif;}"></textarea>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section id="editActionsTab" style="display:none;">
|
||||
<h3>Import / Export</h3>
|
||||
<h2>Import / Export</h2>
|
||||
<div class="split two">
|
||||
<div>
|
||||
<p>
|
||||
|
@ -361,7 +430,7 @@
|
|||
<label class="button">Import Words <input type="file" id="importWordsCSV" accept="text/csv, .csv"><br>
|
||||
<small>Import a CSV file of words.</small>
|
||||
</label>
|
||||
<a class="small button" download="Lexiconga_import-template.csv" href="data:text/csv;charset=utf-8,%22word%22,%22pronunciation%22,%22part of speech%22,%22definition%22,%22explanation%22%0A">Download an example file with the correct formatting</a>
|
||||
<a class="small button" download="Lexiconga_import-template.csv" href="data:text/csv;charset=utf-8,%22word%22,%22pronunciation%22,%22part of speech%22,%22definition%22,%22explanation%22,%22etymology %28comma-separated%29%22,%22related words %28comma-separated%29%22,%22principal parts %28comma-separated%29%22%0A">Download an example file with the correct formatting</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
<meta name="keywords" content="conlanging, dictionary, dictionaries, lexicon, conlangs, constructed languages, glossopoeia, builder, app, tool">
|
||||
|
||||
<meta property="og:site_name" content="Lexiconga">
|
||||
<meta property="og:url" content="http://lexicon.ga/{{dict}}">
|
||||
<meta property="og:url" content="https://lexicon.ga/{{dict}}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="{{dict_name}}">
|
||||
<meta property="og:description" content="A Lexiconga dictionary by {{public_name}}">
|
||||
<meta property="og:image" content="processedImages/logo.png">
|
||||
<meta property="og:image" content="processedImages/social.jpg">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image:alt" content="Lexiconga logo">
|
||||
|
@ -81,6 +81,9 @@
|
|||
<label>Exact Words
|
||||
<input type="checkbox" id="searchExactWords">
|
||||
</label>
|
||||
<label>Translations
|
||||
<input type="checkbox" id="searchOrthography">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split">
|
||||
|
@ -124,7 +127,7 @@
|
|||
</ul>
|
||||
</nav>
|
||||
<article id="detailsPanel" style="display:none;">
|
||||
<p>The dictionary details</p>
|
||||
<p>Loading Dictionary Details</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
@ -148,7 +151,6 @@
|
|||
<footer id="bottom">
|
||||
<a href="https://liberapay.com/robbieantenesse" target="_blank" rel="noopener" class="small button">Support Lexiconga</a>
|
||||
<a href="https://blog.lexicon.ga" target="_blank" rel="noopener" class="small button">Blog</a>
|
||||
<a href="./advertising.html" target="_blank" class="small button">Advertise</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/issues" target="_blank" rel="noopener" class="small button">Issues</a>
|
||||
<a href="https://github.com/Alamantus/Lexiconga/releases" target="_blank" rel="noopener" class="small button">Updates</a>
|
||||
<span class="separator">|</span>
|
||||
|
@ -159,4 +161,4 @@
|
|||
|
||||
<div id="messagingSection"></div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue