Compare commits

...

334 Commits

Author SHA1 Message Date
Robbie Antenesse bfb74a0000 Fix scss disliking `/` as division sign for no reason 2022-08-26 12:56:34 -06:00
Robbie Antenesse 4c8f896462 Remove ads 2022-08-26 12:56:01 -06:00
Robbie Antenesse be44c4c7a5 Upgrade dependencies as far as they'll go without screwing things up 2021-12-03 22:10:11 -07:00
Robbie Antenesse 4de0a7bf8b
Add missing Translations search filter to View template 2021-07-26 11:11:02 -06:00
Robbie Antenesse 459edda009 Upgrade dependencies 2021-02-09 14:41:51 -07:00
Robbie Antenesse 9fcbb08468 Fix og:url on view template to HTTPS 2020-11-03 11:58:25 -07:00
Robbie Antenesse 2f0aa6e14c Update announcement to paid features poll 2020-11-03 11:11:37 -07:00
Robbie Antenesse 76e3f77b0b
Merge pull request #52 from Alamantus/dependabot/npm_and_yarn/bl-4.0.3
Bump bl from 4.0.2 to 4.0.3
2020-10-18 14:50:11 -06:00
dependabot[bot] f1b99960f3
Bump bl from 4.0.2 to 4.0.3
Bumps [bl](https://github.com/rvagg/bl) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-18 20:48:20 +00:00
Robbie Antenesse d64890ec12 Upgrade devDependencies 2020-10-18 14:47:22 -06:00
Robbie Antenesse acdc5cbdaa Correctly escape quotes in rendered input tags
Bump patch version to ensure fixes are loaded
2020-10-18 14:43:17 -06:00
Robbie Antenesse efa623af85 Update dependencies 2020-10-08 15:23:29 -06:00
Robbie Antenesse e1222a43f6 Fix incorrect variable name; Correctly display public dict 2020-10-08 14:56:59 -06:00
Robbie Antenesse 3dd370a375 Update create account text 2020-08-03 23:15:31 -06:00
Robbie Antenesse 34459b40a4 Comment out unused pagination code
- The unused code kept getting bundled, so this should prevent that.
2020-08-03 22:58:29 -06:00
Robbie Antenesse 4318f90263 Upgrade parcel-bundler version, I guess 2020-08-03 22:57:39 -06:00
Robbie Antenesse d98cf9a584 Remove expired ads 2020-08-03 22:48:44 -06:00
Robbie Antenesse f388620d3e Update dependencies 2020-08-03 16:52:43 -06:00
Robbie Antenesse e3c4ce7666 Merge branch 'new-features-wave-2' 2020-08-03 16:43:19 -06:00
Robbie Antenesse 4228ac8063 Add announcement for Wave 2 2020-08-03 16:40:56 -06:00
Robbie Antenesse b61f128745 Make modals scroll to top when opened 2020-08-03 16:33:56 -06:00
Robbie Antenesse 5a728dbd7c Ensure template editor scrolls to display buttons 2020-08-03 16:17:18 -06:00
Robbie Antenesse b87f4e7cad Hide template field when none exist 2020-08-03 16:07:27 -06:00
Robbie Antenesse 5e803d988e Remove edited words if not matching search 2020-08-03 15:50:20 -06:00
Robbie Antenesse 15ad17e1d7 Bump minor version to 2.2.0 2020-07-31 16:41:02 -06:00
Robbie Antenesse a73b74ff71 Save dictionary after editing word 2020-07-31 16:25:51 -06:00
Robbie Antenesse ec37fc53a5 Add new fields to import/export words 2020-07-31 16:25:26 -06:00
Robbie Antenesse b3507128a6 Update help file with new fields 2020-07-31 15:56:35 -06:00
Robbie Antenesse 54cc74b468 Make sure Word Form always displays above footer 2020-07-31 15:20:11 -06:00
Robbie Antenesse f6446eedc9 Add Details field templates 2020-07-31 15:14:41 -06:00
Robbie Antenesse ce06308c1d Make editing words render in place instead of whole list 2020-07-31 12:20:06 -06:00
Robbie Antenesse abacf7a56a Prevent entry headers from going beneath options button 2020-07-31 12:15:39 -06:00
Robbie Antenesse fb42b0ef2b Improve word forms
- Fix not clearing all fields
- Show advanced field if edited word has advanced data
2020-07-31 11:39:54 -06:00
Robbie Antenesse 6d7cdf68a9 Fix word reference finding 2020-07-31 11:29:33 -06:00
Robbie Antenesse c2d02ceccb Merge branch 'master' of https://github.com/Alamantus/Lexiconga 2020-07-17 16:57:40 -06:00
Robbie Antenesse bd8a3f475a
Merge pull request #49 from Alamantus/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-17 11:02:38 -06:00
dependabot[bot] b6811a07e4
Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-17 03:25:46 +00:00
Robbie Antenesse 90e0553f4e Update dependencies in yarn.lock 2020-07-06 14:58:38 -06:00
Robbie Antenesse adb95fbae3 Include principal parts in searches by name 2020-05-08 00:47:18 -06:00
Robbie Antenesse 8cc3b469f9 Display principal parts on public dictionary 2020-05-08 00:02:51 -06:00
Robbie Antenesse e103420245 Add Prinipal Parts for words 2020-05-07 23:57:27 -06:00
Robbie Antenesse d5e2ddec93 Add related to db structure 2020-05-07 23:23:42 -06:00
Robbie Antenesse 8f6dad8a84 Make word references link to hash unless individual 2020-05-07 23:20:47 -06:00
Robbie Antenesse a30bdf5d63 Provide etymology & related in public word view 2020-05-07 23:12:49 -06:00
Robbie Antenesse 4d63aa9034 Display related words in public dictionary render 2020-05-07 23:09:19 -06:00
Robbie Antenesse d1bcfc5b97 Update offline to show etymology & related fields 2020-05-04 00:28:01 -06:00
Robbie Antenesse e2d3164136 Add Related Words field & display 2020-05-04 00:27:50 -06:00
Robbie Antenesse 69c0539370 Remove possible error code on view search
Strip out "Search Translations" filter options
2020-05-03 23:25:49 -06:00
Robbie Antenesse 4d3132e151 Render etymology in public view 2020-05-03 23:25:40 -06:00
Robbie Antenesse 40feae7194 Display word etymology if is present 2020-05-03 22:28:31 -06:00
Robbie Antenesse 65cf421cbe Add setting to set default advanced form display 2020-04-30 01:20:10 -06:00
Robbie Antenesse 98cdd763f8 Improve styling of advanced form toggle 2020-04-30 00:38:45 -06:00
Robbie Antenesse 006f020424 Put etymology in new words_advanced table 2020-04-30 00:28:30 -06:00
Robbie Antenesse 5f15ec6353 Trim and filter etymology entries into array 2020-04-30 00:14:00 -06:00
Robbie Antenesse 4fee98c8fe Update example word data structure in default 2020-04-29 23:51:53 -06:00
Robbie Antenesse 4c4ec4536f Update yarn lock 2020-04-29 23:51:16 -06:00
Robbie Antenesse d4e7df495d Merge branch 'master' into new-features-wave-2 2020-04-29 23:07:45 -06:00
Robbie Antenesse 7bc01c79b7 Upgrade dependencies to latest versions 2020-04-29 22:22:28 -06:00
Robbie Antenesse 3a36817686 Run Yarn Upgrade 2020-04-19 12:54:26 -06:00
Robbie Antenesse 36898ff670
Merge pull request #46 from Alamantus/dependabot/npm_and_yarn/acorn-5.7.4
Bump acorn from 5.7.3 to 5.7.4
2020-03-16 16:57:23 -06:00
dependabot[bot] 46b1b5a01f
Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-14 23:52:28 +00:00
Robbie Antenesse 143bde30f0 Add announcement about fixes since last release 2020-03-04 10:09:54 -07:00
Robbie Antenesse 1d143e1d98 Update marked & papaparse dependencies 2020-03-04 09:54:37 -07:00
Robbie Antenesse 18f460f2e4 Update devDependencies 2020-03-04 09:26:00 -07:00
Robbie Antenesse 689a5d3a59 Merge branch 'master' of https://github.com/Alamantus/Lexiconga 2020-03-04 09:22:43 -07:00
Robbie Antenesse 8d07a26d10 Update .htaccess to fix Chrome security warning 2020-03-04 09:22:39 -07:00
Robbie Antenesse 1ab93d69ae Bump patch version for lastUpdated fix 2020-03-03 21:05:46 -07:00
Robbie Antenesse ed9eba7137 Fix mistake that prevented last_updated from being set 2020-03-03 20:59:20 -07:00
Robbie Antenesse 5fa06b31c2 Upgrade Sharp devDependency to make it install correctly 2020-03-03 15:00:32 -07:00
Robbie Antenesse e118cb8d14 Update README with note about windows-build-tools 2020-03-03 10:03:05 -07:00
Robbie Antenesse a9ae7f938c Update ads and bump patch version 2020-01-24 15:57:31 -07:00
Robbie Antenesse 6fad67da93 Merge branch 'master' of https://github.com/Alamantus/Lexiconga 2019-12-15 13:25:39 -07:00
Robbie Antenesse 7a80be9730 Improve padding in fields 2019-12-15 13:24:25 -07:00
Robbie Antenesse 9965dba80e Add hover text to all ipa-table fields 2019-12-15 13:08:03 -07:00
Robbie Antenesse c25114b9fd Parse word references in all Details notes fields 2019-12-13 10:39:44 -07:00
Robbie Antenesse 1b9a6fcda6 Add tone characters to IPA table chart 2019-12-12 20:59:38 -07:00
Robbie Antenesse abf0bc08a0
Merge pull request #41 from Alamantus/dependabot/npm_and_yarn/serialize-to-js-3.0.1 2019-12-06 17:45:32 -07:00
dependabot[bot] d3559d5465
Bump serialize-to-js from 3.0.0 to 3.0.1
Bumps [serialize-to-js](https://github.com/commenthol/serialize-to-js) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/commenthol/serialize-to-js/releases)
- [Commits](https://github.com/commenthol/serialize-to-js/compare/v3.0.0...v3.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-06 23:57:48 +00:00
Robbie Antenesse 484426d98b Run `yarn upgrade` to update all dependencies 2019-12-02 10:13:21 -07:00
Robbie Antenesse 331d285899 Update ads for GUTS+ PDF 2019-12-02 10:08:42 -07:00
Robbie Antenesse 03974b9d45 Remove box shadow from dark theme 2019-12-01 22:00:13 -07:00
Robbie Antenesse b20c6703ef Make modals stay above footer; Bump version 2019-12-01 21:25:56 -07:00
Robbie Antenesse 981490473f Process etymology field in the backend 2019-10-01 14:53:15 -06:00
Robbie Antenesse d03abfa566 Add etymology field to advanced word form area 2019-10-01 14:46:49 -06:00
Robbie Antenesse 1bb8ace20a Merge branch 'master' of https://github.com/Alamantus/Lexiconga 2019-10-01 11:32:44 -06:00
Robbie Antenesse 39ccddab18 Merge branch 'master' of https://github.com/Alamantus/Lexiconga 2019-08-27 21:49:14 -06:00
Robbie Antenesse 9d08bbb337 Fix some things that have caused errors to get logged 2019-08-26 15:39:21 -06:00
Robbie Antenesse 122282d1a7 Update version number and package data 2019-08-22 14:29:56 -06:00
Robbie Antenesse f39dd886b1 Remove call to nonexistent var on word submit for upload 2019-08-22 14:23:59 -06:00
Robbie Antenesse 725f47782a Fix missing reference to getTimestampInSeconds 2019-08-05 17:28:33 -06:00
Robbie Antenesse abb8511999 Add parcel-plugin-goodie-bag for promise & fetch 2019-08-05 14:12:46 -06:00
Robbie Antenesse 5fbd06a035 Bump UpUp version for import fix 2019-07-19 11:21:56 -06:00
Robbie Antenesse 466e5e84c6 Merge branch 'master' of https://github.com/Alamantus/Lexiconga 2019-07-19 10:04:56 -06:00
Robbie Antenesse b2db35982f Update addWords() to not save; Update importWords to save after complete 2019-07-19 09:48:36 -06:00
Robbie Antenesse 5ba84abd0e Add social image that's better than just the logo 2019-07-17 16:25:22 -06:00
Robbie Antenesse f4ad87f73b Update UpUp version after html and css changes 2019-07-17 11:40:07 -06:00
Robbie Antenesse 10d204d695 Remove "Translation" from search options in View 2019-07-17 11:39:48 -06:00
Robbie Antenesse cb38d57053 Improve spacing for headers and labels 2019-07-17 11:33:02 -06:00
Robbie Antenesse a8c1dbe52b Add missing maximize buttons to notes fields 2019-07-17 11:32:30 -06:00
Robbie Antenesse 54703649a3 Force UTF-8 encoding in dictionary values
Fix "Malformed encoding" with some dictionaries with json_encode in view
2019-07-15 16:46:31 -06:00
Robbie Antenesse 8c92e56312 Remove experimental-scope-hoisting from parcel 2019-07-14 23:47:58 -06:00
Robbie Antenesse 959355bf18 Update yarn lockfile 2019-07-14 23:33:44 -06:00
Robbie Antenesse 8c9ae4b100 Merge branch 'new-features-wave-1' 2019-07-14 23:28:40 -06:00
Robbie Antenesse aefd7747d3 Update text to clarify that accounts are not required 2019-07-14 16:20:42 -06:00
Robbie Antenesse 068edd7a94 Add announcement for features wave 1 2019-07-14 15:37:06 -06:00
Robbie Antenesse 6ee83c7773 Don't render announcements if dismissed 2019-07-14 15:33:21 -06:00
Robbie Antenesse 26e4475fc2 Update browsers setting for autoprefixer 2019-07-14 11:19:53 -06:00
Robbie Antenesse f8b20e7ba2 Update dependency versions 2019-07-14 11:03:35 -06:00
Robbie Antenesse 8f864fda67 Return original default parts of speech to avoid confusion 2019-07-14 11:03:19 -06:00
Robbie Antenesse 6fed4dd4fb Move wordStats to backend for public view 2019-07-11 17:18:06 -06:00
Robbie Antenesse 59bf9c48e0 Update UpUp version for new features 2019-07-11 13:07:31 -06:00
Robbie Antenesse e490ef08d3 Pull public dictionary functions into separate file; sort on backend 2019-07-11 13:07:24 -06:00
Robbie Antenesse 706a7042cc Change dictionary even if current_dictionary is selected 2019-07-11 10:45:16 -06:00
Robbie Antenesse d6bf2bc297 Add loading message when changing dictionaries 2019-07-11 10:45:16 -06:00
Robbie Antenesse 3d94a7c5ec Update public link when changing dictionaries 2019-07-11 10:45:16 -06:00
Robbie Antenesse a533ccbeba Add some tweaks to help with upgrades 2019-07-11 10:45:16 -06:00
Robbie Antenesse 07028f28a8 Update styling for buttons and mobile 2019-07-11 10:45:16 -06:00
Robbie Antenesse 2a00d3d5b9 Wait until externalID has been fetched to render "Make Public" 2019-07-11 10:45:15 -06:00
Robbie Antenesse ad8ca6addc Fix manifest.webmanifest start_url error 2019-07-11 10:45:15 -06:00
Robbie Antenesse 105331bf68 Various fixes/notes for server compatibility 2019-07-11 10:45:15 -06:00
Robbie Antenesse 377e71daf8 Make #mobileWordFormShow visible in all themes 2019-07-11 10:45:15 -06:00
Robbie Antenesse bd895b28f8 Don't consider theme when checking if default 2019-07-11 10:45:14 -06:00
Robbie Antenesse fd10920b61 Update advertising page content 2019-07-11 10:45:14 -06:00
Robbie Antenesse 13ed184390 Remove unneeded console logs and code 2019-07-11 10:45:14 -06:00
Robbie Antenesse ee0e97c9c5 Add social meta and logo 2019-07-11 10:45:14 -06:00
Robbie Antenesse 97b9f97dfe Fix accessibility & best practice issues 2019-07-11 10:45:14 -06:00
Robbie Antenesse e228af66e4 Add metadata & manifest to html; Process icons 2019-07-11 10:45:13 -06:00
Robbie Antenesse cf5fb3cafd Assorted minor fixes 2019-07-11 10:45:13 -06:00
Robbie Antenesse c3bfe1531a Correctly clear dist and cache with npm scripts 2019-07-11 10:45:13 -06:00
Robbie Antenesse feb6c3e88a Confirm migration if imported from http 2019-07-11 10:45:13 -06:00
Robbie Antenesse 8731119dc2 Migrate stored dictionary to https if loading http 2019-07-11 10:45:13 -06:00
Robbie Antenesse 740cdaa4fd Update support links; Clean up html 2019-07-11 10:45:12 -06:00
Robbie Antenesse 386b2e626e Improve ad shuffling 2019-07-11 10:45:12 -06:00
Robbie Antenesse e2b5f1cd83 Dismiss announcements with cookies that expire 2019-07-11 10:45:12 -06:00
Robbie Antenesse 435547cb4d Enable permanently dismissing announcements 2019-07-11 10:45:12 -06:00
Robbie Antenesse eba7307328 Add fade out for messages; Remove unused utility 2019-07-11 10:45:12 -06:00
Robbie Antenesse 84a308693f Update ads and announcements 2019-07-11 10:45:12 -06:00
Robbie Antenesse 828608ee0b Fix User->upgradePassword() missing user 2019-07-11 10:45:11 -06:00
Robbie Antenesse 118b5c2d1a Write and test migration script; Update readme 2019-07-11 10:45:11 -06:00
Robbie Antenesse 102877aefa Change user dates to datetime format 2019-07-11 10:45:11 -06:00
Robbie Antenesse b476b69bb4 Update migrateDictionary to account for changes 2019-07-11 10:45:11 -06:00
Robbie Antenesse e6b973165a Correctly show/hide publicLinkDisplay after changing dictionaries 2019-07-11 10:45:11 -06:00
Robbie Antenesse 769c6f0f15 Don't upload dictionaries have not been edited 2019-07-11 10:45:10 -06:00
Robbie Antenesse 74642dcc0a Put homonymn number on public dictionary entries 2019-07-11 10:45:10 -06:00
Robbie Antenesse 3553530e6e Automate UpUp asset reference via router.php 2019-07-11 10:45:10 -06:00
Robbie Antenesse e755026267 Style announcements 2019-07-11 10:45:10 -06:00
Robbie Antenesse 90464fd15c Move index to template-index.html; Use router to render index
Makes it possible to render announcements more easily
2019-07-11 10:45:10 -06:00
Robbie Antenesse 1b6247f457 Move router.php to root; use realpath for file refs 2019-07-11 10:45:09 -06:00
Robbie Antenesse 8b6a3b05a4 Update UpUp asset 2019-07-11 10:45:09 -06:00
Robbie Antenesse 9d793c6c7d Sort words in public view correctly 2019-07-11 10:45:09 -06:00
Robbie Antenesse 9743e934d3 Always show IPA table button in pronunciation fields 2019-07-11 10:45:09 -06:00
Robbie Antenesse 87b7bbff89 Only split out html/md files;Fix page load problem 2019-07-11 10:45:09 -06:00
Robbie Antenesse 46b579d73f Shrink view js by splitting out only what is needed 2019-07-11 10:45:08 -06:00
Robbie Antenesse 9ade94562c Add UpUp for offline mode; Update Readme for UpUp 2019-07-11 10:45:08 -06:00
Robbie Antenesse 9df29b8d20 Prevent Parcel from removing ids from SVGs 2019-07-11 10:45:08 -06:00
Robbie Antenesse 8b63808d2c Fix settings not existing before being used 2019-07-11 10:45:08 -06:00
Robbie Antenesse 6f320b39b1 Update Terms and Privacy 2019-07-11 10:45:08 -06:00
Robbie Antenesse 239c66b616 Fully update Help file 2019-07-11 10:45:07 -06:00
Robbie Antenesse 8a80412cd6 Finish up password reset 2019-07-11 10:45:07 -06:00
Robbie Antenesse 11db09b44d Rename view.html for the router to template-view.html 2019-07-11 10:45:07 -06:00
Robbie Antenesse c0fe25007d Make sure php files can be used anywhere 2019-07-11 10:45:07 -06:00
Robbie Antenesse 457a6c6798 Transition all style changes with a fade 2019-07-11 10:45:07 -06:00
Robbie Antenesse 171abe17c6 Update ads 2019-07-11 10:45:06 -06:00
Robbie Antenesse f8a0c00c2b Re-upgrade parcel-bundler, use no-hmr as workaround 2019-07-11 10:45:06 -06:00
Robbie Antenesse e717480d18 Add simple text ads shuffled into words 2019-07-11 10:45:06 -06:00
Robbie Antenesse ab02aea88f Add logo and favicon; style logo for each theme 2019-07-11 10:45:06 -06:00
Robbie Antenesse 874d2ff3c2 Fix header#top suddenly having a margin 2019-07-11 10:45:05 -06:00
Robbie Antenesse a47f38caa0 Downgrade parcel-bundler to 1.9.7; minor fixes
Newer versions incorrectly link html files:
https://github.com/parcel-bundler/parcel/issues/2791
2019-07-11 10:45:05 -06:00
Robbie Antenesse 3ed27534ad Create an Advertising information page 2019-07-11 10:45:05 -06:00
Robbie Antenesse f3f4d18c48 Process main.scss in html so parcel shares the output 2019-07-11 10:45:05 -06:00
Robbie Antenesse 0f23454473 Start writing password reset 2019-07-11 10:45:05 -06:00
Robbie Antenesse 8d2ee80c26 Make view.html use dictionary theme 2019-07-11 10:45:05 -06:00
Robbie Antenesse 601cfc64bd Tie theme to dictionary instead of settings 2019-07-11 10:45:04 -06:00
Robbie Antenesse 5c5309a9a5 Replace leftover isComplete with theme 2019-07-11 10:45:04 -06:00
Robbie Antenesse d96817b485 Fix typo in Sync preventing publicLink from being set 2019-07-11 10:45:04 -06:00
Robbie Antenesse ac2d1b63c8 Make view.html use defaultTheme 2019-07-11 10:45:04 -06:00
Robbie Antenesse 3c5d19f0c6 Create Red theme 2019-07-11 10:45:04 -06:00
Robbie Antenesse 861bff60b4 Fix Royal link color 2019-07-11 10:45:04 -06:00
Robbie Antenesse e1ba94c3c7 Create Yellow theme 2019-07-11 10:45:03 -06:00
Robbie Antenesse acdde8da81 Add themes to stylesheet 2019-07-11 10:45:03 -06:00
Robbie Antenesse 737dd169a1 Create Royal theme 2019-07-11 10:45:03 -06:00
Robbie Antenesse 09914440be Create Mint and Grape themes 2019-07-11 10:45:03 -06:00
Robbie Antenesse 1528503c52 Create Green theme 2019-07-11 10:45:03 -06:00
Robbie Antenesse 3117637c59 Create Blue theme 2019-07-11 10:45:03 -06:00
Robbie Antenesse 3e1465b905 Create Light theme 2019-07-11 10:45:03 -06:00
Robbie Antenesse 32ad87d661 Create Dark theme 2019-07-11 10:45:02 -06:00
Robbie Antenesse 1b0c6da3e4 Update border around inputs 2019-07-11 10:45:02 -06:00
Robbie Antenesse ba5baf8dd9 Remove non-color styling from theme; Button styling fixes 2019-07-11 10:45:02 -06:00
Robbie Antenesse 251ad12407 Make the default theme use Lexiconga colors! 2019-07-11 10:45:02 -06:00
Robbie Antenesse dcee87e978 Enable changing themes 2019-07-11 10:45:02 -06:00
Robbie Antenesse 35ccf7bf2a Split all colors into a _defaultTheme.scss 2019-07-11 10:45:02 -06:00
Robbie Antenesse 2b4fd70b85 Update footer styling 2019-07-11 10:45:01 -06:00
Robbie Antenesse db021647bd Show/remove share link if turning off Make Public 2019-07-11 10:45:01 -06:00
Robbie Antenesse 596efff70d Fix IPA field help rendering; Use arrows in usage.html 2019-07-11 10:45:01 -06:00
Robbie Antenesse 04f70e4bfa Add prominent share links if logged in or viewing 2019-07-11 10:45:01 -06:00
Robbie Antenesse 4de8b572c3 Parse references on backend for view 2019-07-11 10:45:01 -06:00
Robbie Antenesse 601841c754 Correctly link public word references 2019-07-11 10:45:00 -06:00
Robbie Antenesse b9cfce5a05 Add public word view 2019-07-11 10:45:00 -06:00
Robbie Antenesse b7bdd8d7e0 Make parseReferences() parse its own references 2019-07-11 10:45:00 -06:00
Robbie Antenesse 6292a73717 Add failed login lockout 2019-07-11 10:45:00 -06:00
Robbie Antenesse 5ee6e9078e Fix login error message 2019-07-11 10:45:00 -06:00
Robbie Antenesse 41a880068e Prevent rendering reference if specified does not exist 2019-07-11 10:45:00 -06:00
Robbie Antenesse 240d226d4e Add share links to words (word view not added yet) 2019-07-11 10:45:00 -06:00
Robbie Antenesse 92628cfa40 Use domain + pathname for public links instead of href 2019-07-11 10:44:59 -06:00
Robbie Antenesse 5587056f3b Auto-number homonymns; enable referencing specific homonymns 2019-07-11 10:44:59 -06:00
Robbie Antenesse e0b3a2b90e Make sure publicLink is populated even when nonexistent 2019-07-11 10:44:59 -06:00
Robbie Antenesse 2eb5b4db84 Add error message if public dictionary not found 2019-07-11 10:44:59 -06:00
Robbie Antenesse 92949673bb Prevent iOS from zooming on inputs 2019-07-11 10:44:59 -06:00
Robbie Antenesse d575e8f8d9 Make public link visible when public; copy on click 2019-07-11 10:44:57 -06:00
Robbie Antenesse 389b4d9015 Update Help file to add new features 2019-07-11 10:30:22 -06:00
Robbie Antenesse 0811dbde08 Add alphabetical order to database 2019-07-11 10:30:21 -06:00
Robbie Antenesse c07ae23f8a Render custom css in public view 2019-07-11 10:30:21 -06:00
Robbie Antenesse 1cf547dd2e Split out setupListeners into separate files 2019-07-11 10:30:21 -06:00
Robbie Antenesse 91a5967727 Add new features to offline and view 2019-07-11 10:30:21 -06:00
Robbie Antenesse 9ca6ef15d7 Add Custom CSS field 2019-07-11 10:30:20 -06:00
Robbie Antenesse f9fa6a178d Stop dictionary update if name or specification blank 2019-07-11 10:30:20 -06:00
Robbie Antenesse a2542fe7a1 Add Phonology Notes 2019-07-11 10:30:20 -06:00
Robbie Antenesse a1ea105f66 Fix console error when uploading whole dictionary 2019-07-11 10:30:19 -06:00
Robbie Antenesse 10d2159262 Add ability to search for orthographic translations 2019-07-11 10:30:19 -06:00
Robbie Antenesse 56ce6c30a6 Only save dictionary and settings when changed 2019-07-11 10:30:19 -06:00
Robbie Antenesse aa956e9820 Make alphabetical order editable 2019-07-11 10:30:19 -06:00
Robbie Antenesse 2bf0f15f67 Fix how custom sorting works!
Sort alphabetically first, then by custom if specified
2019-07-11 10:30:18 -06:00
Robbie Antenesse 4e1ee35c5b Fix when orthography section displays 2019-07-11 10:30:18 -06:00
Robbie Antenesse ee30fe53b2 Split render.js out into separate files 2019-07-11 10:30:18 -06:00
Robbie Antenesse 460506012e Start setting up custom alphabetical order
Need to figure out why non-alphabetical letters are sorting wrong.
They should be at the end no matter what, but they're not always.
2019-07-11 10:30:17 -06:00
Robbie Antenesse 45e9e5230c Correctly show/hide homonymn number 2019-07-11 10:30:17 -06:00
Robbie Antenesse 079288a0a8 Add translations to structure.sql 2019-07-11 10:30:17 -06:00
Robbie Antenesse 850b042d6b Do reference parsing and orthography translation on backend for public view 2019-07-11 10:30:17 -06:00
Robbie Antenesse d36eec52fa Replace Ctrl+X hotkey with Ctrl+Backspace/Delete 2019-07-11 10:30:16 -06:00
Robbie Antenesse f153e0c3ec Add extra classes to word references and translated text 2019-07-11 10:30:16 -06:00
Robbie Antenesse 4c5dafd6f0 Improve Details display of tags 2019-07-11 10:30:16 -06:00
Robbie Antenesse 1491fd1196 Fix view rendering after updating dictionary structure 2019-07-11 10:30:16 -06:00
Robbie Antenesse 945e2a3c76 Display orthograpy translations in details 2019-07-11 10:30:15 -06:00
Robbie Antenesse 4f9f4a97ad Set up orthography translation
Also fix dictionary description not auto-linking
2019-07-11 10:30:15 -06:00
Robbie Antenesse 1c2570684d Change phonotactics exceptions to notes where used 2019-07-11 10:30:15 -06:00
Robbie Antenesse 686a7fa542 Prepare dictionary structure for changes (breaking changes) 2019-07-11 10:30:15 -06:00
Robbie Antenesse 2ddd513098 Move migrateDictionary() to migration.js 2019-07-11 10:30:14 -06:00
Robbie Antenesse bc672a564a Change dictionary even if current_dictionary is selected 2019-07-11 10:30:14 -06:00
Robbie Antenesse d8111fbe1d Add loading message when changing dictionaries 2019-07-11 10:30:14 -06:00
Robbie Antenesse 88d7e4fd8c Update public link when changing dictionaries 2019-07-11 10:30:13 -06:00
Robbie Antenesse 9e7e113cc2 Add some tweaks to help with upgrades 2019-07-11 10:30:13 -06:00
Robbie Antenesse 21d91a9920 Update styling for buttons and mobile 2019-07-11 10:30:13 -06:00
Robbie Antenesse 0eb8be330b Wait until externalID has been fetched to render "Make Public" 2019-07-11 10:30:13 -06:00
Robbie Antenesse 2760efef80 Fix manifest.webmanifest start_url error 2019-07-11 10:30:12 -06:00
Robbie Antenesse 2f96084b4a Various fixes/notes for server compatibility 2019-07-11 10:30:12 -06:00
Robbie Antenesse 54c3054b25 Make #mobileWordFormShow visible in all themes 2019-07-11 10:30:12 -06:00
Robbie Antenesse 4fc8cec5e3 Don't consider theme when checking if default 2019-07-11 10:30:12 -06:00
Robbie Antenesse 48239809a5 Update advertising page content 2019-07-11 10:30:11 -06:00
Robbie Antenesse 85ab5e2b96 Remove unneeded console logs and code 2019-07-11 10:30:11 -06:00
Robbie Antenesse 29eeb06ce4 Add social meta and logo 2019-07-11 10:30:11 -06:00
Robbie Antenesse d57a4a2a48 Fix accessibility & best practice issues 2019-07-11 10:30:11 -06:00
Robbie Antenesse a012baeec2 Add metadata & manifest to html; Process icons 2019-07-11 10:30:10 -06:00
Robbie Antenesse 6e24fca1bd Assorted minor fixes 2019-07-11 10:30:10 -06:00
Robbie Antenesse 3f690383f8 Correctly clear dist and cache with npm scripts 2019-07-11 10:30:10 -06:00
Robbie Antenesse a3b9a0a934 Confirm migration if imported from http 2019-07-11 10:30:10 -06:00
Robbie Antenesse acb94637c5 Migrate stored dictionary to https if loading http 2019-07-11 10:30:09 -06:00
Robbie Antenesse b54e35be92 Update support links; Clean up html 2019-07-11 10:30:09 -06:00
Robbie Antenesse ca7d219a96 Improve ad shuffling 2019-07-11 10:30:09 -06:00
Robbie Antenesse 352e6d3fc0 Dismiss announcements with cookies that expire 2019-07-11 10:30:09 -06:00
Robbie Antenesse 3a61d4ccc6 Enable permanently dismissing announcements 2019-07-11 10:30:08 -06:00
Robbie Antenesse 8d132f1919 Add fade out for messages; Remove unused utility 2019-07-11 10:30:08 -06:00
Robbie Antenesse 66791d8dad Update ads and announcements 2019-07-11 10:30:08 -06:00
Robbie Antenesse cbd22c5ee0 Fix User->upgradePassword() missing user 2019-07-11 10:30:08 -06:00
Robbie Antenesse ea61ba24bd Write and test migration script; Update readme 2019-07-11 10:30:08 -06:00
Robbie Antenesse 0eaf289abc Change user dates to datetime format 2019-07-11 10:30:07 -06:00
Robbie Antenesse 2c4c281850 Update migrateDictionary to account for changes 2019-07-11 10:30:07 -06:00
Robbie Antenesse ec42aa3778 Correctly show/hide publicLinkDisplay after changing dictionaries 2019-07-11 10:30:07 -06:00
Robbie Antenesse 546c82996e Don't upload dictionaries have not been edited 2019-07-11 10:30:07 -06:00
Robbie Antenesse d9f8390672 Put homonymn number on public dictionary entries 2019-07-11 10:30:06 -06:00
Robbie Antenesse 5e4ded844e Automate UpUp asset reference via router.php 2019-07-11 10:30:06 -06:00
Robbie Antenesse 12d46c5eef Style announcements 2019-07-11 10:30:06 -06:00
Robbie Antenesse 39a5c11a11 Move index to template-index.html; Use router to render index
Makes it possible to render announcements more easily
2019-07-11 10:30:05 -06:00
Robbie Antenesse 7c5c33c54f Move router.php to root; use realpath for file refs 2019-07-11 10:30:05 -06:00
Robbie Antenesse 41c6322477 Update UpUp asset 2019-07-11 10:30:05 -06:00
Robbie Antenesse 9fe0da58db Sort words in public view correctly 2019-07-11 10:30:04 -06:00
Robbie Antenesse b8ca61d3fe Always show IPA table button in pronunciation fields 2019-07-11 10:30:04 -06:00
Robbie Antenesse 57ca675f7a Only split out html/md files;Fix page load problem 2019-07-11 10:30:04 -06:00
Robbie Antenesse c0909343f6 Shrink view js by splitting out only what is needed 2019-07-11 10:30:04 -06:00
Robbie Antenesse cbcc7ecfd4 Add UpUp for offline mode; Update Readme for UpUp 2019-07-11 10:30:03 -06:00
Robbie Antenesse 7cdf43f2e5 Prevent Parcel from removing ids from SVGs 2019-07-11 10:30:03 -06:00
Robbie Antenesse 64903fc544 Fix settings not existing before being used 2019-07-11 10:30:03 -06:00
Robbie Antenesse 881cd9cb2e Update Terms and Privacy 2019-07-11 10:30:03 -06:00
Robbie Antenesse fa559afb8b Fully update Help file 2019-07-11 10:30:02 -06:00
Robbie Antenesse 8c39d05c13 Finish up password reset 2019-07-11 10:30:02 -06:00
Robbie Antenesse 9e993be503 Rename view.html for the router to template-view.html 2019-07-11 10:30:02 -06:00
Robbie Antenesse ffdd008344 Make sure php files can be used anywhere 2019-07-11 10:30:01 -06:00
Robbie Antenesse c44d4eee3e Transition all style changes with a fade 2019-07-11 10:30:01 -06:00
Robbie Antenesse 559d6a698f Update ads 2019-07-11 10:30:01 -06:00
Robbie Antenesse c6ae72ec1b Re-upgrade parcel-bundler, use no-hmr as workaround 2019-07-11 10:30:01 -06:00
Robbie Antenesse 3c75fae3a8 Add simple text ads shuffled into words 2019-07-11 10:30:00 -06:00
Robbie Antenesse 5d7d0e319e Add logo and favicon; style logo for each theme 2019-07-11 10:30:00 -06:00
Robbie Antenesse 3795eceed7 Fix header#top suddenly having a margin 2019-07-11 10:29:59 -06:00
Robbie Antenesse 8ed216cb7a Downgrade parcel-bundler to 1.9.7; minor fixes
Newer versions incorrectly link html files:
https://github.com/parcel-bundler/parcel/issues/2791
2019-07-11 10:29:59 -06:00
Robbie Antenesse 15d9201ef3 Create an Advertising information page 2019-07-11 10:29:59 -06:00
Robbie Antenesse 3d3a69c65f Process main.scss in html so parcel shares the output 2019-07-11 10:29:59 -06:00
Robbie Antenesse 3340c96507 Start writing password reset 2019-07-11 10:29:58 -06:00
Robbie Antenesse 5dcccaba59 Make view.html use dictionary theme 2019-07-11 10:29:58 -06:00
Robbie Antenesse 278b0b61d3 Tie theme to dictionary instead of settings 2019-07-11 10:29:58 -06:00
Robbie Antenesse 37689fdab1 Replace leftover isComplete with theme 2019-07-11 10:29:58 -06:00
Robbie Antenesse f2105465df Fix typo in Sync preventing publicLink from being set 2019-07-11 10:29:57 -06:00
Robbie Antenesse b0646209eb Make view.html use defaultTheme 2019-07-11 10:29:57 -06:00
Robbie Antenesse 07694cd250 Create Red theme 2019-07-11 10:29:57 -06:00
Robbie Antenesse af2b7edcfb Fix Royal link color 2019-07-11 10:29:57 -06:00
Robbie Antenesse b034bb770b Create Yellow theme 2019-07-11 10:29:57 -06:00
Robbie Antenesse 8a1589aaac Add themes to stylesheet 2019-07-11 10:29:56 -06:00
Robbie Antenesse 038e47613e Create Royal theme 2019-07-11 10:29:56 -06:00
Robbie Antenesse 22a94e78f5 Create Mint and Grape themes 2019-07-11 10:29:56 -06:00
Robbie Antenesse 6227be15d0 Create Green theme 2019-07-11 10:29:56 -06:00
Robbie Antenesse d33a2359b1 Create Blue theme 2019-07-11 10:29:55 -06:00
Robbie Antenesse 535020053e Create Light theme 2019-07-11 10:29:55 -06:00
Robbie Antenesse f7eecf9796 Create Dark theme 2019-07-11 10:29:55 -06:00
Robbie Antenesse 5461a3f1ed Update border around inputs 2019-07-11 10:29:55 -06:00
Robbie Antenesse 86a37f0280 Remove non-color styling from theme; Button styling fixes 2019-07-11 10:29:54 -06:00
Robbie Antenesse 227d7f59f9 Make the default theme use Lexiconga colors! 2019-07-11 10:29:54 -06:00
Robbie Antenesse 06bac52c36 Enable changing themes 2019-07-11 10:29:54 -06:00
Robbie Antenesse 188779131e Split all colors into a _defaultTheme.scss 2019-07-11 10:29:54 -06:00
Robbie Antenesse bd58301a9a Update footer styling 2019-07-11 10:29:53 -06:00
Robbie Antenesse 918dad1ad8 Show/remove share link if turning off Make Public 2019-07-11 10:29:53 -06:00
Robbie Antenesse a1d8059068 Fix IPA field help rendering; Use arrows in usage.html 2019-07-11 10:29:53 -06:00
Robbie Antenesse ce143be3f5 Add prominent share links if logged in or viewing 2019-07-11 10:29:53 -06:00
Robbie Antenesse e9cf9653be Parse references on backend for view 2019-07-11 10:29:52 -06:00
Robbie Antenesse eb0dd669bb Correctly link public word references 2019-07-11 10:29:52 -06:00
Robbie Antenesse 1872fffba8 Add public word view 2019-07-11 10:29:52 -06:00
Robbie Antenesse 19e41958a4 Make parseReferences() parse its own references 2019-07-11 10:29:52 -06:00
Robbie Antenesse dafecd9582 Add failed login lockout 2019-07-11 10:29:52 -06:00
Robbie Antenesse 63b1f20d58 Fix login error message 2019-07-11 10:29:51 -06:00
Robbie Antenesse 08cadfb121 Prevent rendering reference if specified does not exist 2019-07-11 10:29:51 -06:00
Robbie Antenesse 4a1dd7aae4 Add share links to words (word view not added yet) 2019-07-11 10:29:51 -06:00
Robbie Antenesse b59c4702b2 Use domain + pathname for public links instead of href 2019-07-11 10:29:51 -06:00
Robbie Antenesse 520ede111b Auto-number homonymns; enable referencing specific homonymns 2019-07-11 10:29:50 -06:00
Robbie Antenesse eadc13e04b Make sure publicLink is populated even when nonexistent 2019-07-11 10:29:50 -06:00
Robbie Antenesse 4bdeff3296 Add error message if public dictionary not found 2019-07-11 10:29:50 -06:00
Robbie Antenesse 50b0941223 Prevent iOS from zooming on inputs 2019-07-11 10:29:50 -06:00
Robbie Antenesse 910e025997 Make public link visible when public; copy on click 2019-07-11 10:29:47 -06:00
99 changed files with 10947 additions and 4455 deletions

4
.gitignore vendored
View File

@ -2,5 +2,7 @@ node_modules/
.cache/
dist/
vendor/
processedImages/
src/php/api/config.php
src/php/api/config.php
src/php/api/migrate.php

3
.htmlnanorc Normal file
View File

@ -0,0 +1,3 @@
{
"minifySvg": false
}

View File

@ -1,7 +1,7 @@
{
"plugins": {
"autoprefixer": {
"browsers": [
"overrideBrowserslist": [
">1%",
"last 4 versions",
"Firefox ESR",

View File

@ -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,23 @@ 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.
`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.
## Migration
There is a script called `src/php/api/migrate.php.changeme` that can be used to help with the migration process from a `version1` Lexiconga database into a `master` database. **Note:** Migration is intended only for migrating from an old server to a freshly-installed/empty new database. To use this, copy `src/php/api/migrate.php.changeme` to `migrate.php` somewhere in the `version1` project (probably in `/php`) and copy the same to `/api/migrate.php` in your `master` project, making sure that all the variables for referencing the databases are correct.
Visit `migrate.php` on your `version1` server with `?outgoing=true` set in order to begin the transfer. The other server's `migrate.php` will receive an "incoming" request multiple times, and your screen will display messages as it works.
_DELETE THESE `migrate.php` FILES IMMEDIATELY AFTER MIGRATION IS COMPLETE!_.
## Emails
Be sure you set up email senders/receivers for at least these 3 email addresses:
- help (can be forwarder)
- donotreply (must be sender)

50
dev/resize-images.js Normal file
View File

@ -0,0 +1,50 @@
const fs = require('fs');
const sharp = require('sharp');
const folder = './processedImages/';
if (!fs.existsSync(folder)) {
fs.mkdirSync(folder);
}
const favicon = sharp('./src/images/favicon.svg');
sharp('./src/images/social.jpg').toFile(folder + 'social.jpg', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(32, 32).toFile(folder + 'favicon.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(128, 128).toFile(folder + 'icon-128.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(144, 144).toFile(folder + 'icon-144.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(152, 152).toFile(folder + 'icon-152.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(192, 192).toFile(folder + 'icon-192.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(256, 256).toFile(folder + 'icon-256.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});
favicon.clone().resize(512, 512).toFile(folder + 'icon-512.png', (err, info) => {
if (err) return console.error(err);
console.log(info);
});

View File

@ -1,334 +0,0 @@
<!DOCTYPE html>
<html>
<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>Lexiconga</title>
<script src="src/index.js"></script>
</head>
<body>
<header id="top">
<h1 id="title">Lexiconga</h1>
<input id="openSearchModal" placeholder="🔍&#xFE0E; Search"> <span id="searchResults"></span>
<section id="searchModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<label>Search Term
<input id="searchBox" placeholder="Search term">
</label>
<a id="searchButton" class="small button">Search</a>
<a id="clearSearchButton" class="small red button">Clear</a>
<a class="small button" onclick="var options=document.getElementById('searchOptions').style;options.display=options.display=='block'?'none':'block';">
Toggle Options
</a>
</section>
<footer id="searchOptions" style="display:none;">
<div class="split">
<div class="quarter category">
<h3>Search For</h3>
</div>
<div class="three-quarter options">
<label>Case-Sensitive
<input type="checkbox" id="searchCaseSensitive">
</label>
<label>Ignore Diacritics/Accents
<input type="checkbox" id="searchIgnoreDiacritics">
</label>
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include in Search</h3>
</div>
<div class="three-quarter options">
<label>Word Name
<input type="checkbox" checked id="searchIncludeName">
</label>
<label>Definition
<input type="checkbox" checked id="searchIncludeDefinition">
</label>
<label>Details
<input type="checkbox" checked id="searchIncludeDetails">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include Only</h3>
</div>
<div class="three-quarter options" id="searchPartsOfSpeech"></div>
</div>
</footer>
</div>
</section>
<div id="headerMenu">
<a id="settingsButton" class="button">Settings</a>
<a id="loginCreateAccountButton" class="button">Log In&nbsp;/ Create Account</a>
</div>
<div style="clear:both;"></div>
</header>
<main>
<aside id="sideColumn">
<div id="mobileWordFormShow">+</div>
<form id="wordForm">
<label>Word<span class="red">*</span><br>
<input id="wordName" maxlength="200">
</label>
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
<input id="wordPronunciation" class="ipa-field" maxlength="200"><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
</label>
<label>Part of Speech<br>
<select id="wordPartOfSpeech" class="part-of-speech-select"></select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition" maxlength="2500" placeholder="Equivalent words">
</label>
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
<textarea id="wordDetails" placeholder="Markdown formatting allowed"></textarea>
</label>
<div id="wordErrorMessage"></div>
<a class="button" id="addWordButton">Add Word</a>
</form>
</aside>
<section id="mainColumn">
<section id="detailsSection">
<h2 id="dictionaryName">Dictionary Name</h2>
<nav>
<ul>
<li>Description</li><li>Details</li><li>Stats</li><li id="editDictionaryButton">Edit</li>
</ul>
</nav>
<article id="detailsPanel" style="display:none;">
<p>The dictionary details</p>
</article>
</section>
<section class="pagination"></section>
<section id="entries">
<article class="entry">
<header>
<h4 class="word">Loading Words</h4>
</header>
<dl>
<dt class="definition">Please Wait...</dt>
</dl>
</article>
</section>
<section class="pagination"></section>
</section>
</main>
<footer id="bottom">
<a href="https://buymeacoff.ee/robbieantenesse" target="_blank" class="small button">Support Lexiconga</a>
<a href="https://blog.lexicon.ga" target="_blank" class="small button">Blog</a>
<a href="https://github.com/Alamantus/Lexiconga/issues" target="_blank" class="small button">Issues</a>
<a href="https://github.com/Alamantus/Lexiconga/releases" target="_blank" class="small button">Updates</a>
|
<a class="button" id="helpInfoButton">Help</a>
<a class="button" id="termsInfoButton">Terms</a>
<a class="button" id="privacyInfoButton">Privacy</a>
</footer>
<section id="settingsModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<form class="split two">
<div>
<h3>General Settings</h3>
<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
Pronunciation fields.</small>
</label>
<label>Use Hotkeys
<input id="settingsUseHotkeys" type="checkbox" checked><br />
<small>Check this to enable keyboard combinations to perform different helpful actions.</small>
</label>
<label>Theme
<select disabled>
<option selected value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="royal">Royal</option>
</select>
</label>
<div id="accountSettings"></div>
</div>
<div id="accountActions"></div>
</form>
</section>
<footer>
<a class="button" id="settingsSave">Save</a>
<a class="button" id="settingsSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
<section id="editModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<nav class="tabs">
<ul>
<li class="active">Description</li><li>Details</li><li>Settings</li><li>Actions</li>
</ul>
</nav>
<section id="editDescriptionTab">
<label>Name<br>
<input id="editName" maxlength="50">
</label>
<label>Specification<br>
<input id="editSpecification" maxlength="50">
</label>
<label>Description<a class="label-button maximize-button">Maximize</a><br>
<textarea id="editDescription"></textarea>
</label>
</section>
<section id="editDetailsTab" style="display:none;">
<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>
<h3>Phonology</h3>
<div class="split three">
<div>
<label>Consonants<br>
<small>(Space separated list)</small><br>
<input id="editConsonants" class="ipa-field" maxlength="100" placeholder="p b m n t ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Vowels<br>
<small>(Space separated list)</small><br>
<input id="editVowels" class="ipa-field" maxlength="100" placeholder="æ u e ɪ ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Polyphthongs&nbsp;/ Blends<br>
<small>(Space separated list)</small><br>
<input id="editBlends" class="ipa-field" maxlength="100" placeholder="ai ou ue ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
</div>
<h3>Phonotactics</h3>
<div class="split three">
<div>
<label>Onset<br>
<small>(Comma separated list)</small><br>
<input id="editOnset" maxlength="100" placeholder="Consonants,Vowels">
</label>
</div>
<div>
<label>Nucleus<br>
<small>(Comma separated list)</small><br>
<input id="editNucleus" maxlength="100" placeholder="Vowels,Blends">
</label>
</div>
<div>
<label>Coda<br>
<small>(Comma separated list)</small><br>
<input id="editCoda" maxlength="100" placeholder="Any">
</label>
</div>
</div>
<label>Exceptions <small>(Markdown-enabled)</small><br>
<textarea id="editExceptions"></textarea>
</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>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editGrammar"></textarea>
</label>
</section>
<section id="editSettingsTab" style="display:none;">
<label>Prevent Duplicate Words
<input type="checkbox" id="editPreventDuplicates"><br>
<small>Checking this box will prevent the creation of words with the exact same spelling.</small>
</label>
<label>Words are Case-Sensitive
<input type="checkbox" id="editCaseSensitive"><br>
<small>Checking this box will allow the creation of words with the exact same spelling if their capitalization is different.</small>
</label>
<label>Sort by Definition
<input type="checkbox" id="editSortByDefinition"><br>
<small>Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.</small>
</label>
</section>
<section id="editActionsTab" style="display:none;">
<h3>Import&nbsp;/ Export</h3>
<div class="split two">
<div>
<p>
<label class="button">Import JSON <input type="file" id="importDictionaryFile" accept="application/json, .dict"><br>
<small>Import a previously-exported <code>JSON</code> file.</small>
</label>
</p>
<p>
<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>
</p>
</div>
<div>
<p>
<a class="button" id="exportDictionaryButton">Export JSON</a><br>
<small>Export your work as a <code>JSON</code> file to re-import later.</small>
</p>
<p>
<a class="button" id="exportWordsButton">Export Words</a><br>
<small>Export a CSV file of your words.</small>
</p>
</div>
</div>
<p>
<a class="red button" id="deleteDictionaryButton">Delete Dictionary</a><br>
<small>This will permanently delete your current dictionary, and it will not be possible to return it if you have not backed it up!</small>
</p>
</section>
<footer>
<a class="button" id="editSave">Save</a>
<a class="button" id="editSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
<div id="messagingSection"></div>
</body>
</html>

41
manifest.webmanifest Normal file
View File

@ -0,0 +1,41 @@
{
"name": "Lexiconga",
"short_name": "Lexiconga",
"icons": [
{
"src": "processedImages/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "processedImages/icon-144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "processedImages/icon-152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "processedImages/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "processedImages/icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "processedImages/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#e6cfaa",
"theme_color": "#bd7251"
}

425
offline.html Normal file
View File

@ -0,0 +1,425 @@
<!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>Lexiconga (OFFLINE)</title>
<meta name="description" content="The quick and easy (offline) dictionary builder for constructed languages.">
<meta name="keywords" content="conlanging, dictionary, dictionaries, lexicon, conlangs, constructed languages, glossopoeia, builder, app, tool">
<meta property="og:url" content="https://lexicon.ga/offline.html">
<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/social.jpg">
<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="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<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">
<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">
<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> <strong>You are OFFLINE</strong>
<input id="openSearchModal" title="Open Search Panel" placeholder="🔍&#xFE0E; Search"> <span id="searchResults"></span>
<section id="searchModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<label>Search Term
<input id="searchBox" placeholder="Search term">
</label>
<a id="searchButton" class="small button">Search</a>
<a id="clearSearchButton" class="small red button">Clear</a>
<a class="small button" onclick="var options=document.getElementById('searchOptions').style;options.display=options.display=='block'?'none':'block';">
Toggle Options
</a>
</section>
<footer id="searchOptions" style="display:none;">
<div class="split">
<div class="quarter category">
<h3>Search For</h3>
</div>
<div class="three-quarter options">
<label>Case-Sensitive
<input type="checkbox" id="searchCaseSensitive">
</label>
<label>Ignore Diacritics/Accents
<input type="checkbox" id="searchIgnoreDiacritics">
</label>
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
<label>Translations
<input type="checkbox" id="searchOrthography">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include in Search</h3>
</div>
<div class="three-quarter options">
<label>Word Name
<input type="checkbox" checked id="searchIncludeName">
</label>
<label>Definition
<input type="checkbox" checked id="searchIncludeDefinition">
</label>
<label>Details
<input type="checkbox" checked id="searchIncludeDetails">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include Only</h3>
</div>
<div class="three-quarter options" id="searchPartsOfSpeech"></div>
</div>
</footer>
</div>
</section>
<div id="headerMenu">
<a id="settingsButton" class="button">Settings</a>
<a id="loginCreateAccountButton" hidden style="display:none;">Log In&nbsp;/ Create Account</a>
</div>
<div style="clear:both;"></div>
</header>
<main>
<aside id="sideColumn">
<div id="mobileWordFormShow">+</div>
<form id="wordForm">
<label>Word<span class="red">*</span><br>
<input id="wordName" maxlength="200">
</label>
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
<input id="wordPronunciation" class="ipa-field" maxlength="200"><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
</label>
<label>Part of Speech<br>
<select id="wordPartOfSpeech" class="part-of-speech-select"></select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition" maxlength="2500" placeholder="Equivalent words">
</label>
<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>
</aside>
<section id="mainColumn">
<section id="detailsSection">
<h2 id="dictionaryName">Dictionary Name</h2>
<nav>
<ul>
<li>Description</li><li>Details</li><li>Stats</li><li id="editDictionaryButton">Edit</li>
</ul>
</nav>
<article id="detailsPanel" style="display:none;">
<p>The dictionary details</p>
</article>
</section>
<section class="pagination"></section>
<section id="entries">
<article class="entry">
<header>
<h4 class="word">Loading Words</h4>
</header>
<dl>
<dt class="definition">Please Wait...</dt>
</dl>
</article>
</section>
<section class="pagination"></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="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>
<section id="settingsModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<form class="split two">
<div>
<h3>General Settings</h3>
<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
Pronunciation fields.</small>
</label>
<label>Use Hotkeys
<input id="settingsUseHotkeys" type="checkbox" checked><br />
<small>Check this to enable keyboard combinations to perform different helpful actions.</small>
</label>
<label>Default Theme <small>(the theme new dictionaries will use)</small>
<select id="settingsDefaultTheme">
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
<option value="red">Red</option>
<option value="royal">Royal</option>
<option value="mint">Mint</option>
<option value="grape">Grape</option>
</select>
</label>
<div id="accountSettings"></div>
</div>
<div id="accountActions"></div>
</form>
</section>
<footer>
<a class="button" id="settingsSave">Save</a>
<a class="button" id="settingsSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
<section id="editModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<nav class="tabs">
<ul>
<li class="active">Description</li><li>Details</li><li>Settings</li><li>Actions</li>
</ul>
</nav>
<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>
</label>
</section>
<section id="editDetailsTab" style="display:none;">
<label>Parts of Speech <small>(Comma Separated List)</small><br>
<input id="editPartsOfSpeech" maxlength="2500" placeholder="Noun,Adjective,Verb">
</label>
<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>&nbsp;
<small>Leave blank for default (case-insensitive ASCII/Unicode sorting)</small>
</label>
<h3>Phonology</h3>
<div class="split three">
<div>
<label>Consonants<br>
<small>(Space separated list)</small><br>
<input id="editConsonants" class="ipa-field" maxlength="100" placeholder="p b m n t ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Vowels<br>
<small>(Space separated list)</small><br>
<input id="editVowels" class="ipa-field" maxlength="100" placeholder="æ u e ɪ ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Polyphthongs&nbsp;/ Blends<br>
<small>(Space separated list)</small><br>
<input id="editBlends" class="ipa-field" maxlength="100" placeholder="ai ou ue ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
</div>
<label>Notes <small>(Markdown-enabled)</small><br>
<textarea id="editPhonologyNotes"></textarea>
</label>
<h3>Phonotactics</h3>
<div class="split three">
<div>
<label>Onset<br>
<small>(Comma separated list)</small><br>
<input id="editOnset" maxlength="100" placeholder="Consonants,Vowels">
</label>
</div>
<div>
<label>Nucleus<br>
<small>(Comma separated list)</small><br>
<input id="editNucleus" maxlength="100" placeholder="Vowels,Blends">
</label>
</div>
<div>
<label>Coda<br>
<small>(Comma separated list)</small><br>
<input id="editCoda" maxlength="100" placeholder="Any">
</label>
</div>
</div>
<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>
<h3>Grammar</h3>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editGrammar"></textarea>
</label>
</section>
<section id="editSettingsTab" style="display:none;">
<label>Prevent Duplicate Words
<input type="checkbox" id="editPreventDuplicates"><br>
<small>Checking this box will prevent the creation of words with the exact same spelling.</small>
</label>
<label>Words are Case-Sensitive
<input type="checkbox" id="editCaseSensitive"><br>
<small>Checking this box will allow the creation of words with the exact same spelling if their capitalization is different.</small>
</label>
<label>Sort by Definition
<input type="checkbox" id="editSortByDefinition"><br>
<small>Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.</small>
</label>
<label>Theme
<select id="editTheme">
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
<option value="red">Red</option>
<option value="royal">Royal</option>
<option value="mint">Mint</option>
<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&nbsp;/ Export</h3>
<div class="split two">
<div>
<p>
<label class="button">Import JSON <input type="file" id="importDictionaryFile" accept="application/json, .dict"><br>
<small>Import a previously-exported <code>JSON</code> file.</small>
</label>
</p>
<p>
<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>
</p>
</div>
<div>
<p>
<a class="button" id="exportDictionaryButton">Export JSON</a><br>
<small>Export your work as a <code>JSON</code> file to re-import later.</small>
</p>
<p>
<a class="button" id="exportWordsButton">Export Words</a><br>
<small>Export a CSV file of your words.</small>
</p>
</div>
</div>
<p>
<a class="red button" id="deleteDictionaryButton">Delete Dictionary</a><br>
<small>This will permanently delete your current dictionary, and it will not be possible to return it if you have not backed it up!</small>
</p>
</section>
<footer>
<a class="button" id="editSave">Save</a>
<a class="button" id="editSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
<div id="messagingSection"></div>
</body>
</html>

View File

@ -1,32 +1,39 @@
{
"name": "lexiconga-lite",
"version": "1.0.0",
"description": "A light-as-possible rewrite of Lexiconga",
"main": "index.html",
"repository": "https://cybre.tech/Alamantus/lexiconga-lite.git",
"name": "lexiconga",
"version": "2.2.1",
"description": "The quick and easy dictionary builder for constructed languages.",
"main": "template-index.html",
"repository": "https://github.com/Alamantus/Lexiconga.git",
"author": "Robbie Antenesse <dev@alamantus.com>",
"license": "UNLICENCED",
"scripts": {
"start": "concurrently \"npm run watch-js\" \"npm run watch-php\"",
"watch-js": "parcel watch index.html view.html --public-url ./",
"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": "parcel build index.html && cpx \"src/php/**/{*,.*}\" dist",
"serve-frontend-only": "parcel index.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",
"serve-frontend-only": "parcel template-index.html",
"clear": "npm run clear-dist && npm run clear-cache",
"clear-dist": "rimraf dist/*",
"clear-cache": "rimraf .cache/*"
"clear-dist": "rimraf dist/{*,.*}",
"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"
"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"
}
}

View File

@ -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
}, */
],
@ -46,7 +45,8 @@ export const DEFAULT_DICTIONARY = {
allowDuplicates: false,
caseSensitive: false,
sortByDefinition: false,
isComplete: false,
theme: 'default',
customCSS: '',
isPublic: false,
},
lastUpdated: getTimestampInSeconds(),
@ -57,6 +57,9 @@ export const DEFAULT_DICTIONARY = {
export const DEFAULT_SETTINGS = {
useIPAPronunciationField: true,
useHotkeys: true,
showAdvanced: false,
defaultTheme: 'default',
templates: [],
};
export const DEFAULT_PAGE_SIZE = 50;

BIN
src/images/ad-example.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

82
src/images/favicon.svg Normal file
View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="512"
height="512"
viewBox="0 0 480.00003 480.00003"
id="svg4162"
version="1.1"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="favicon.svg"
inkscape:export-filename="C:\Users\Robbie\Documents\Git Repositories\DictionaryBuilder\images\favicon.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4164" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="256"
inkscape:cy="257.06667"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1058"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
units="px" />
<metadata
id="metadata4167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-294.94889,118.42491)">
<g
id="g4154"
transform="matrix(3.8771366,0,0,2.7190508,-134.76396,-309.06057)"
style="stroke-width:0.0669281">
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="rect4136"
d="m 234.39344,70.455884 -42.42641,83.842656 h -80.8927 l 42.42641,-83.842656 z"
style="opacity:1;fill:#ff5500;fill-opacity:1;stroke:none;stroke-width:0.0669281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="rect4138"
d="m 191.96703,162.45587 42.42641,83.84266 h -80.8927 l -42.42641,-83.84266 z"
style="opacity:1;fill:#ff5500;fill-opacity:1;stroke:none;stroke-width:0.0669281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

121
src/images/logo.svg Normal file
View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1200"
height="630"
version="1.1"
viewBox="0 0 1125.0074 590.61647"
id="svg30"
sodipodi:docname="logo.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<defs
id="defs34" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1248"
inkscape:window-height="611"
id="namedview32"
showgrid="false"
units="px"
inkscape:zoom="0.26648629"
inkscape:cx="937.32668"
inkscape:cy="197.75442"
inkscape:window-x="2322"
inkscape:window-y="321"
inkscape:window-maximized="0"
inkscape:current-layer="svg30" />
<metadata
id="metadata2">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="matrix(4.5967881,0,0,4.5967881,-505.87772,-547.68653)"
id="g28"
style="stroke-width:0.2175432">
<g
id="lexi"
style="stroke-width:0.2175432">
<path
d="m 144.03,159.39 -11.339,22.409 h -21.62 L 122.41,159.39 Z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#ff5500;stroke-width:0.2175432" />
<path
d="m 132.69,183.97 11.339,22.409 h -21.62 L 111.07,183.97 Z"
id="path6"
inkscape:connector-curvature="0"
style="fill:#ff5500;stroke-width:0.2175432" />
<g
id="g14"
style="fill:#ff5400;stroke-width:0.2175432">
<path
d="m 160.1,186.76 v 7.0042 h -6.1056 q -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.29952 h 6.2899 v 8.9856 h -7.3498 q -5.184,0 -7.9258,-2.4653 -2.7418,-2.4883 -2.7418,-7.1655 v -12.326 q 0,-4.5159 2.5805,-7.0042 2.6035,-2.4883 7.3037,-2.4883 h 8.1332 v 9.0317 h -5.9674 q -2.4192,0 -2.4192,1.5667 0,0.92161 0.576,1.2902 0.57601,0.3456 2.0506,0.3456 z"
id="path8"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
<path
d="m 187.75,174.53 -8.1792,15.299 8.4557,16.151 h -9.8381 l -3.8707,-7.6263 -3.2717,7.6263 h -9.6768 l 8.64,-15.898 -8.1792,-15.552 h 9.746 l 3.6634,7.1424 2.9491,-7.1424 z"
id="path10"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
<path
d="m 198.95,174.53 v 31.45 h -9.3543 v -31.45 z"
id="path12"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
</g>
</g>
<g
id="conga"
style="fill:#d70000;stroke-width:0.2175432">
<path
d="m 218.5,205.38 h -3.2347 q -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.918 h 4.7381 q 6.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.13668 l -1.7768,-0.13668 q 0,-2.5057 -1.5946,-3.3713 -1.0934,-0.59227 -3.6902,-0.59227 h -1.8679 q -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.6401 z m 3.0069,-31.208 q 3.8269,0.68338 7.0616,1.549 0.63782,1.0023 1.0934,2.1412 0.50114,1.139 0.50114,2.0046 l -1.5946,-3.0524 -3.5991,-1.4123 q -1.4579,-0.18224 -2.1868,-0.18224 -0.68338,0 -1.6401,0 h -3.8725 q 1.0478,-0.13667 2.1868,-0.36447 1.6401,-0.36447 2.0501,-0.68338 z m 10.797,13.303 q -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.31891 l 4.5559,-0.13668 q 0.7745,0 1.5034,0 0.72894,0 1.5946,0.13668 l -0.0911,-8.0183 q 0.22779,2.1413 0.59226,4.3281 0.36447,2.1412 0.95673,4.6925 h -0.27335 z m -0.59226,13.531 -1.2301,-0.22779 0.31891,-0.59227 q 0.0456,-0.0911 0.0456,-0.91117 v -1.3668 q 0,-0.91117 0.2278,-1.8679 0.22779,-1.0023 0.50114,-1.959 l 0.13668,5.1481 v 1.7768 z m -23.28,-25.604 q 0.13668,-0.0456 0.63782,-0.31891 0.54671,-0.27336 0.72894,-0.27336 l 0.13668,0.18224 q 0.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.0911 l -3.7358,0.68338 -3.6447,0.54671 z m 8.6561,-0.59227 -0.95673,0.36447 -1.9135,-0.13667 q 1.3212,-0.41003 2.688,-0.82006 l 2.369,-0.13667 q -0.5467,0.13667 -1.0023,0.27335 -0.41003,0.0911 -1.1845,0.45558 z m -5.786,14.032 q 0,-0.5467 0.0911,-1.0023 0.13668,-0.50114 0.36447,-1.139 v 0.50114 q 0,0.13668 0.31891,2.1413 0.0911,0.59226 0.0911,1.2756 v 5.5582 l -0.86562,-4.1458 0.18224,-0.59226 q 0,-0.0911 -0.0911,-1.139 -0.0911,-1.0478 -0.0911,-1.4579 z m 0.72894,-5.4215 0.82006,-0.63782 q -0.36447,0.82005 -0.36447,1.6401 v 0.31891 l -0.68338,1.3668 z m 0.45559,13.759 q 0.18223,0.0455 1.0023,0.31891 1.0934,0.27335 1.6857,0.27335 l 3.3258,-0.68338 q 0.27335,-0.0456 1.0023,-0.18224 0.7745,0 1.5946,0 l 1.4123,-2.4602 q -0.36447,1.0478 -0.45559,1.4579 -0.22779,0.86561 -0.22779,1.4579 l 0.0911,0.22779 0.13667,-0.0456 q -4.237,0.41002 -8.4739,0.77449 l -1.0934,-1.139 z m 19.18,4.9659 q -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.1845 l 1.0023,0.0911 z m -4.8292,3.4169 -1.5034,0.0456 2.7335,-1.0934 q -0.31891,0.22779 -0.63782,0.50115 -0.31891,0.22779 -0.59226,0.5467 z m -24.921,-21.64 -0.13667,-4.5103 1.5946,-2.2779 q 1.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.4625 z m 5.3759,22.233 q -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.3668 l 1.6401,2.1868 z m -4.9659,-16.811 v -2.8702 q 0,-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.4123 l 0.95673,0.82006 q -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.41003 z m 20.957,16.447 q 0.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.63782 l -0.72894,-1.0478 5.9682,0.7745 q 0.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.13668 z m -21.003,-9.8407 q 0.31891,0.54671 0.59226,2.5513 0.0911,0.86561 0.27336,1.0478 l 0.50114,0.63782 -0.0456,0.18224 0.18224,0.36447 q 0.45558,0.63782 0.59226,1.7312 0.18223,1.0478 0.31891,2.0046 l 2.0501,2.2779 -2.4146,-1.8679 q -0.72894,-2.0046 -1.3668,-4.0092 -0.77449,-2.5057 -1.8679,-6.8794 l 1.1845,1.959 z m 26.06,-1.2756 q -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.18223 l -0.63782,0.68337 -1.4579,0.36447 z m -9.1117,-12.027 2.8702,1.0478 0.27335,1.0934 q -2.1868,-1.0023 -4.4192,-1.549 -2.2324,-0.59227 -4.4647,-0.59227 z"
id="path17"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
<path
d="m 262.61,195.81 q 0,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.4716 z m -12.939,8.9295 h -4.0547 q -5.9682,0 -8.7472,-2.688 -2.7791,-2.688 -2.7791,-8.6561 v -5.1026 q 0,-5.6037 2.9158,-8.2917 2.9158,-2.688 8.565,-2.688 h 3.6447 q 5.7859,0 8.7928,2.5513 3.1891,2.7335 3.1891,8.4283 v 5.1026 q 0,6.1504 -2.688,8.7472 -2.688,2.5968 -8.8384,2.5968 z m 4.4192,-16.264 q 0,-3.098 -1.0934,-4.2825 -1.0934,-1.1845 -4.1914,-1.1845 h -2.4146 q -3.0069,0 -4.1003,1.1845 -1.0934,1.1845 -1.0934,4.1914 v 4.7836 q 0,3.1435 1.0478,4.4647 1.0934,1.3212 4.1458,1.3212 h 2.4146 q 3.0524,0 4.1458,-1.3212 1.139,-1.3668 1.139,-4.5103 z m -1.2301,-11.8 q 1.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.86561 l 0.27336,-2.8702 q -3.0524,-0.63782 -5.8315,-1.2301 -2.7335,-0.59226 -3.098,-0.7745 l -1.4123,-1.1845 q 0.54671,0.0911 1.139,0.18223 0.63782,0.0456 1.0478,0.18224 z m -14.67,2.0501 q -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.50114 1.959,-0.27335 3.5991,-0.27335 0.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.0478 z m 17.221,25.832 q 1.0934,-0.50115 2.4146,-1.2301 1.5946,-0.86562 2.2779,-1.2756 l -6.3782,4.1458 -2.369,-1.3212 q 0.59227,-0.13668 2.1868,-0.18224 1.6401,-0.0455 1.8679,-0.13667 z m -3.7358,1.6401 q -0.68338,-0.13668 -1.4579,-0.27335 -0.72894,-0.0911 -1.139,-0.0911 l -0.27335,0.0911 q -0.22779,-0.0911 -0.68338,-0.27335 -0.45558,-0.13668 -0.82005,-0.36447 z m -18.315,-24.055 q 1.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.5034 l -0.13668,9.5673 q -0.13667,-1.2756 -0.36446,-2.9613 -0.45559,-3.3713 -0.45559,-5.0114 0,-3.7358 0.41003,-7.426 z m 4.4647,21.549 q 3.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.0524 z m 14.989,-13.622 0.45558,0.86561 -0.0911,6.1048 -0.59226,-1.5034 q 0.27335,-2.0046 0.27335,-3.9636 l -0.0456,-1.5034 z m -0.31891,-4.647 q 0.59226,0.45558 0.82005,1.4123 0.2278,0.95674 0.2278,2.0046 l -0.13668,1.2301 z m -10.023,0.77449 q 0,0.68338 0.13667,2.0501 0.13668,1.3668 0.13668,2.0957 v 2.1868 q 0,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.0023 l -1.1845,-0.36447 q -1.5946,-1.7768 -2.0501,-3.4624 -0.41003,-1.7312 -0.41003,-4.647 v -1.4579 q 0,-1.2756 0.27335,-2.3235 0.31891,-1.0934 1.1845,-1.8224 z m 9.5673,-2.3235 q -0.36447,0.41002 -1.2301,0.72893 -0.82005,0.27336 -1.5034,0.27336 h -3.508 l -1.5034,-1.0934 7.745,0.0911 z m -4.7836,13.986 q 0.59226,-0.0911 1.2756,-0.2278 0.68338,-0.13667 1.0934,-0.13667 l 2.4146,0.13667 -3.3258,0.72894 z"
id="path19"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
<path
d="m 287.51,205.02 q -0.82006,0 -1.6401,0 -0.68338,-0.13668 -1.0023,-0.18224 l -11.891,-20.274 v 20.866 h -7.1071 v -29.431 q 3.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.0046 h 7.0616 v 29.75 q -0.95673,0.13668 -2.9158,0.31891 -1.9135,0.18224 -4.0092,0.18224 z m -10.114,-31.39 q 0.0911,0.22779 0.31891,0.63782 0.22779,0.41003 0.31891,0.7745 l -0.0456,-0.18224 q 0.2278,0.45559 0.72894,1.4579 0.45559,1.2301 0.91117,2.4146 l -1.2301,-0.45558 q -0.0911,-1.0934 -0.36447,-2.0957 -0.22779,-1.0023 -0.63782,-2.5513 z m 18.725,1.3668 v 1.959 q 0,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.7381 z m -0.95673,30.752 0.0456,-19.772 q 0,3.2802 0.31891,9.8407 0.31891,6.5605 0.31891,9.8407 l -2.2779,0.59226 q -1.4123,0 -3.1891,-0.2278 -1.7768,-0.22779 -3.4169,-0.41002 l 8.2005,0.13667 z m -8.4283,-12.665 q -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.68338 -0.0911,0.36447 -0.18223,0.72894 l 1.139,-0.13668 z m -14.533,-18.406 q -2.5057,0.27335 -5.2848,0.7745 l -1.3212,-1.2301 z m 3.8269,-0.22779 1.0023,0.5467 -3.3713,-0.27335 z m 5.1026,24.966 q 2.1868,3.4169 4.5103,6.3782 l 4.5103,0.27335 -4.6014,0.72894 q -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.1481 l 1.0934,-0.22779 q 0.27336,2.4602 2.5057,5.9682 1.7768,2.5968 3.5991,5.1481 z m -15.9,5.6493 q -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.4123 -0.0911,-0.59224 -0.31891,-2.0957 -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.6129 z m 7.6083,0.59227 q 0.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.9135 l 0.18224,-0.18223 q -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.77449 v -0.2278 h 1.2756 l 0.0456,8.155 -1.2301,-0.13667 z m -8.4283,-28.747 q 0,1.2756 -0.41003,12.848 -0.13667,3.8725 -0.13667,7.6994 h 0.45558 q 0,-5.1481 -0.22779,-11.162 -0.22779,-6.0137 -0.68338,-10.57 l 1.2301,-1.0934 q -0.22779,1.2756 -0.22779,2.2779 z m 0.68338,29.613 0.18223,-0.95673 h 0.50115 q 1.7312,0 3.7814,0.22779 2.0957,0.22779 3.7814,0.59226 z m 29.294,-33.076 1.6401,0.59226 -0.59226,0.63782 q -0.86562,-0.0911 -1.7768,-0.18224 -0.91117,-0.0911 -1.6857,-0.0911 h -2.2324 l -2.7335,0.0911 q 1.0023,-0.59226 2.0046,-0.82005 1.0478,-0.22779 2.2324,-0.22779 z"
id="path21"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
<path
d="m 314.06,205.29 h -3.1891 q -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.2825 h 4.6014 q 4.8748,0 7.9728,2.2324 3.508,2.5057 3.508,7.1982 h -7.426 q -0.31891,-1.9135 -2.1868,-2.5968 -1.2756,-0.45558 -3.6902,-0.45558 H 311.6 q -3.2347,0 -4.3281,1.7312 -0.82006,1.3212 -0.82006,4.7837 v 3.5536 q 0,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.5559 h -5.1937 v -5.6948 q 1.4123,0 4.2825,0.18224 2.8702,0.13667 3.918,0.13667 2.2779,0 4.3281,-0.31891 v 7.4716 q 0,5.6493 -3.2802,8.1094 -2.8702,2.1413 -8.7472,2.1413 z m 10.114,-29.203 2.0501,2.8246 q 0.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.22779 l 0.72894,-0.95673 z m -3.098,-0.31891 -3.4169,-0.82006 q -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.0456 l -7.016,-0.63782 12.848,0.22779 2.6424,1.139 z m 2.2779,0.45558 v 0.27335 l -1.7768,-0.31891 1.7768,0.0456 z m 3.9636,14.305 -0.13667,0.7745 q -0.18224,0.91117 -0.31891,2.5057 -0.13668,1.549 -0.27336,2.7791 v -0.36446 q 0,-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.0023 z m -16.037,-7.6538 h 1.6401 q 1.549,0.63782 3.2802,1.3212 1.7312,0.63782 3.0069,0.86561 l 4.7836,0.18223 2.369,0.0911 -1.0934,0.63782 q -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.2756 l -3.1435,-0.27333 z m -8.0639,-6.9249 q -0.68338,0.5467 -2.0501,1.959 -1.0023,1.0478 -2.0046,2.0501 1.139,-2.0957 2.2779,-4.237 l 3.8269,-0.68338 z m 8.9295,17.449 q 0,-1.0934 0.0911,-3.6902 0.0911,-2.6424 0.0911,-2.8246 l 0.72894,0.18224 -0.0456,0.86561 q 0,0.86561 0.0456,2.5968 0.0456,1.73119 0.0456,2.6424 l 3.6447,1.2756 1.4579,2.688 -0.27335,-2.5513 q -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.45559 z m 11.207,11.162 q 0.63782,0 0.91118,-0.59226 0.68337,-1.5946 1.139,-3.1891 0.45559,-1.6401 0.68338,-3.3258 l 1.139,0.45558 -0.36446,-0.86561 q -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.0911 l 1.139,-0.63783 q 0.54671,0.18224 0.95673,0.18224 z m -15.718,-10.433 q 0,-0.22779 -0.0911,-3.4169 -0.0911,-3.1891 -0.0911,-4.5103 v -0.5467 q 0,-0.91117 0.18224,-1.2301 0.22779,-0.31891 0.5467,-0.31891 0.31891,0 0.59226,0.41003 0.0911,0.13667 0.0911,0.31891 0,1.0934 -0.7745,4.4647 -1.0934,4.8292 -1.139,4.9659 l 1.6401,3.2802 -0.0911,0.0456 q -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.41003 z m 1.0934,12.073 q 2.0501,0 6.1049,-0.36447 4.0547,-0.36447 6.0593,-0.36447 l -1.139,0.82006 q -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.82006 z m -10.433,-11.071 q -0.31891,0.0911 -0.50114,0.45559 l 1.0478,1.1845 q -0.68338,-1.4123 -0.86562,-2.5057 -0.13667,-1.139 -0.13667,-3.0069 v -2.4146 q 0,-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.1914 l 1.8224,-0.82006 q -1.2756,1.0934 -4.4192,5.6493 -3.1435,4.5103 -3.1435,5.467 l -0.0456,3.9636 q 0,1.3212 0.13668,3.918 0.18223,2.5968 0.41002,2.8246 z m -1.6857,-5.9226 q 0.27335,2.3235 0.68338,4.4192 0.41002,2.0501 0.63782,2.4146 l -0.2278,0.59226 q 2.4602,2.5968 4.8292,4.6014 3.098,2.6424 5.9226,4.0547 3.4169,1.7312 6.3782,1.7312 l 4.1914,-0.27335 q -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.5968 z m 12.756,8.6561 8.2006,-0.0911 -0.82005,0.50114 -6.4693,0.0456 -0.91118,-0.45558 z m 4.6014,-10.843 10.57,0.31891 q -1.0023,0 -2.2324,0.0456 -1.1845,0 -2.6424,0 h -0.95673 q -1.139,0 -2.5968,-0.0456 -1.4579,-0.0456 -2.1413,-0.31891 z"
id="path23"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
<path
d="m 345.75,204.47 -1.6401,-6.3782 h -8.1094 l -1.7312,6.3782 h -5.8771 l 7.0616,-26.515 h 9.1117 l 7.0616,26.515 z m -5.7404,-21.504 -2.9158,10.752 h 5.8315 z m -2.1413,-6.3326 q 0.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.0456 h 0.82005 l -0.0456,0.63782 q -0.18223,0 -2.9613,0.0911 -2.7335,0.0911 -3.9636,0.0911 h -2.5513 q 0.45559,-0.13668 0.91117,-0.36447 0.68338,-0.36447 0.72894,-0.59226 0.27335,-0.0456 0.68338,-0.0456 0.41003,0 0.95673,0 z m 7.0616,0.63782 1.139,0.5467 q 0.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.5559 z m 5.7404,22.688 -0.18224,0.41003 q -0.0456,-0.59226 -0.0911,-1.2301 0,-4.2825 -1.0934,-7.4716 -0.45559,-1.3212 -1.8679,-4.647 l 0.95673,-0.36447 q 0.59226,3.5536 1.0478,5.467 0.22779,1.0023 2.2779,8.8839 l -1.0478,-1.0478 z m 1.0478,1.7312 q 0.36446,0.95673 0.95673,1.8223 0.95673,1.4123 1.0934,1.5946 l -1.0478,-0.18223 q -0.13667,-0.36447 -0.45558,-1.0478 -0.27335,-0.68338 -0.36447,-1.0934 -0.0911,-0.45558 -0.18223,-1.0934 z m -17.631,-22.962 0.68338,-0.36446 q -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.071 z m 6.2871,19.454 q 0.86562,0 1.959,0.18223 0.36447,0.0456 1.139,0.18223 l -0.18223,-0.45558 q -2.9158,0 -4.2825,0.50114 -0.31891,0.13668 -2.7791,1.1845 l 0.54671,-1.4579 z m -13.394,6.2415 q 0.86561,-1.0934 2.369,-7.0616 0.95673,-3.9636 1.9135,-7.9728 l -3.4625,15.809 z m 6.1048,-24.647 0.27335,-0.18223 q -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.2871 z m 10.387,20 q 0.36447,-0.0456 1.0023,2.5513 0.68338,2.5968 1.0478,2.5968 0.86561,0 1.8223,0 0.95669,0 2.0046,-0.0456 1.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.0911 l -5.1026,-0.13667 q -0.13668,0.0456 -0.18224,-0.13668 0,-0.18223 0,-0.63782 v -0.68338 q 0,-0.63782 -0.18223,-1.7768 -0.18224,-1.1845 -0.41003,-2.5513 z m -7.745,0.77449 q 0.18223,0.59226 0.18223,1.4579 0,0.5467 -0.0456,1.1845 -0.0455,0.63782 -0.0455,0.68338 l 0.13667,1.4123 h -1.8679 q -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.0934 z m 6.0137,-11.982 -1.2301,-2.5057 1.0934,4.237 z m 0.54671,4.5559 -0.54671,0.22779 -0.0456,-0.86561 v -0.27335 l 0.0456,-0.50115 z m -2.2779,-8.7017 -1.9135,8.7017 3.0524,-0.0456 0.0911,0.41003 h -3.6447 l 2.4146,-9.0662 z"
id="path25"
inkscape:connector-curvature="0"
style="stroke-width:0.2175432" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

BIN
src/images/social.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1,14 +1,19 @@
import './main.scss';
import migrate from './js/migration';
import setupListeners from './js/setupListeners';
import { renderAll } from './js/render';
import { hasToken } from './js/utilities';
import { hasToken, addMessage } from './js/utilities';
import { loadDictionary } from './js/dictionaryManagement';
import { loadSettings } from './js/settings';
function initialize() {
loadDictionary();
if (window.isOffline) {
addMessage('<strong>You are using the Offline version of Lexiconga.</strong><br>Refresh the page while connected to the internet to enable using accounts.', 0);
}
migrate();
loadSettings();
loadDictionary();
setupListeners();
if (hasToken()) {
@ -16,13 +21,11 @@ function initialize() {
account.loginWithToken();
});
}
renderAll();
}
window.onload = (function (oldLoad) {
return function () {
oldLoad && oldLoad();
initialize();
}
oldLoad && oldLoad();
initialize();
})(window.onload);

View File

@ -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='&quot)'>
<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='&quot&quot'>
<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>

View File

@ -9,17 +9,17 @@ that allow you to type any IPA symbol with only a standard keyboard.</p>
<li>
<p>Many symbols that look similar to letters of the English alphabet (such as
small caps) can be produced by doubling the capital letter:</p>
<pre><code> GG -&gt; ɢ ?? -&gt; ʔ NN -&gt; ɴ BB -&gt; ʙ RR -&gt; ʀ XX -&gt; χ
LL -&gt; ʟ II -&gt; ɪ YY -&gt; ʏ UU -&gt; ʊ EE -&gt; ɛ OO -&gt; ɞ
AA -&gt; ɑ '' -&gt; ˈ ,, -&gt; ˌ :: -&gt; ː
<pre><code> GG &rarr; ɢ ?? &rarr; ʔ NN &rarr; ɴ BB &rarr; ʙ RR &rarr; ʀ XX &rarr; χ
LL &rarr; ʟ II &rarr; ɪ YY &rarr; ʏ UU &rarr; ʊ EE &rarr; ɛ OO &rarr; ɞ
AA &rarr; ɑ '' &rarr; ˈ ,, &rarr; ˌ :: &rarr; ː
</code></pre>
</li>
<li>
<p>The slash is used to &quot;reflect,&quot; &quot;rotate,&quot; or otherwise flip around a given
symbol:</p>
<pre><code> ʀ/ -&gt; ʁ ʔ/ -&gt; ʕ ?/ -&gt; ʕ r/ -&gt; ɹ y/ -&gt; ʎ m/ -&gt; ɯ
o/ -&gt; ø e/ -&gt; ə ɛ/ -&gt; ɜ c/ -&gt; ɔ a/ -&gt; ɐ ɑ/ -&gt; ɒ
w/ -&gt; ʍ h/ -&gt; ɥ k/ -&gt; ʞ !/ -&gt; ¡ v/ -&gt; ʌ
<pre><code> ʀ/ &rarr; ʁ ʔ/ &rarr; ʕ ?/ &rarr; ʕ r/ &rarr; ɹ y/ &rarr; ʎ m/ &rarr; ɯ
o/ &rarr; ø e/ &rarr; ə ɛ/ &rarr; ɜ c/ &rarr; ɔ a/ &rarr; ɐ ɑ/ &rarr; ɒ
w/ &rarr; ʍ h/ &rarr; ɥ k/ &rarr; ʞ !/ &rarr; ¡ v/ &rarr; ʌ
</code></pre>
<p>Also note that for any digraph, if either of the two characters that
compose it are not &quot;standard&quot; letters you can find on your keyboard, the
@ -35,26 +35,26 @@ visual <code>o|</code> and <code>|o</code>.</p>
characters, or two characters next to each other. This includes &quot;hooked&quot;
letters (such as ŋ), produced with the original letter and a comma, and
&quot;stroked&quot; letters (such as ɟ), produced with the original letter and a dash:</p>
<pre><code> m, -&gt; ɱ n, -&gt; ŋ ŋ, -&gt; ɲ v, -&gt; ⱱ c, -&gt; ç j, -&gt; ʝ
x, -&gt; ɣ ɣ, -&gt; χ h, -&gt; ɦ w, -&gt; ɰ
<pre><code> m, &rarr; ɱ n, &rarr; ŋ ŋ, &rarr; ɲ v, &rarr; ⱱ c, &rarr; ç j, &rarr; ʝ
x, &rarr; ɣ ɣ, &rarr; χ h, &rarr; ɦ w, &rarr; ɰ
j- -&gt; ɟ h- -&gt; ħ l- -&gt; ɬ i- -&gt; ɨ u- -&gt; ʉ e- -&gt; ɘ
o- -&gt; ɵ ʕ- -&gt; ʢ ?- -&gt; ʡ ʔ- -&gt; ʡ
j- &rarr; ɟ h- &rarr; ħ l- &rarr; ɬ i- &rarr; ɨ u- &rarr; ʉ e- &rarr; ɘ
o- &rarr; ɵ ʕ- &rarr; ʢ ?- &rarr; ʡ ʔ- &rarr; ʡ
LZ -&gt; ɮ OX -&gt; ɤ XO -&gt; ɤ OE -&gt; œ EB -&gt; ɞ AE -&gt; æ
CE -&gt; ɶ RL -&gt; ɺ LR -&gt; ɺ ɾl -&gt; ɺ lɾ -&gt; ɺ
LZ &rarr; ɮ OX &rarr; ɤ XO &rarr; ɤ OE &rarr; œ EB &rarr; ɞ AE &rarr; æ
CE &rarr; ɶ RL &rarr; ɺ LR &rarr; ɺ ɾl &rarr; ɺ lɾ &rarr; ɺ
w| -&gt; ɰ o/ -&gt; ø ɜ( -&gt; ɞ /\ -&gt; ʌ o| -&gt; ɑ a| -&gt; ɑ
|o -&gt; ɒ |a -&gt; ɒ
w| &rarr; ɰ o/ &rarr; ø ɜ( &rarr; ɞ /\ &rarr; ʌ o| &rarr; ɑ a| &rarr; ɑ
|o &rarr; ɒ |a &rarr; ɒ
o. -&gt; ʘ |= -&gt; ǂ || -&gt; ‖ /^ -&gt; ↗ /&gt; -&gt; ↗ \v -&gt; ↘
\&gt; -&gt; ↘
o. &rarr; ʘ |= &rarr; ǂ || &rarr; ‖ /^ &rarr; ↗ /&gt; &rarr; ↗ \v &rarr; ↘
\&gt; &rarr; ↘
</code></pre>
</li>
<li>
<p>Others are based on pronunciation:</p>
<pre><code> PH -&gt; ɸ BH -&gt; β TH -&gt; θ DH -&gt; ð SH -&gt; ʃ ZH -&gt; ʒ
SJ -&gt; ɕ ZJ -&gt; ʑ ʃx -&gt; ɧ xʃ -&gt; ɧ
<pre><code> PH &rarr; ɸ BH &rarr; β TH &rarr; θ DH &rarr; ð SH &rarr; ʃ ZH &rarr; ʒ
SJ &rarr; ɕ ZJ &rarr; ʑ ʃx &rarr; ɧ xʃ &rarr; ɧ
</code></pre>
<p>It may be worth noting at this point that digraphs of two lowercase letters
were intentionally avoided to prevent interference with regular typing. If
@ -66,7 +66,7 @@ to the second character.</p>
</li>
<li>
<p>A few digraphs are based on shape:</p>
<pre><code> rO -&gt; ɾ r0 -&gt; ɾ vO -&gt; ʋ v0 -&gt; ʋ
<pre><code> rO &rarr; ɾ r0 &rarr; ɾ vO &rarr; ʋ v0 &rarr; ʋ
</code></pre>
<p>Another related point: digraphs that contain a lowercase letter can also be
typed with that letter as uppercase. So, if <code>RO</code> is easier to type than
@ -75,35 +75,35 @@ typed with that letter as uppercase. So, if <code>RO</code> is easier to type th
<li>
<p>Retroflex and nonpulmonic symbols have their own categories:</p>
<pre><code> retroflex: ) looks like the shape of the tongue
t) -&gt; ʈ d) -&gt; ɖ n) -&gt; ɳ r) -&gt; ɽ ɾ) -&gt; ɽ s) -&gt; ʂ
z) -&gt; ʐ ɹ) -&gt; ɻ l) -&gt; ɭ ɗ) -&gt; ᶑ
t) &rarr; ʈ d) &rarr; ɖ n) &rarr; ɳ r) &rarr; ɽ ɾ) &rarr; ɽ s) &rarr; ʂ
z) &rarr; ʐ ɹ) &rarr; ɻ l) &rarr; ɭ ɗ) &rarr; ᶑ
clicks: clicking noise reminiscent of a *
o* -&gt; ʘ |* -&gt; ǀ !* -&gt; ǃ =* -&gt; ǁ
o* &rarr; ʘ |* &rarr; ǀ !* &rarr; ǃ =* &rarr; ǁ
implosives and ejective marker: direction of airflow
b( -&gt; ɓ d( -&gt; ɗ j( -&gt; ʄ ɟ( -&gt; ʄ g( -&gt; ɠ ɢ( -&gt; ʛ
ɖ( -&gt; ᶑ ') -&gt; ʼ
b( &rarr; ɓ d( &rarr; ɗ j( &rarr; ʄ ɟ( &rarr; ʄ g( &rarr; ɠ ɢ( &rarr; ʛ
ɖ( &rarr; ᶑ ') &rarr; ʼ
</code></pre>
</li>
<li>
<p>Superscripts and diacritics that go above the letter use <code>^</code>, diacritics that
go below use <code>_</code>, and miscellaneous &quot;moved&quot; symbols
use <code>&lt;</code> or <code>&gt;</code>:</p>
<pre><code> ^h -&gt; ʰ ^n -&gt; ⁿ ^m -&gt; ᵐ ^ŋ -&gt; ᵑ ^l -&gt; ˡ ^w -&gt; ʷ
^j -&gt; ʲ ^ɥ -&gt; ᶣ ^ʋ -&gt; ᶹ ^ɣ -&gt; ˠ ^ʕ -&gt; ˤ
<pre><code> ^h &rarr; ʰ ^n &rarr; ⁿ ^m &rarr; ᵐ ^ŋ &rarr; ᵑ ^l &rarr; ˡ ^w &rarr; ʷ
^j &rarr; ʲ ^ɥ &rarr; ᶣ ^ʋ &rarr; ᶹ ^ɣ &rarr; ˠ ^ʕ &rarr; ˤ
_| -&gt; ◌̩ ^| -&gt; ◌̍ _o -&gt; ◌̥ ^o -&gt; ◌̊ _v -&gt; ◌̬ ^v -&gt; ◌̌
_| &rarr; ◌̩ ^| &rarr; ◌̍ _o &rarr; ◌̥ ^o &rarr; ◌̊ _v &rarr; ◌̬ ^v &rarr; ◌̌
_^ -&gt; ◌̯ _: -&gt; ◌̤ _~ -&gt; ◌̰ _[ -&gt; ◌̪ _] -&gt; ◌̺ _{ -&gt; ◌̼
_+ -&gt; ◌̟ __ -&gt; ◌̠ _) -&gt; ◌̹ _( -&gt; ◌̜ _# -&gt; ◌̻ [] -&gt; ◌̻
_^ &rarr; ◌̯ _: &rarr; ◌̤ _~ &rarr; ◌̰ _[ &rarr; ◌̪ _] &rarr; ◌̺ _{ &rarr; ◌̼
_+ &rarr; ◌̟ __ &rarr; ◌̠ _) &rarr; ◌̹ _( &rarr; ◌̜ _# &rarr; ◌̻ [] &rarr; ◌̻
^&gt; -&gt; ◌̚ ^: -&gt; ◌̈ ^x -&gt; ◌̽ ^~ -&gt; ◌̃
^&gt; &rarr; ◌̚ ^: &rarr; ◌̈ ^x &rarr; ◌̽ ^~ &rarr; ◌̃
-' -&gt; ˔ _˔ -&gt; ◌̝ -, -&gt; ˕ _˕ -&gt; ◌̞ &lt;| -&gt; ⊣ _⊣ -&gt; ◌̘
&gt;| -&gt; ⊢ _⊢ -&gt; ◌̙
-' &rarr; ˔ _˔ &rarr; ◌̝ -, &rarr; ˕ _˕ &rarr; ◌̞ &lt;| &rarr; ⊣ _⊣ &rarr; ◌̘
&gt;| &rarr; ⊢ _⊢ &rarr; ◌̙
~~ -&gt; ◌̴ &gt;r -&gt; ˞
~~ &rarr; ◌̴ &gt;r &rarr; ˞
</code></pre>
<p>Note that all of these sequences can be flipped in order—that is, <code>^h</code>
produces the same thing as <code>h^</code>. This allows usage such as <code>|&lt;_</code> to produce
@ -111,16 +111,16 @@ produces the same thing as <code>h^</code>. This allows usage such as <code>|&lt
</li>
<li>
<p>Tonal countours use numbers plus <code>|</code>:</p>
<pre><code> 5| -&gt; ˥ 4| -&gt; ˦ 3| -&gt; ˧ 2| -&gt; ˨ 1| -&gt; ˩
+| -&gt; ꜛ -| -&gt; ꜜ
<pre><code> 5| &rarr; ˥ 4| &rarr; ˦ 3| &rarr; ˧ 2| &rarr; ˨ 1| &rarr; ˩
+| &rarr; ꜛ -| &rarr; ꜜ
</code></pre>
</li>
<li>
<p>Finally, some digraphs simply have unique mnemonics:</p>
<pre><code> ː- -&gt; ˑ &quot;chop off&quot; the bottom triangle
(( -&gt; ◌͡◌ two parens for a tie that connects 2 chars
)) -&gt; ◌͜◌
◌͜◌) -&gt; ‿ one more paren to make it a little bit longer
<pre><code> ː- &rarr; ˑ &quot;chop off&quot; the bottom triangle
(( &rarr; ◌͡◌ two parens for a tie that connects 2 chars
)) &rarr; ◌͜◌
◌͜◌) &rarr; ‿ one more paren to make it a little bit longer
</code></pre>
</li>
</ul>

View File

@ -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,

View File

@ -8,7 +8,6 @@ export function request (data = {}, success = () => {}, error = () => {}/* , fai
credentials: 'same-origin',
}).then(res => res.json())
.then(response => {
console.log('Success:', JSON.stringify(response));
if (response.error) {
return error(response.data);
}

View File

@ -30,9 +30,9 @@ export function logIn() {
saveToken(successData.token);
window.account = successData.user;
}, errorData => {
errorHTML += errorData;
errorHTML += '<p class="bold red">' + errorData + '</p>';
}).then(() => {
createAccountErrorMessages.innerHTML = errorHTML;
loginErrorMessages.innerHTML = errorHTML;
if (errorHTML === '') {
const loginModal = document.getElementById('loginModal');
loginModal.parentElement.removeChild(loginModal);
@ -79,7 +79,6 @@ export function createAccount() {
}).then(() => {
createAccountErrorMessages.innerHTML = errorHTML;
if (errorHTML === '') {
console.log('creating account');
request({
action: 'create-account',
email,
@ -94,12 +93,9 @@ export function createAccount() {
if (responseData.hasOwnProperty('dictionary')) {
uploadWholeDictionary(); // Saves external id
}
return responseData;
}, errorData => {
errorHTML += `<p class="bold red">${errorData}</p>`;
return errorData;
}).then(responseData => {
console.log(responseData);
errorHTML += `<p class="bold red">${errorData}</p>`;
}).then(() => {
createAccountErrorMessages.innerHTML = errorHTML;
if (errorHTML === '') {
const loginModal = document.getElementById('loginModal');

View File

@ -0,0 +1,113 @@
import { request } from "./helpers";
export function renderForgotPasswordForm() {
const modal = document.createElement('section');
modal.classList.add('modal');
modal.innerHTML = `<div class="modal-background"></div>
<div class="modal-content">
<a class="close-button">&times;&#xFE0E;</a>
<section class="info-modal" id="forgotPasswordForm">
<h2>Forgot Password</h2>
<p>Enter the email address associated with your Lexiconga account to initiate a password reset.</p>
<label>Email<br>
<input type="email" id="forgotPasswordEmailField" style="max-width:250px;" maxlength="100">
</label>
<section id="forgotPasswordErrorMessages"></section>
<button class="button" id="forgotPasswordSubmit">Email Password Reset Key</button>
</section>
</div>`;
document.body.appendChild(modal);
setupStartResetForm();
setupInfoModal(modal);
}
function setupInfoModal(modal) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
modal.parentElement.removeChild(modal);
});
});
}
function setupStartResetForm() {
document.getElementById('forgotPasswordSubmit').addEventListener('click', startPasswordReset);
}
function startPasswordReset() {
const email = document.getElementById('forgotPasswordEmailField').value.trim();
const errorMessageElement = document.getElementById('forgotPasswordErrorMessages');
let errorMessage = '';
if (email === '') {
errorMessage += '<p class="red bold">Please enter an email address.</p>';
}
errorMessageElement.innerHTML = errorMessage;
if (errorMessage === '') {
request({
action: 'initiate-password-reset',
email,
}, success => {
console.log(success);
}, error => {
errorMessage += '<p class="red bold">' + error + '</p>';
}).then(() => {
errorMessageElement.innerHTML = errorMessage;
if (errorMessage === '') {
document.getElementById('forgotPasswordForm').innerHTML = `<h2>Password Reset Key Sent</h2>
<p>Go check your email for the password reset link.</p>
<p><em>Note that it may be sent to your spam/junk folder by mistake.</em></p>`;
}
});
}
}
function setupPasswordResetForm() {
const submitButton = document.getElementById('newPasswordSubmit');
if (submitButton) {
submitButton.addEventListener('click', submitPasswordReset);
}
}
function submitPasswordReset() {
const password = document.getElementById('newPassword').value;
const confirm = document.getElementById('newConfirm').value;
const account = document.getElementById('account').value;
const errorMessageElement = document.getElementById('newPasswordErrorMessages');
let errorMessage = '';
if (password === '') {
errorMessage += '<p class="red bold">Please enter a password.</p>';
} else if (password !== confirm) {
errorMessage += '<p class="red bold">The passwords do not match.</p>';
}
errorMessageElement.innerHTML = errorMessage;
if (errorMessage === '') {
request({
action: 'password-reset',
account,
password,
}, success => {
console.log(success);
}, error => {
errorMessage += '<p class="red bold">' + error + '</p>';
}).then(() => {
errorMessageElement.innerHTML = errorMessage;
if (errorMessage === '') {
document.getElementById('detailsPanel').innerHTML = `<h3>Your password has been reset</h3>
<p>You can now <a href="/">Return to Lexiconga</a> and log in using your new password.</p>`;
}
});
}
}
window.onload = (function (oldLoad) {
oldLoad && oldLoad();
setTimeout(setupPasswordResetForm, 1000);
})(window.onload);

View File

@ -1,4 +1,5 @@
import { setupLoginModal, setupChangeDictionary, setupCreateNewDictionary, setupDeletedDictionaryChangeModal } from "./setupListeners";
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>
@ -55,12 +57,25 @@ export function renderLoginForm() {
export function renderMakePublic() {
const editSettingsTab = document.getElementById('editSettingsTab');
const editSettingsTabHTML = `<label>Make Public
<input type="checkbox" id="editIsPublic"><br>
<small>Checking this box will make this public via a link you can share with others.</small>
</label>
`;
editSettingsTab.innerHTML += editSettingsTabHTML;
const { isPublic } = window.currentDictionary.settings;
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() {
@ -79,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 1315 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 1315 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;

View File

@ -1,6 +1,9 @@
import { logIn, createAccount } from "./login";
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');
@ -35,6 +38,7 @@ export function setupLoginModal(modal) {
});
document.getElementById('loginSubmit').addEventListener('click', logIn);
document.getElementById('forgotPasswordButton').addEventListener('click', renderForgotPasswordForm);
document.getElementById('createAccountSubmit').addEventListener('click', createAccount);
}
@ -60,3 +64,15 @@ export function setupDeletedDictionaryChangeModal() {
}
document.getElementById('createNewDictionaryAfterDelete').addEventListener('click', createNewDictionary);
}
export function setupMakePublic() {
document.getElementById('editIsPublic').addEventListener('change', function(event) {
document.getElementById('publicLinkDisplay').style.display = event.target.checked ? '' : 'none';
});
document.getElementById('publicLinkCopy').addEventListener('click', function() {
document.getElementById('publicLink').select();
document.execCommand('copy');
addMessage('Copied public link to clipboard', 3000);
});
setupMaximizeButtons();
}

View File

@ -1,41 +1,14 @@
import { addMessage } from "../utilities";
import { saveDictionary, clearDictionary } from "../dictionaryManagement";
import { request } from "./helpers";
import { saveToken } from "./utilities";
import { saveToken, dictionaryIsDefault, getPublicLink } from "./utilities";
import { renderAll } from "../render";
import { sortWords } from "../wordManagement";
import { getLocalDeletedWords, clearLocalDeletedWords, saveDeletedWordsLocally } from "./utilities";
import { renderChangeDictionaryOptions } from "./render";
/* Outline for syncing
login
-> check local dictionary id
(DONE!) ? no id
-> upload dictionary
-> make new dictionary current
(Canceled) ? mismatched id
-> sync local dictionary (see 'same id' below)
-> if no matching remote id, ignore (assume deleted)
-> clear local dictionary
-> insert downloaded dictionary
(DONE!) ? same id
-> compare detail last updated timestamp
? downloaded details are newer
-> replace local details
? local details are newer
-> flag to upload details
-> filter deleted words from current words
-- check id and compare deletedOn with createdOn
-> compare each word and by lastUpdated/createdOn
? downloaded word is newer
-> update local word
? local word is newer
-> put word in an array to upload
-> upload anything that needs update
*/
export function syncDictionary(uploadAsNewIfNoExternalID = true) {
if (!window.currentDictionary.hasOwnProperty('externalID')) {
if (!window.currentDictionary.hasOwnProperty('externalID') && !dictionaryIsDefault()) {
uploadWholeDictionary(uploadAsNewIfNoExternalID);
} else {
addMessage('Syncing...');
@ -64,7 +37,14 @@ export function performSync(remoteDictionary) {
syncWords(remoteDictionary.words, remoteDictionary.deletedWords).then(success => {
if (success) {
renderAll();
document.getElementById('accountSettingsChangeDictionary').value = window.currentDictionary.externalID;
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');
}
@ -100,6 +80,9 @@ export function uploadWholeDictionary(asNew = false) {
dictionary,
}, remoteId => {
window.currentDictionary.externalID = remoteId;
if (document.getElementById('publicLink')) {
document.getElementById('publicLink').value = getPublicLink();
}
saveDictionary(false);
addMessage('Dictionary Uploaded Successfully');
renderChangeDictionaryOptions();
@ -191,10 +174,11 @@ export function syncWords(remoteWords, deletedWords) {
}
remoteWords.forEach(remoteWord => {
const 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);

View File

@ -1,11 +1,42 @@
import { setCookie } from "../StackOverflow/cookie";
import { DELETED_WORDS_LOCALSTORAGE_KEY } from "./constants";
import { getTimestampInSeconds } from "../../helpers";
import { getTimestampInSeconds, cloneObject } from "../../helpers";
import { DEFAULT_DICTIONARY } from "../../constants";
export function saveToken(token) {
setCookie('token', token, 30);
}
export function dictionaryIsDefault() {
const defaultDictionary = cloneObject(DEFAULT_DICTIONARY);
delete defaultDictionary.settings.theme;
delete defaultDictionary.lastUpdated;
delete defaultDictionary.createdOn;
delete defaultDictionary.version;
const currentDictionary = cloneObject(window.currentDictionary);
delete currentDictionary.settings.theme;
delete currentDictionary.lastUpdated;
delete currentDictionary.createdOn;
delete currentDictionary.version;
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 => {

20
src/js/announcements.js Normal file
View File

@ -0,0 +1,20 @@
import { fadeOutElement } from "./utilities";
import { setCookie, getCookie } from "./StackOverflow/cookie";
export function isDismissed(announcementId) {
let dismissed = getCookie(announcementId);
return dismissed === 'dismissed';
}
export function dismiss(announcement) {
if (announcement.id) {
const expireDate = announcement.dataset.expires;
const now = new Date();
const expire = new Date(expireDate);
const timeDiff = Math.abs(now.getTime() - expire.getTime());
const dayDifference = Math.ceil(timeDiff / (1000 * 3600 * 24));
setCookie(announcement.id, 'dismissed', dayDifference + 1);
}
fadeOutElement(announcement)
}

View File

@ -1,33 +1,39 @@
import { renderDictionaryDetails, renderPartsOfSpeech, renderAll } from "./render";
import papa from 'papaparse';
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, 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;
@ -35,54 +41,77 @@ export function openEditModal() {
document.getElementById('editCaseSensitive').checked = caseSensitive;
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());
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());
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;
if (hasToken()) {
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();
renderDictionaryDetails();
renderPartsOfSpeech();
if (objectValuesAreDifferent(updatedDictionary, window.currentDictionary)) {
window.currentDictionary = Object.assign(window.currentDictionary, updatedDictionary);
if (needsReSort) {
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.');
}
}
@ -110,6 +139,7 @@ export function loadDictionary() {
export function clearDictionary() {
window.currentDictionary = cloneObject(DEFAULT_DICTIONARY);
window.currentDictionary.settings.theme = window.settings.defaultTheme;
}
export function deleteDictionary() {
@ -129,7 +159,7 @@ export function deleteDictionary() {
export function confirmDeleteDictionary() {
if (confirm(`Are you sure you want to delete your ${window.currentDictionary.name} ${window.currentDictionary.specification}?\n\nThis cannot be undone!`)) {
const input = prompt(`If you really want to delete your ${window.currentDictionary.name} ${window.currentDictionary.specification} please type DELETE in the text box.\n\nAfter you confirm, cour dicitonary will be PERMANENTLY AND IRRETRIEVABLY DESTROYED!`);
console.log(input);
if (input === 'DELETE') {
deleteDictionary();
document.getElementById('editModal').style.display = 'none';
@ -196,50 +226,90 @@ export function importWords() {
if (importWordsField.files.length === 1) {
if (confirm('Importing a CSV file with words will add all of the words in the file to your dictionary regardless of duplication!\nDo you want to continue?')) {
addMessage('Importing words...');
import('papaparse').then(papa => {
const importedWords = [];
papa.parse(importWordsField.files[0], {
header: true,
encoding: "utf-8",
step: results => {
if (results.errors.length > 0) {
results.errors.forEach(err => {
addMessage('Error Importing Word: ' + err, undefined, 'error');
console.error('Error Importing Word: ', err)
});
} else {
const row = results.data[0];
const importedWord = addWord({
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);
importedWords.push(importedWord);
const importedWords = [];
papa.parse(importWordsField.files[0], {
header: true,
encoding: "utf-8",
step: results => {
if (results.errors.length > 0) {
results.errors.forEach(err => {
addMessage('Error Importing Word: ' + err, undefined, 'error');
console.error('Error Importing Word: ', err)
});
} else {
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(),
};
if (typeof row['etymology'] !== 'undefined') {
const etymology = removeTags(row['etymology']).trim().split(',').filter(etymology => etymology.trim() !== '');
if (etymology.length > 0) {
wordToImport.etymology = etymology;
}
}
},
complete: () => {
saveDictionary(false);
renderAll();
importWordsField.value = '';
document.getElementById('editModal').style.display = 'none';
addMessage(`Done Importing ${importedWords.length} Words`);
if (hasToken()) {
import('./account/index.js').then(account => {
account.syncImportedWords(importedWords);
});
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;
}
}
},
error: err => {
addMessage('Error Importing Words: ' + err, undefined, 'error');
console.error('Error Importing Words: ', err);
},
skipEmptyLines: true,
});
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: () => {
sortWords(false);
renderAll();
importWordsField.value = '';
document.getElementById('editModal').style.display = 'none';
addMessage(`Done Importing ${importedWords.length} Words`);
if (hasToken()) {
import('./account/index.js').then(account => {
account.syncImportedWords(importedWords);
});
}
},
error: err => {
addMessage('Error Importing Words: ' + err, undefined, 'error');
console.error('Error Importing Words: ', err);
},
skipEmptyLines: true,
});
}
}
@ -262,55 +332,23 @@ export function exportWords() {
addMessage('Exporting Words...');
setTimeout(() => {
import('papaparse').then(papa => {
const { name, specification } = window.currentDictionary;
const fileName = slugify(name + '_' + specification) + '_words.csv';
const words = window.currentDictionary.words.map(word => {
return {
word: word.name,
pronunciation: word.pronunciation,
'part of speech': word.partOfSpeech,
definition: word.definition,
explanation: word.details,
}
});
const csv = papa.unparse(words, { quotes: true });
download(csv, fileName, 'text/csv;charset=utf-8');
const { name, specification } = window.currentDictionary;
const fileName = slugify(name + '_' + specification) + '_words.csv';
const words = window.currentDictionary.words.map(word => {
return {
word: word.name,
pronunciation: word.pronunciation,
'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(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#92;/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;
window.currentDictionary.settings.sortByDefinition = window.currentDictionary.settings.sortByEquivalent;
delete window.currentDictionary.settings.sortByEquivalent;
migrated = true;
} else if (window.currentDictionary.version !== MIGRATE_VERSION) {
switch (window.currentDictionary.version) {
default: console.error('Unknown version'); break;
}
}
if (migrated) {
saveDictionary();
}
}

View File

@ -1,4 +1,4 @@
import { renderDescription, renderDetails, renderStats } from './render';
import { renderDescription, renderDetails, renderStats } from './render/details';
export function showSection(sectionName) {
switch (sectionName) {

View File

@ -1,10 +1,11 @@
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";
import { addMessage, hideAllModals } from "./utilities";
import helpFile from '../markdown/help.md';
export function enableHotKeys() {
document.addEventListener('keydown', hotKeyActions);
@ -55,13 +56,14 @@ 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;
}
}
function toggleDetailsDisplay() {
const activeTab = document.querySelector('#detailsSection nav li.active');
console.log(activeTab);
Array.from(document.querySelectorAll('#detailsSection nav li')).forEach(li => li.classList.remove('active'));
if (activeTab) {
switch(activeTab.innerText.trim().toLowerCase()) {
@ -102,17 +104,13 @@ function submitWord() {
}
function showHelpModal() {
import('../markdown/help.md').then(html => {
renderInfoModal(html);
});
renderInfoModal(helpFile);
}
function maximizeTextarea() {
const focused = document.activeElement;
if (focused) {
const maximizeButton = focused.parentElement.querySelector('.maximize-button');
console.log(maximizeButton);
console.log(maximizeButton.parentElement);
if (maximizeButton) {
renderMaximizedTextbox(maximizeButton);
}

128
src/js/migration.js Normal file
View File

@ -0,0 +1,128 @@
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 === '/') {
if (isNotSecure()) {
sendDictionaryToHTTPS();
} else {
checkForReceived();
}
}
}
function isNotSecure() {
return window.location.host !== 'localhost' && window.location.protocol !== 'https:';
}
function sendDictionaryToHTTPS() {
const storedDictionary = window.localStorage.getItem(LOCAL_STORAGE_KEY);
const httpsURL = 'https://' + window.location.host;
if (storedDictionary) {
if (!dictionaryIsOldDefault(storedDictionary)) {
const form = document.createElement('form');
form.action = httpsURL;
form.method = 'POST';
form.hidden = true;
const field = document.createElement('input');
field.name = 'oldDictionaryFromHTTP';
field.value = storedDictionary;
form.appendChild(field);
const blackoutShield = document.createElement('div');
blackoutShield.classList.add('modal-background');
document.body.appendChild(form);
document.body.appendChild(blackoutShield);
alert('You are about to be redirected to the secure https version of Lexiconga. Please update your bookmarks.')
form.submit();
return;
}
}
window.location = httpsURL;
}
function dictionaryIsOldDefault(dictionaryJSON) {
const defaultDictionary = {
name: "New",
description: "A new dictionary.",
// createdBy: publicName,
words: [],
nextWordId: 1,
settings: {
allowDuplicates: false,
caseSensitive: false,
partsOfSpeech: "Noun,Adjective,Verb,Adverb,Preposition,Pronoun,Conjunction",
sortByEquivalent: false,
isComplete: false,
isPublic: false
},
externalID: 0
}
const dictionary = JSON.parse(dictionaryJSON);
delete dictionary.createdBy;
return JSON.stringify(defaultDictionary) === JSON.stringify(dictionary);
}
function checkForReceived() {
if (window.hasOwnProperty('dictionaryImportedFromHTTP')) {
let saveOld = true;
const storedDictionary = window.localStorage.getItem(LOCAL_STORAGE_KEY);
if (storedDictionary) {
saveOld = confirm('You have an old local dictionary to import. Would you like to overwrite your current local dictionary?');
}
if (saveOld) {
window.localStorage.setItem(LOCAL_STORAGE_KEY, window.dictionaryImportedFromHTTP);
delete window.dictionaryImportedFromHTTP;
}
}
}
export function migrateDictionary() {
let migrated = false;
if (!window.currentDictionary.hasOwnProperty('version')) {
const fixStupidOldNonsense = string => string.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#92;/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();
}
}

View File

@ -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;
@ -20,7 +20,6 @@ export function goToPage(page) {
window.currentPage = parseFloat(page);
Array.from(document.getElementsByClassName('pagination')).forEach(pagination => {
console.log('setting loader');
pagination.innerHTML = `<span class="loader">Loading Page ${window.currentPage + 1}...</span>`;
});

View File

@ -1,345 +0,0 @@
import md from 'marked';
import { removeTags, slugify } from '../helpers';
import { getWordsStats, wordExists } 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 } from './wordManagement';
export function renderAll() {
renderDictionaryDetails();
renderPartsOfSpeech();
renderWords();
}
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);
document.getElementById('dictionaryName').innerHTML = dictionaryName;
}
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&nbsp;/&nbsp;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;
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 => {
let detailsMarkdown = removeTags(originalWord.details);
const references = detailsMarkdown.match(/\{\{.+?\}\}/g);
if (references && Array.isArray(references)) {
new Set(references).forEach(reference => {
const wordToFind = reference.replace(/\{\{|\}\}/g, '');
const existingWordId = wordExists(wordToFind, true);
if (existingWordId !== false) {
const wordMarkdownLink = `[${wordToFind}](#${existingWordId})`;
detailsMarkdown = detailsMarkdown.replace(new RegExp(reference, 'g'), wordMarkdownLink);
}
});
}
const word = highlightSearchTerm({
name: removeTags(originalWord.name),
pronunciation: removeTags(originalWord.pronunciation),
partOfSpeech: removeTags(originalWord.partOfSpeech),
definition: removeTags(originalWord.definition),
details: detailsMarkdown,
wordId: originalWord.wordId,
});
wordsHTML += `<article class="entry" id="${word.wordId}">
<header>
<h4 class="word">${word.name}</h4>
<span class="pronunciation">${word.pronunciation}</span>
<span class="part-of-speech">${word.partOfSpeech}</span>
<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">&laquo; 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 &raquo;</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 = `<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
<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>
</label>`;
const plainPronunciationField = `<label>Pronunciation<br>
<input id="wordPronunciation_${wordId}" maxlength="200" value="${word.pronunciation}">
</label>`;
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>
${window.settings.useIPAPronunciationField ? ipaPronunciationField : plainPronunciationField}
<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 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');
import('./KeyboardFire/phondue/ipa-table.html').then(html => {
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">&times;&#xFE0E;</a>
<header><label>${label} <input value="${textBox.value}" class="ipa-field"></label></header>
<section>
${html}
</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;
console.log(maximizeButton.parentElement);
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">&times;&#xFE0E;</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">&times;&#xFE0E;</a>
<section class="info-modal">
<div class="content">
${content}
</div>
</section>
</div>`;
document.body.appendChild(modalElement);
setupInfoModal(modalElement);
}

141
src/js/render/details.js Normal file
View File

@ -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 = '&#10150;';
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&nbsp;/&nbsp;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, '&quot;')}">${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();
}

31
src/js/render/index.js Normal file
View File

@ -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;
}
}

74
src/js/render/modals.js Normal file
View File

@ -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">&times;&#xFE0E;</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">&times;&#xFE0E;</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">&times;&#xFE0E;</a>
<section class="info-modal">
<div class="content">
${content}
</div>
</section>
</div>`;
document.body.appendChild(modalElement);
setupInfoModal(modalElement);
}

46
src/js/render/settings.js Normal file
View File

@ -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 = '';
}

213
src/js/render/words.js Normal file
View File

@ -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">&#10150;</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">&laquo; 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 &raquo;</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, '&quot;');
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();
}
}

View File

@ -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,
@ -30,7 +32,6 @@ export function getSearchFilters() {
const partsOfSpeech = document.querySelectorAll('#searchPartsOfSpeech input[type="checkbox"]');
let checkedBoxes = 0;
Array.from(partsOfSpeech).forEach(partOfSpeech => {
// console.log('partOfSpeech Inner Text:', partOfSpeech.parentElement.innerText);
const partOfSpeechLabel = partOfSpeech.parentElement.innerText.trim();
filters.partsOfSpeech[partOfSpeechLabel] = partOfSpeech.checked;
if (partOfSpeech.checked) checkedBoxes++;
@ -41,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) {
@ -81,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);
@ -92,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);
@ -115,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>`);

View File

@ -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();
toggleIPAPronunciationFields(false);
toggleShowAdvancedFields();
}
export function saveSettings() {
@ -17,17 +19,99 @@ export function saveSettings() {
}
export function openSettingsModal() {
const { useIPAPronunciationField, useHotkeys } = 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;
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 => {
@ -38,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() {
@ -65,8 +161,8 @@ export function toggleHotkeysEnabled() {
}
}
export function toggleIPAPronunciationFields() {
const ipaButtons = document.querySelectorAll('.ipa-table-button, .ipa-field-help-button'),
export function toggleIPAPronunciationFields(render = true) {
const ipaButtons = document.querySelectorAll('.ipa-field-help-button'),
ipaFields = document.querySelectorAll('.ipa-field');
if (!window.settings.useIPAPronunciationField) {
Array.from(ipaButtons).forEach(button => {
@ -83,5 +179,34 @@ export function toggleIPAPronunciationFields() {
field.addEventListener('keypress', usePhondueDigraphs);
});
}
renderWords();
if (render) {
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';
}
});
}

View File

@ -1,388 +0,0 @@
import {showSection, hideDetailsPanel} from './displayToggles';
import { renderWords, renderEditForm, renderMaximizedTextbox, renderInfoModal, renderIPATable } 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';
export default function setupListeners() {
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 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', () => {
console.log('changed');
const caseSensitiveBox = document.getElementById('editCaseSensitive');
if (preventDuplicatesBox.checked) {
console.log('checked');
caseSensitiveBox.disabled = false;
} else {
console.log('unchecked');
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 = '&times;&#xFE0E;';
} 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);
});
const renderIPAHelp = () => {
import('./KeyboardFire/phondue/usage.html').then(html => {
renderInfoModal(html);
});
}
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', () => {
import('../markdown/help.md').then(html => {
renderInfoModal(html);
});
});
document.getElementById('termsInfoButton').addEventListener('click', () => {
import('../markdown/terms.md').then(html => {
renderInfoModal(html);
});
});
document.getElementById('privacyInfoButton').addEventListener('click', () => {
import('../markdown/privacy.md').then(html => {
renderInfoModal(html);
});
});
}
export function setupInfoModal(modal) {
const closeElements = modal.querySelectorAll('.modal-background, .close-button');
Array.from(closeElements).forEach(close => {
close.addEventListener('click', () => {
modal.parentElement.removeChild(modal);
});
});
}

View File

@ -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);
});
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);
});
});
}

View File

@ -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);
}

View File

@ -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);
}
});
}

View File

@ -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 = '&times;&#xFE0E;';
} 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);
// });
// }

View File

@ -1,4 +1,3 @@
import { addWord } from './wordManagement';
import { getCookie } from './StackOverflow/cookie';
export function getNextId() {
@ -107,29 +106,33 @@ export function wordExists(word, returnId = false) {
return foundWord ? (returnId ? foundWord.wordId : true) : false;
}
export function generateRandomWords(numberOfWords) {
console.log('Generating', numberOfWords, 'words...');
const letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
letters.forEach(letter => letters.push(letter.toUpperCase()));
const words = [];
while (words.length < numberOfWords) {
let word = '';
while (word === '' || words.includes(word)) {
word += letters[Math.floor(Math.random() * letters.length)];
}
words.push(word);
}
words.forEach((word, index) => {
addWord({
name: word,
pronunciation: '/' + word + '/',
partOfSpeech: Math.random() > 0.5 ? 'Noun' : 'Verb',
definition: word,
details: word + (index > 0 ? '\n\nRef: {{' + words[index - 1] + '}}' : ''),
wordId: getNextId(),
}, false);
export function getHomonymnIndexes(word) {
const { currentDictionary } = window;
const { caseSensitive } = currentDictionary.settings;
const foundIndexes = [];
currentDictionary.words.forEach((existingWord, index) => {
if (existingWord.wordId !== word.wordId
&& (caseSensitive ? existingWord.name === word.name : existingWord.name.toLowerCase() === word.name.toLowerCase())) {
foundIndexes.push(index);
}
});
console.log('done');
return foundIndexes;
}
export function getHomonymnNumber(word) {
const homonyms = getHomonymnIndexes(word);
if (homonyms.length > 0) {
const index = window.currentDictionary.words.findIndex(w => w.wordId === word.wordId);
let number = 1;
for (let i = 0; i < homonyms.length; i++) {
if (index < homonyms[i]) break;
number++;
}
return number;
}
return 0;
}
export function addMessage(messageText, time = 5000, extraClass = false) {
@ -145,7 +148,7 @@ export function addMessage(messageText, time = 5000, extraClass = false) {
const closeButton = element.querySelector('.close-button');
const closeMessage = () => {
closeButton.removeEventListener('click', closeMessage);
messagingSection.removeChild(element);
fadeOutElement(element);
};
closeButton.addEventListener('click', closeMessage);
@ -154,6 +157,13 @@ export function addMessage(messageText, time = 5000, extraClass = false) {
}
}
export function fadeOutElement(element) {
element.classList.add('fadeout');
setTimeout(() => {
element.parentElement.removeChild(element);
}, 300);
}
export function hideAllModals() {
const permanentModals = ['#searchModal', '#settingsModal', '#editModal'];
const hideModals = document.querySelectorAll(permanentModals.join(',')),
@ -163,5 +173,21 @@ export function hideAllModals() {
}
export function hasToken() {
return getCookie('token') !== '';
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;
}

View File

@ -1,12 +0,0 @@
export function getDictionary() {
const url = window.location.href.replace(/\#.*$/gi, '');
console.log(url);
let dict = url.substr(url.lastIndexOf('?'));
console.log(dict);
if (dict === url) {
dict = dict.substr(dict.lastIndexOf('/'));
console.log(dict);
}
dict = dict.replace(/[\?\/]/g, '');
console.log(dict);
}

View File

@ -1,21 +1,12 @@
import '../../main.scss';
import { renderAll } from './render';
import setupListeners from './setupListeners';
// import setupListeners, { setupSearchFilters } from './js/setupListeners';
// import { renderAll } from './js/render';
// import { hasToken } from './js/utilities';
// import { loadDictionary } from './js/dictionaryManagement';
// import { loadSettings } from './js/settings';
function initialize() {
renderAll();
setupListeners();
}
window.onload = (function (oldLoad) {
return function () {
oldLoad && oldLoad();
initialize();
}
oldLoad && oldLoad();
initialize();
})(window.onload);

View File

@ -1,79 +1,120 @@
import md from 'marked';
import { removeTags, slugify } from '../../helpers';
import { getWordsStats, wordExists } from '../utilities';
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from '../search';
import { showSection } from '../displayToggles';
import { setupSearchFilters, setupInfoModal } from './setupListeners';
import { getHomonymnNumber } from './utilities';
import { getMatchingSearchWords, highlightSearchTerm, getSearchFilters, getSearchTerm } from './search';
import { showSection } from './displayToggles';
import { setupInfoModal } from '../setupListeners/modals';
import { setupSearchFilters } from '../setupListeners/search';
export function renderAll() {
renderTheme();
renderCustomCSS();
renderDictionaryDetails();
renderPartsOfSpeech();
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;
}
}
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() {
const dictionaryName = removeTags(window.currentDictionary.name) + ' ' + removeTags(window.currentDictionary.specification);
document.getElementById('dictionaryName').innerHTML = dictionaryName;
const shareLink = 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;
document.getElementById('dictionaryShare').href = shareLink;
}
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&nbsp;/&nbsp;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&nbsp;/&nbsp;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>
@ -89,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, '&quot;')}">${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>`;
@ -134,37 +175,46 @@ export function renderWords() {
}
words.forEach(originalWord => {
let detailsMarkdown = removeTags(originalWord.details);
const references = detailsMarkdown.match(/\{\{.+?\}\}/g);
if (references && Array.isArray(references)) {
new Set(references).forEach(reference => {
const wordToFind = reference.replace(/\{\{|\}\}/g, '');
const existingWordId = wordExists(wordToFind, true);
if (existingWordId !== false) {
const wordMarkdownLink = `[${wordToFind}](#${existingWordId})`;
detailsMarkdown = detailsMarkdown.replace(new RegExp(reference, 'g'), wordMarkdownLink);
}
});
}
const word = highlightSearchTerm({
name: removeTags(originalWord.name),
pronunciation: removeTags(originalWord.pronunciation),
partOfSpeech: removeTags(originalWord.partOfSpeech),
definition: removeTags(originalWord.definition),
details: detailsMarkdown,
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 += `<article class="entry" id="${word.wordId}">
<header>
<h4 class="word">${word.name}</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">&#10150;</a>
</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${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>`;
});

168
src/js/view/search.js Normal file
View File

@ -0,0 +1,168 @@
import { cloneObject, getIndicesOf } from "../../helpers";
import removeDiacritics from "../StackOverflow/removeDiacritics";
import { renderWords } from "./render";
export function showSearchModal() {
document.getElementById('searchModal').style.display = 'block';
document.getElementById('searchBox').focus();
}
export function clearSearchText() {
document.getElementById('searchBox').value = '';
document.getElementById('openSearchModal').value = '';
renderWords();
}
export function getSearchTerm() {
return document.getElementById('searchBox').value;
}
export function getSearchFilters() {
const filters = {
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,
partsOfSpeech: {},
};
const partsOfSpeech = document.querySelectorAll('#searchPartsOfSpeech input[type="checkbox"]');
let checkedBoxes = 0;
Array.from(partsOfSpeech).forEach(partOfSpeech => {
const partOfSpeechLabel = partOfSpeech.parentElement.innerText.trim();
filters.partsOfSpeech[partOfSpeechLabel] = partOfSpeech.checked;
if (partOfSpeech.checked) checkedBoxes++;
});
filters.allPartsOfSpeechChecked = checkedBoxes === partsOfSpeech.length;
return filters;
}
export function getMatchingSearchWords() {
let 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();
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)
);
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 searchTerm === '' || isInName || isInDefinition || isInDetails || isInPrincipalParts;
});
return matchingWords;
}
return window.currentDictionary.words
}
export function highlightSearchTerm(word) {
let searchTerm = getSearchTerm();
if (searchTerm) {
const filters = getSearchFilters();
const markedUpWord = cloneObject(word);
if (filters.ignoreDiacritics) {
const searchTermLength = searchTerm.length;
searchTerm = removeDiacritics(searchTerm);
if (filters.name) {
const nameMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.name), filters.caseSensitive);
nameMatches.forEach((wordIndex, i) => {
wordIndex += '<mark></mark>'.length * i;
markedUpWord.name = markedUpWord.name.substring(0, wordIndex)
+ '<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);
definitionMatches.forEach((wordIndex, i) => {
wordIndex += '<mark></mark>'.length * i;
markedUpWord.definition = markedUpWord.definition.substring(0, wordIndex)
+ '<mark>' + markedUpWord.definition.substr(wordIndex, searchTermLength) + '</mark>'
+ markedUpWord.definition.substr(wordIndex + searchTermLength);
});
}
if (filters.details) {
const detailsMatches = getIndicesOf(searchTerm, removeDiacritics(markedUpWord.details), filters.caseSensitive);
detailsMatches.forEach((wordIndex, i) => {
wordIndex += '<mark></mark>'.length * i;
markedUpWord.details = markedUpWord.details.substring(0, wordIndex)
+ '<mark>' + markedUpWord.details.substr(wordIndex, searchTermLength) + '</mark>'
+ markedUpWord.details.substr(wordIndex + searchTermLength);
});
}
} else {
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>`);
}
if (filters.details) {
markedUpWord.details = markedUpWord.details.replace(new RegExp(`(${searchTerm})`, regexMethod), `<mark>$1</mark>`);
}
}
return markedUpWord;
}
return word;
}
export function checkAllPartsOfSpeechFilters() {
const searchFilters = document.querySelectorAll('#searchPartsOfSpeech input[type="checkbox"]');
Array.from(searchFilters).forEach(filter => {
filter.checked = true;
});
renderWords();
}
export function uncheckAllPartsOfSpeechFilters() {
const searchFilters = document.querySelectorAll('#searchPartsOfSpeech input[type="checkbox"]');
Array.from(searchFilters).forEach(filter => {
filter.checked = false;
});
renderWords();
}

View File

@ -1,6 +1,9 @@
import {showSection, hideDetailsPanel} from './displayToggles';
import { showSearchModal, clearSearchText, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from '../search';
import { showSearchModal, clearSearchText, checkAllPartsOfSpeechFilters, uncheckAllPartsOfSpeechFilters } from './search';
import { renderWords, renderInfoModal } from './render';
import helpFile from '../../markdown/help.md';
import termsFile from '../../markdown/terms.md';
import privacyFile from '../../markdown/privacy.md';
export default function setupListeners() {
setupDetailsTabs();
@ -79,19 +82,13 @@ export function setupSearchFilters() {
export function setupInfoButtons() {
document.getElementById('helpInfoButton').addEventListener('click', () => {
import('../../markdown/help.md').then(html => {
renderInfoModal(html);
});
renderInfoModal(helpFile);
});
document.getElementById('termsInfoButton').addEventListener('click', () => {
import('../../markdown/terms.md').then(html => {
renderInfoModal(html);
});
renderInfoModal(termsFile);
});
document.getElementById('privacyInfoButton').addEventListener('click', () => {
import('../../markdown/privacy.md').then(html => {
renderInfoModal(html);
});
renderInfoModal(privacyFile);
});
}

28
src/js/view/utilities.js Normal file
View File

@ -0,0 +1,28 @@
export function getHomonymnIndexes(word) {
const { currentDictionary } = window;
const { caseSensitive } = currentDictionary.settings;
const foundIndexes = [];
currentDictionary.words.forEach((existingWord, index) => {
if (existingWord.wordId !== word.wordId
&& (caseSensitive ? existingWord.name === word.name : existingWord.name.toLowerCase() === word.name.toLowerCase())) {
foundIndexes.push(index);
}
});
return foundIndexes;
}
export function getHomonymnNumber(word) {
const homonyms = getHomonymnIndexes(word);
if (homonyms.length > 0) {
const index = window.currentDictionary.words.findIndex(w => w.wordId === word.wordId);
let number = 1;
for (let i = 0; i < homonyms.length; i++) {
if (index < homonyms[i]) break;
number++;
}
return number;
}
return 0;
}

View File

@ -0,0 +1,11 @@
import removeDiacritics from "../StackOverflow/removeDiacritics";
export function sortWords() {
const { sortByDefinition } = window.currentDictionary.settings;
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;
});
}

View File

@ -1,8 +1,10 @@
import { renderWords } from "./render";
import { wordExists, addMessage, getNextId, hasToken } from "./utilities";
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,12 +104,89 @@ 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 => {
const wordMarkdownLink = getWordReferenceMarkdown(reference);
if (wordMarkdownLink !== reference) {
detailsMarkdown = detailsMarkdown.replace(new RegExp(reference, 'g'), wordMarkdownLink);
}
});
}
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(),
@ -62,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();
}
}
@ -75,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;
@ -86,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;
}
@ -101,6 +254,7 @@ export function deleteWord(wordId) {
const wordIndex = window.currentDictionary.words.findIndex(word => word.wordId === wordId);
if (wordIndex < 0) {
console.error('Could not find word to delete');
addMessage('Could not find word to delete. Please refresh your browser and try again.', 10000, 'error');
} else {
window.currentDictionary.words.splice(wordIndex, 1);
addMessage('Word Deleted Successfully');
@ -119,12 +273,32 @@ export function updateWord(word, wordId) {
if (wordIndex < 0) {
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 => {
@ -136,12 +310,14 @@ export function updateWord(word, wordId) {
export function confirmEditWord(id) {
const wordId = typeof id.target !== 'undefined' ? parseInt(this.id.replace('editWordButton_', '')) : id;
console.log(wordId);
const name = document.getElementById('wordName_' + wordId).value,
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(),
@ -152,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');
@ -174,6 +362,7 @@ export function confirmDeleteWord(wordId) {
if (!word) {
console.error('Something went wrong! Couldn\'t find word with id of ' + wordId);
addMessage('Could not find word to delete. Please refresh your browser and try again.', 10000, 'error');
} else {
if (confirm(`Are you sure you want to delete "${word.name}"?`)) {
if (confirm(`Just to double-check:\nDo you really want to delete "${word.name}"?\n\nYou won't be able to undo it!`)) {

View File

@ -9,10 +9,13 @@
@import 'scss/mobile/structure';
@import 'scss/mobile/elements';
html, body {
font-family: $font;
* {
box-sizing: border-box;
}
}
@import 'scss/themes/default';
@import 'scss/themes/dark';
@import 'scss/themes/light';
@import 'scss/themes/blue';
@import 'scss/themes/green';
@import 'scss/themes/royal';
@import 'scss/themes/yellow';
@import 'scss/themes/red';
@import 'scss/themes/mint';
@import 'scss/themes/grape';

View File

@ -1,50 +1,52 @@
# Lexiconga Help
## Table of Contents
* [What is Lexiconga?](#what-is-lexiconga-dictionary-builder-)
* [How do I use Lexiconga?](#how-do-i-use-lexiconga-)
* [What is Lexiconga?](#what-is-lexiconga)
* [How do I use Lexiconga?](#how-do-i-use-lexiconga)
* [Getting Started](#getting-started)
* [Viewing your Dictionary's Details](#viewing-your-dictionary-s-details)
* [Viewing your Dictionary's Details](#viewing-your-dictionarys-details)
* [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](#search-filter)
* [The Dictionary Settings Menu](#the-dictionary-settings-menu)
* [Search/Filter](#searchfilter)
* [The Settings Window](#the-settings-window)
* [The Dictionary Settings Window](#the-dictionary-settings-window)
* [Keyboard Shortcuts](#keyboard-shortcuts)
* [Importing and Exporting](#importing-and-exporting)
* [Accounts](#accounts)
* [Creating An Account](#creating-an-account)
* [Logging In](#logging-in)
* [Differences](#differences)
* [Account Settings](#account-settings)
* [Dictionary Settings](#dictionary-settings)
* [Settings](#settings-1)
* [Public Dictionaries](#public-dictionaries)
* [Forgot Your Password?](#forgot-your-password-)
* [Forgot Your Password?](#forgot-your-password)
* [Lockout](#lockout)
* [Problems or Requests](#problems-or-requests)
* [Update Log](#update-log)
* [Future Plans](#future-plans)
* [Thanks](#thanks-)
* [Libraries Used](#libraries-used)
* [Open Source](#open-source)
* [Thanks](#thanks)
## What is Lexiconga?
Lexiconga is a tool intended to help you build constructed language (conlang) dictionaries/lexicons.
You can enter words and definitions, and they will appear nicely formatted and in alphabetical order by name under your dictionary's title, where you can also sort them by definition. If the default parts of speech are not adequate for your conlang, you can change them to whatever you might need. You can even enter a description and full set of language rules that you can toggle on and off below the dictionary's title!
Lexiconga is a tool built to help you build constructed language (conlang) dictionaries/lexicons quickly and easily.
It 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 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.
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!
If you would like an added layer of accessibility and security (in case you clear your browser cache frequently), you can create an account, where you can store and switch between as many dictionaries as you need. Having an account will also allow you to access your dictionaries from any browser by logging in. (Just be careful you don't overwrite dictionaries by logging in and saving from separate locations!)
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.
If you would like an added layer of accessibility and security (in case you clear your browser cache frequently), you can create an account, where you can store and switch between as many dictionaries as you need. Having an account will also allow you to access your dictionaries from any browser by logging in.
## How do I use Lexiconga?
### Getting Started
When you have a brand new, empty dictionary, the first thing you'll probably want to do is change the title to whatever your conlang is called and add at least a little description of what your language is like or how to use it. You can do this by clicking on the **Edit** button, which will open up the Dictionary Settings screen. Here, you will find all the fields you need to update your dictionary's Name, Specification, and Description. The Description text area uses [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) to format any text you include, so brush up on how to do basic things in Markdown before you get started _(NOTE: a line break is done by adding 2 or more spaces to the end of the line and then going to the next line!)_.
When you have a brand new, empty dictionary, the first thing you'll probably want to do is change the title to whatever your conlang is called and add at least a little description of what your language is like or how to use it. You can do this by clicking on the **Edit** button, which will open up the Dictionary Settings screen. Here, you will find all the fields you need to update your dictionary's Name, Specification, and Description. The Description text area uses [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) to format any text you include, so brush up on how to do basic things in Markdown before you get started _(NOTE: a line break is created by adding 2 or more spaces to the end of the line and then going to the next line!)_.
After this, go to the **Details** tab and make sure that the Parts of Speech are adequate for your language _(see below for more information about this)_, and add phonology information if you'd like _(learning to use the [International Phonetic Alphabet](https://en.wikipedia.org/wiki/International_Phonetic_Alphabet) can be very helpful here)_. Update these fields how you want them and click the "Save" button to keep the Dictionary Settings menu open, or the "Save & Close" button to close the menu and start adding words!
To add words, use the form on the top left side of the window _(in mobile, click the **+** button to show the form)_. Hopefully the form is self-explanatory, but if not, here's a little guide:
Enter the word in your language in the "Word" field, the pronunciation of the word in the "Pronunciation" field, choose a Part of Speech, enter an definition/equivalent word in the "Definition" field and/or a longer definition or fuller explanation of the word using [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) in the "Details" text area, and click "Add Word". Your word will instantly appear in your dictionary under the dictionary's name! You can add as many words as you want this way.
Enter the word in your constructed language in the "Word" field, the pronunciation of the word in the "Pronunciation" field, choose a Part of Speech, enter an definition/equivalent word in the "Definition" field and/or a longer definition or fuller explanation of the word using [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) in the "Details" text area, and click "Add Word". Your word will instantly appear in your dictionary under the dictionary's name! You can add as many words as you want this way.
The only things to remember while adding new words is that the minimum information that you can enter is the Word itself and either the Definition OR the Details. You can have both of these or just one, but you need at least one. If you do not want to use the Pronunciation or Parts of Speech then you do not need to, though if you leave out Part of Speech, you will miss out on the handy Search Filter feature.
And that's all you need to get started! Everything else should be pretty self-explanatory, but a full explanation of Lexiconga and all of its functions continues below.
@ -54,11 +56,36 @@ After you enter a markdown-formatted description/rules in the Dictionary Setting
### Referencing Other Words
If you want to reference another existing word in your dictionary, wrapping the word with its exact name in double-curly-braces \{\{like so\}\} in the Details field will automatically create a link to the word in the dictionary.
Note: If you do not prevent duplicates, it will find the first entry in the list.
If you have more than one word with the same spelling, the duplicate words will appear in your word listing with small numbers beside them. By writing the number after a colon \{\{like so:2\}\}, you can directly reference the specific homonymn. If you have duplicate words and exclude the reference number, it will link to the homonymn marked with 1.
### Maximizing Large Text Boxes
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 &times; 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.
@ -66,133 +93,170 @@ The **Edit** button will cause a form with the current details of the word you e
The **Delete** button will ask you to confirm that you want to delete the entry, and if you say yes, the word will be _permanently deleted and **cannot be retrieved**_.
Note that to use these fields, your browser will need to allow "confirm" and "prompt" popups. Preventing these fuctions will prevent words from being edited or deleted.
### Search/Filter
You can search entries or filter by part of speech by clicking the "Search/Filter Options" button to expand the search panel.
You can search entries or filter by part of speech by clicking the "Search" button at the top of the screen. This will open a small panel that will stick to the top of your screen as you scroll.
From there, you can enter any text you want in the search box and either press Enter or click anywhere outside the search box, and Lexiconga will display any and every entry including your entry. To display the entire dictionary again, you must clear the search box.
You can refine your search by using the checkboxes below the search box:
You can refine your search by clicking the "Toggle Options" button and using the checkboxes below the search box. There are 3 sections that can help you filter your 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.
* **Explanation**: When checked, Lexiconga searches your dictionary's "Explanation/Long Definition" entries for the entered text. When unchecked, it ignores it.
* **Search 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.
- **Search For**
- **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.
- **Explanation**: When checked, Lexiconga searches your dictionary's "Explanation/Long Definition" entries for the entered text. When unchecked, it ignores it.
- **Include Only:** Lists all parts of speech and "Unclassified" with a checkbox next to each. If you uncheck any of the parts of speech, any entries in your dictionary with that part of speech will not be shown. Use the "Check All" and "Uncheck All" buttons to quickly select/unselect all of them.
- **Unclassified:** Checking this box will show entries that do not have a part of speech.
The "Filter Words" drop-down box allows you to filter your dictionary by part of speech. To display the whole dictionary again after setting a filter, reset the filter option to "All".
When you have a search term or filter applied, you can see the number of results and "(Filtered)" next to the search button. To close the search panel, click the &times; button or the darker space on either side of it.
### The Dictionary Settings Menu
While you were in the settings menu when you were getting started, you probably noticed the other tabs.
To display _all_ of your words again, clear your search bar and ensure all the "Include Only" checkboxes are checked.
The **Parts of Speech** field is where you can add custom parts of speech for your language if you need to! Just list your parts of speech in a comma-separated list the same way as the default parts of speech are listed, and your options in the word form and filters will update as soon as you save!
>_Please note that if you have other parts of speech added to existing words, those words will not update and will keep the old parts of speech. You will need to manually update any words with incorrect parts of speech after the fact, which is why I recommend you update the available parts of speech as one of the first things you do if you need to change them at all!_
### The Settings Window
Clicking the "Settings" button in the top-right side of Lexiconga will show the Settings window with some options.
The **Allow Duplicates** checkbox allows you to control whether or not Lexiconga will allow you to add the same word multiple times. If you leave Allow Duplicates unchecked and you try to add a word that is already in the dictionary, Lexiconga will tell you that the word already exists and will ask if you want to update it with the newly entered word.
- **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.
The **Case-Sensitive** checkbox allows you to control Lexiconga's duplicate detection. If you leave Case-Sensitive unchecked, you will be alerted when you are trying to add a word with the same letters to your dictionary a second time. For example, "dog" is identified as the same word as "DOG" or "doG". The dictionary will keep whatever capitalization you save but it will identify words with the same spelling as duplicates. If Case-Sensitive is checked, then it will not identify "dog" and "DOG" as the same word.
If Allow Duplicates is checked, this checkbox becomes unavailable.
After making changes, click the "Save" or "Save & Close" button to save your changes.
The **Dictionary is Complete** checkbox will make the word add/edit form go away so you can view or share it more easily/safely. Plus when you export your dictionary, all of the options to change anything about your dictionary will be excluded when it is re-imported! Your dictionary will become static, and will not be able to be changed or updated without a password.
#### 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.
The **Export...** and **Import...** buttons are discussed in the [Importing and Exporting](#importing-and-exporting) section below.
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).
The **Empty Current Dictionary** should only be used if you want to completely start over from scratch. It will ask you to confirm that you want to delete, and if you confirm, your dictionary will be gone forever. If you have not exported your dictionary before emptying it, there will be absolutely no way to get it back. Please be careful with this!
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.
#### Description
- **Name:** Your dictionary's name. Displays above the entries.
- **Specification:** How your dictionary is referred to. For example, you can change this to something like "Word List" or "Lexicon" if you prefer. Displays above the entries after Name.
- **Description:** Any information you want to enter about your dictionary. Uses [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) formatting.
#### 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:** 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_.
- **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.
#### Settings
- **Prevent Duplicate Words:** Checking this box will prevent the creation of words with the exact same spelling.
- **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
- **Import JSON:** Choose a previously exported JSON file or older `.dict` file to import. If you are logged in, this will upload the dictionary to your account and display it. If you are not logged in, it will _replace_ your current dictionary with the imported one.
- Again, please note that if you are not logged in, this import process will _**permanently overwrite your current dictionary**_, so please be sure to export your dictionary _before_ you import a new one!
- **Import Words:** Choose a properly-formatted `.csv` file of words to add to your dictionary. This will add all of the words from the file to your current dictionary _even if the list includes duplicate words_. Use the "Download an example file with the correct formatting" link below this field to save a file that you can open with any program that processes CSVs like Microsoft Excel or some other spreadsheet program.
- Please note that when importing words, you must make sure that the parts of speech specified in the parts of speech column are written _exactly as they are in your dictionary settings_ (capitalized, spelled correctly, or any other details). If they are not the same, then you will not be able to use the filters to find the words! So if you import a word with the part of speech set to "adj" or "adjective", but the part of speech in your dictionary's settings is "Adjective", then you will not be able to find the word using the filters!
- If you import a word _without_ a part of speech, you _can_ use the filter's "Blanks" option to find any words with empty parts of speech to help you clean up after the import.
- **Export JSON:** Start a download of a file with your dictionary's name in a `.json` format. _Please note that this may not work as expected on mobile platforms._ This export can be a personal backup for your own uses, to work on multiple dictionaries at a time (i.e. export one dictionary and import the other to work on the one you'd like), or you can share it with friends to view it. The file contains your whole dictionary in a JSON format and is mainly only useful for importing back into Lexiconga.
- **Export Words:** Start a download of all of the words in the currently loaded dictionary into a convenient CSV file format that you can use to re-import into another Lexiconga dictionary or otherwise use as you need it! All of the data is wrapped in double quotes (`"`) to comply with standard CSV format.
- **Delete Dictionary:** Clicking this button will confirm that you want to delete your dictionary. If you confirm, it will permanently destroy your dictionary and all of its data. If you are logged in, this also removes the dictionary from your account.
After making any changes, be sure to click "Save" or "Save & Close" to ensure that your changes are saved!
### Keyboard Shortcuts
**Esc** : Exits a window (i.e. Dictionary Settings, Account Settings, this about page, etc.) without saving.
**Esc** : Closes any open window (i.e. Dictionary Settings, Settings, this help page, etc.) without saving.
**Ctrl/Control +**
* **Enter/Return** : Submit Word (when typing in Word or Edit Form)
* **D** : Toggle Dictionary Description visibility.
* **H** : Open this help window.
* **M** : Maximize/Minimize Full Screen textbox when typing in the boxes that have the Maximize button.
* **S** : Jump to Search box.
* **U** : Toggle Word Form lock.
**Alt/Option +**
* **A** : Toggle Account Settings window (if logged in).
* **S** : Toggle Dicitonary Settings window. Saves & Closes if it's already open.
### Importing and Exporting
In the Settings screen, you may notice the buttons labeled "**Export...**" and "**Import...**". If you click on either of these, the respective Export and Import page will appear.
#### Exporting
Clicking the **"Export Words"** button will start a download of all of the words in the currently loaded dictionary into a convenient CSV file format that you can use to re-import into another Lexiconga dictionary or otherwise use as you need it! All of the data is wrapped in double quotes (`"`) to comply with standard CSV format.
Clicking the **"Export Dictionary"** button will start a download of a file with your dictionary's name in a ".dict" format. _Please note that this may not work as expected on mobile platforms._ This export can be a personal backup for your own uses, to work on multiple dictionaries at a time (i.e. export one dictionary and import the other to work on the one you'd like), or you can share it with friends to view it. The .dict file contains your whole dictionary in a JSON format, and is mainly only useful for importing back into Lexiconga.
#### Importing
Clicking the **"Import Words"** button after choosing a file allows you to import a correctly-formatted CSV list of words into your currently loaded dictionary. This can either be a previously-exported list of words from another Lexiconga dictionary or a list created in Excel with the correct column headers that has been saved as a CSV file. You can download a CSV file as a template from the Import page as an example to help you format and save your Excel lists properly.
Please note that when importing words, you must make sure that the parts of speech specified in the parts of speech column are written _exactly as they are in your dictionary settings_ (capitalized, spelled correctly, or any other details). If they are not the same, then you will not be able to use the filters to find the words! So if you import a word with the part of speech set to "adj" or "adjective", but the part of speech in your dictionary's settings is "Adjective", then you will not be able to find the word using the filters!
If you import a word _without_ a part of speech, you _can_ use the filter's "Blanks" option to find any words with empty parts of speech to help you clean up after the import.
Clicking the **"Import Dictionary"** button after choosing a file allows you to upload and view any previously-exported ".dict" files. After selecting your ".dict" file, click the "Import Dictionary" button to _overwrite your current dictionary_ and view the imported one. Again, please note that if you are not logged in, this import process will _**permanently overwrite your current dictionary**_, so please be sure to export your dictionary _before_ you import a new one!
- **Enter/Return:** Submit Word (when typing in Word or Edit Form) _OR_ Save & Close the Settings or Dictionary Settings window.
- **D:** Cycle through dictionary description, details, and stats.
- **E:** Open the Dictionary Settings window.
- **H:** Open this help window.
- **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.
- **Backspace/Delete:** Clear the Search box.
## Accounts
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.
**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
The first time you create an account, you will need to enter your email address and a password (for logging in) in addition to a "Public Name". Your Public Name will be more important when we add dictionary sharing later, but for now, it is important in that it helps indicate whether or not you are logged in (see below). If you have a dictionary loaded in your browser, it will be automatically uploaded to your account and saved after it is created.
The first time you create an account, you will need to enter your email address and a password (for logging in) in addition to a "Public Name". Your Public Name will display whenever you log in and will display under your dictionary's name if you make your dictionary public and share it (see below). If you have a dictionary loaded in your browser, it will be automatically uploaded to your account and saved after it is created.
### Logging In
To log in after creating an account, just click the "Log In/Create Account" button and enter your email address and password under the "Log In" form, just like any other account online. You will know that you are logged in from the "Welcome back!" notification at the top of the screen when you load the page. You can also know that you're logged in if you see a "Log Out" button instead of "Log In/Create Account" in the top right corner of the screen.
To log in after creating an account, just click the "Log In/Create Account" button and enter your email address and password under the "Log In" section, just like any other account online. You will know that you are logged in from the "Welcome back!" notification at the bottom of the screen when you load the page. You can also know that you're logged in if you see a "Log Out" button instead of "Log In/Create Account" in the top right corner of the screen.
Logging in creates a cookie in your browser that Lexiconga uses to know that you are logged in and verify your account. That means that if your browser prevents cookies, you cannot use a Lexiconga account's features.
### Differences
Every time you save a change to your dictionary's settings or add, edit, or delete a word, the changes are automatically saved to both your browser's localStorage in addition to being sent to your account. If you're paranoid that your changes are not being saved, you can check your browser's console log to see the little save and update notifications.
Every time you save a change to your dictionary's settings or add, edit, or delete a word, the changes are automatically saved to both your browser's localStorage in addition to being sent to your account. Little confirmation messages will appear in the bottom right side of the screen after every change you make to confirm that things are saved correctly.
#### Account Settings
After logging in, you'll see an "Account Settings" button in the top, right side of the Lexiconga window. Clicking this will allow you to change a few settings about your account:
#### Settings
After logging in, you'll see some additional options in the "Settings" window under new "Account Settings" and "Account Actions" sections.
The **Email** field allows you to specify a different login and contact email address. Make sure that you do not forget what you chose, because there is no way to retrieve your email address if you change it to something you forget!
##### Account Settings
The **Public Name** field allows you to change your public name.
- **Email:** Allows you to specify a different login and contact email address. Make sure that you do not forget what you chose, because there is no way to retrieve your email address if you change it to something you forget!
- **Public Name:** Allows you to change your public name.
- **Allow Emails:** Check this box if you would like to receive emails about important Lexiconga updates. Make sure that you allow emails from addresses at lexicon.ga or check your spam folder just in case. Note that this checkbox does not affect password reset requests—if you forget your password, Lexiconga will send you a password reset email regardless of your choice here.
- **New Password:** Enter a new password in this field _only if you want to change it_! Leave this field blank to prevent your password from changing.
The **Allow Emails** checkbox allows you to choose if you would like to receive emails about important Lexiconga updates. Make sure that you allow emails from addresses at lexicon.ga or check your spam folder just in case. Note that this checkbox does not affect password reset requests—if you forget your password, Lexiconga will send you a password reset email regardless of your choice here.
If you change any of the options above, be sure you click the "Save" or "Save & Close" button.
If you change any of the three options above, be sure you click the "Save Settings" button.
##### Account Actions
The "Reset Password" button in the "Reset Your Password" section will allow you to reset your login password. Don't forget it!
#### Dictionary Settings
Under the Settings menu, you'll see some additional options:
The **Dictionary is Public** checkbox determines whether or not the current dictionary can be viewed by anyone online using the Public Link that appears when checked. Public dictionaries are explained more below.
The **Change Dicitonaries** dropdown box contains the names of all of your created dictionaries. If you have more than one, selecting a different dictionary from the list will immediately download and display that dictionary.
The **Create New Dictionary** button will instantly create and save a new blank dictionary to your account.
The **Import Dictionary** button acts the same as before, but instead of overwriting your dictionary, it imports the dictionary as a new, separate dictionary and saves it to your account. After importing, the imported dictionary will display, and you can use the Change Dictionaries dropdown box to change to a previous one if you desire.
The **Delete Current Dictionary** button will permanently and irretrievably delete the currently loaded dictionary from your account! Be careful with that one. After deleting, you will then be prompted to either select another dictionary to load or create a new one, _or_ if you have no other dictionaries, immediately create a new one for you.
- **Change Dictionary:** Shows all dictionaries created on your account. Choose a different one from the drop-down box to instantly load and display that dictionary.
- **Create New Dictionary:** Clicking this button will create a new dictionary on your account and display the new empty dictionary to you.
#### Public Dictionaries
When a dictionary is marked as public, you can share its public link and allow anyone to view its contents without being able to make changes. The dictionary's description and the search/filter area is visible by default, and the viewer can scroll through or search your dictionary without being able to make changes. Public dictionaries also have the ability to share specific word entries using the "➦" buttons in each word box. When viewing a word, the search/filter options are not available, but anyone can still read the dictionary's description.
When a dictionary is marked as public, you can share its public link and allow anyone to view and search its contents without being able to make changes. Public dictionaries also have the ability to share specific word entries using the "➦" buttons in each word box. When viewing a word, anyone can still read the dictionary's description and details.
To log in or create an account when viewing a dictionary, you need to go back to the main Lexiconga page. You can get there by clicking either the logo or the "Go Home" button. Or, if you are the owner of the dictionary and are currently logged in, the "Go Home" button will be replaced with an "Edit Dictionary" button, and you can click that to change your current dictionary adn start editing it.
To log in or create an account when viewing a dictionary, you need to go back to the main Lexiconga page. You can get there by clicking the Lexiconga logo.
### Forgot Your Password?
If you forget your password, you can request a password reset email by clicking the "Forgot Password" button on the "Log In/Create Account" button entering the email address associated with your account and clicking "Email Password Reset Key". This will send an email (_check your spam_) with a link that will allow you to reset your password. When you go to the link provided, you'll be able to enter a new password that you can log in with.
If you forget your password, you can request a password reset email by clicking the "Forgot Password" button in the "Log In/Create Account" window, entering the email address associated with your account, and clicking "Email Password Reset Key". This will send an email (_check your spam_) with a link that will allow you to reset your password. When you go to the link provided, you'll be able to enter a new password that you can log in with.
### Lockout
If you manage to enter your password wrong 10 times, you'll be locked out from logging in for an hour. Use this time to try to remember your password or something. You can get an idea of how long you've waited by refreshing the page and clicking the unfortunate "Can't Login" button. After an hour has passed, refresh the page again and you'll get another 10 tries.
If you manage to enter your password wrong 10 times, you'll be locked out from logging in for 1 hour. Use this time to try to remember your password or something. You can get an idea of how long you've waited by trying to log in again. After an hour has passed, refresh the page again and you'll get another 10 tries.
## Problems or Requests
Please report any problems you come across to the [Lexiconga Issues page](https://lexicon.ga/issues). You can also submit enhancement requests to the same place if you have any requests for new features.
Please report any problems you come across to the [Lexiconga Issues page](https://github.com/Alamantus/Lexiconga/issues). You can also submit enhancement requests to the same place if you have any requests for new features.
## Update Log
You can see all previous updates to Lexiconga here:
[https://lexicon.ga/updates](https://lexicon.ga/updates)
You can see all previous updates to Lexiconga on the [Lexiconga Releases page](https://github.com/Alamantus/Lexiconga/releases).
## Open Source
The free features of Lexiconga are fully open-source and can be found here: https://cybre.tech/Alamantus/lexiconga-lite
Lexiconga's source code is fully open and readable on Github here: https://github.com/Alamantus/Lexiconga
## Thanks!
If you like Lexiconga and want to buy me a cup of coffee for the service, you can use **[Buy Me A Coffee](https://buymeacoff.ee/robbieantenesse)** to help keep it online if you want.
If you like Lexiconga and want to help contribute to keeping it online and motivate me to keep adding new features, you can use **[Buy Me A Coffee](https://buymeacoff.ee/robbieantenesse)** to give a one-time donation or **[Liberapay](https://liberapay.com/robbieantenesse)** to make a recurring donation.
I hope you enjoy Lexiconga and that it helps you build some awesome languages.

View File

@ -1,6 +1,6 @@
## Privacy Policy
This document was last updated on May 16, 2018 to update contact information and add transparency to our hosting service and how your data is used (i.e. to display your dicitonaries and sometimes send you emails if you agree to receive them).
This document was last updated on June 5, 2019 to update information about our hosting service and how your data is used (i.e. to display your dicitonaries and sometimes send you emails if you agree to receive them) and to reflect changes to Lexiconga made during the app update.
This Privacy Policy governs the manner in which Lexiconga collects, uses, maintains and discloses information collected from users (each, a "User") of the https://lexicon.ga website ("Site") and its accounts ("Account").
@ -11,17 +11,17 @@ 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 does not use "cookies" to enhance User experience, but 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:
- **To run and operate our Site and personalize user experience:** We need your input to display content on the Site correctly, because it is a tool built entirely to display the content that you enter.
- **To run and operate our Site and personalize User experience:** We need your input to display content on the Site correctly, because it is a tool built entirely to display the content that you enter.
We do not use your data in _any way_ other than to serve your content or to send the occasional update email—you may opt out of receiving emails at any time via your Account Settings! Your email address is tied directly to your account, however, so altering it _will change your login credentials_.
### How we protect your information
If you are utilizing an Account, we protect your information by keeping your information on a trusted database server provided by the Site's web host, [InfinityFree](https://infinityfree.net/). All data you save from an Account is uploaded to this database server from your browser. If we ever change web hosts, you will be notified via the on-site notification.
If you are utilizing an Account, we protect your information by keeping your information on a trusted database server provided by the Site's web host, [Namecheap](https://namecheap.com/). All data you save from an Account is uploaded to this database server from your browser. If we ever change web hosts, you will be notified via the on-site notification.
If you utilize the ability to make dictionaries associated with an Account visible to the public, you accept all responsibility for its content being visible to anyone who might find it. We cannot protect what you willingly put in public, and we do not accept liability if information you place online is used against you somehow. Never put personal information online!
@ -34,10 +34,10 @@ We do not sell, trade, or rent Users personal identification information to othe
If User decides to opt-in to our mailing list, they will receive emails that may include company news, updates, related product or service information, etc. We may use third party service providers like MailChimp to help us operate our business and the Site or administer activities on our behalf, such as sending out newsletters or surveys. We may share your information with these third parties for those limited purposes provided that you have given us your permission.
### Third party websites
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. We do not control the content or links that appear 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.
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 may be delivered to Users by advertising partners, who may set cookies. These cookies allow the ad server to recognize your computer each time they send you an online advertisement to compile non personal identification information about you or others who use your computer. This information allows ad networks to, among other things, deliver targeted advertisements that they believe will be of most interest to you. This privacy policy does not cover the use of cookies by any advertisers.
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.

View File

@ -1,8 +1,8 @@
## Terms of Service ("Terms")
Last updated: May 16, 2018 — Update to contact information.
Last updated: June 5, 2019 — Minor text fixes and link additions.
Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the https://lexicon.ga website (the "Service") or the cloud account service (an "Account") operated by Robbie Antenesse and Alamantus GameDev ("us", "we", or "our").
Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the https://lexicon.ga website (the "Service") or the cloud account service (an "Account") operated by [Robbie Antenesse](https://robbie.antenesse.net) and [Alamantus GameDev](https://alamantus.com) ("us", "we", or "our").
Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who access or use the Service. By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.
@ -14,7 +14,7 @@ If you stop using our Service, your data will remain unless you request its remo
Instructions for use are visible through the "Help" button on the site.
### Public Dictionaries
Any dictionaries created and associated with an Account are private by default and only visible to the owner of the Account. You may make your dictionaries visible to the public at any time by utilizing the appropriate dictionarys setting.
Any dictionaries created and associated with an Account are private by default and only visible to the owner of the Account. You may make your dictionaries visible to the public at any time by utilizing the appropriate dictionary setting.
If you utilize the ability to make dictionaries associated with an Account visible to the public, you accept all responsibility for any and all damages that may come as a result of the information you place online and acknowledge that we are not liable in any way. Do not put any personal information online. We cannot protect what you deliberately make publicly visible.

View File

@ -1,12 +1,17 @@
# 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]+)/?$ api/router.php?view=publicview&dict=$1&word=$2 [NC,L] # Handle word ids.
RewriteRule ^view/([0-9]+)/([0-9]+)/?$ router.php?view=word&dict=$1&word=$2 [NC,L] # Handle word ids.
RewriteRule ^([0-9]+)/([0-9]+)/?$ api/router.php?view=publicview&dict=$1&word=$2 [NC,L] # Handle word ids.
RewriteRule ^([0-9]+)/([0-9]+)/?$ router.php?view=word&dict=$1&word=$2 [NC,L] # Handle word ids.
RewriteRule ^view/([0-9]+)/?$ api/router.php?view=publicview&dict=$1 [NC,L] # Handle dictionary ids.
RewriteRule ^view/([0-9]+)/?$ router.php?view=dictionary&dict=$1 [NC,L] # Handle dictionary ids.
RewriteRule ^([0-9]+)/?$ api/router.php?view=publicview&dict=$1 [NC,L] # Handle dictionary ids.
RewriteRule ^([0-9]+)/?$ router.php?view=dictionary&dict=$1 [NC,L] # Handle dictionary ids.
RewriteRule ^/?(index.html)?$ router.php [NC,L] # Handle dictionary ids.
#RewriteRule ^issues/?$ https://github.com/Alamantus/Lexiconga/issues [R=301,L] # Shorten issues url.

View File

@ -0,0 +1,14 @@
[
{
"header": "Lexiconga is now fully ad-free",
"body": "<p><em>August 26, 2022</em> &ndash; 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> &ndash; 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"
}
]

View File

@ -1,11 +1,11 @@
<?php
require_once('./config.php');
require_once(realpath(dirname(__FILE__) . '/./config.php'));
class Db {
private $dbh;
public $last_error_info;
function __construct() {
$this->dbh = new PDO('mysql:host=localhost;dbname=lexiconga;charset=utf8', DB_USER, DB_PASSWORD);
$this->dbh = new PDO('mysql:host=localhost;dbname=' . DB_NAME . ';charset=utf8', DB_USER, DB_PASSWORD);
$this->dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$this->last_error_info = null;
}

View File

@ -1,6 +1,6 @@
<?php
require_once('./Db.php');
require_once('./Token.php');
require_once(realpath(dirname(__FILE__) . '/./Db.php'));
require_once(realpath(dirname(__FILE__) . '/./Token.php'));
class Dictionary {
private $db;
@ -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,84 +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,
'isComplete' => false,
'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) {
return array(
'name' => $row['name'],
'pronunciation' => $row['pronunciation'],
'partOfSpeech' => $row['part_of_speech'],
'definition' => $row['definition'],
'details' => $row['details'],
'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 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'],
@ -173,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(
@ -197,7 +129,8 @@ VALUES ($new_id, ?, ?, ?, ?)";
'allowDuplicates' => $result['allow_duplicates'] === '1' ? true : false,
'caseSensitive' => $result['case_sensitive'] === '1' ? true : false,
'sortByDefinition' => $result['sort_by_definition'] === '1' ? true : false,
'isComplete' => 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'],
@ -215,7 +148,8 @@ SET name=:name,
allow_duplicates=:allow_duplicates,
case_sensitive=:case_sensitive,
sort_by_definition=:sort_by_definition,
is_complete=:is_complete,
theme=:theme,
custom_css=:custom_css,
is_public=:is_public,
last_updated=:last_updated,
created_on=:created_on
@ -229,7 +163,8 @@ WHERE user=$user AND id=$dictionary";
':allow_duplicates' => $dictionary_object['settings']['allowDuplicates'] ? 1 : 0,
':case_sensitive' => $dictionary_object['settings']['caseSensitive'] ? 1 : 0,
':sort_by_definition' => $dictionary_object['settings']['sortByDefinition'] ? 1 : 0,
':is_complete' => 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'],
@ -239,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";
@ -253,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'],
));
@ -274,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'],
@ -288,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();
@ -312,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),
@ -341,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); });
@ -354,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,

View File

@ -0,0 +1,423 @@
<?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) {
$this->db = new Db();
$this->token = new Token();
$this->defaults = array(
'partsOfSpeech' => 'Noun,Adjective,Verb',
);
$this->details = $this->getPublicDictionaryDetails($dictionary_id);
$this->words = $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) {
if (is_numeric($dictionary) && is_numeric($word)) {
$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 words.word_id=? AND dictionaries.is_public=1";
$result = $this->db->query($query, array($dictionary, $word))->fetch();
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->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 = 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 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();
}
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;
}
}

View File

@ -1,5 +1,5 @@
<?php
require_once('../vendor/autoload.php');
require_once(realpath(dirname(__FILE__) . '/../vendor/autoload.php'));
use \Firebase\JWT\JWT;
use \Hashids\Hashids;

View File

@ -1,7 +1,7 @@
<?php
require_once('./Db.php');
require_once('./Token.php');
require_once('./Dictionary.php');
require_once(realpath(dirname(__FILE__) . '/./Db.php'));
require_once(realpath(dirname(__FILE__) . '/./Token.php'));
require_once(realpath(dirname(__FILE__) . '/./Dictionary.php'));
class User {
private $db;
@ -18,12 +18,12 @@ class User {
if ($user) {
if ($user['old_password'] !== null) {
if ($user['old_password'] === crypt($password, $email)) {
if ($this->upgradePassword($password)) {
if ($this->upgradePassword($password, $user)) {
return $this->logIn($email, $password);
}
}
} else if (password_verify($password, $user['password'])) {
$this->db->execute('UPDATE users SET last_login=' . time() . ' WHERE id=' . $user['id']);
$this->db->execute('UPDATE users SET last_login=current_timestamp() WHERE id=' . $user['id']);
$token = $this->generateUserToken($user['id'], $user['current_dictionary']);
return array(
'token' => $token,
@ -42,7 +42,7 @@ class User {
public function create ($email, $password, $user_data) {
$insert_user_query = 'INSERT INTO users (email, password, public_name, allow_email, created_on)
VALUES (?, ?, ?, ?, ?)';
VALUES (?, ?, ?, ?, current_timestamp())';
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$insert_user = $this->db->execute($insert_user_query, array(
@ -50,7 +50,6 @@ VALUES (?, ?, ?, ?, ?)';
$password_hash,
$user_data['publicName'] !== '' ? $user_data['publicName'] : null,
$user_data['allowEmail'] ? 1 : 0,
time(),
));
if ($insert_user === true) {
$new_user_id = $this->db->lastInsertId();
@ -267,6 +266,75 @@ VALUES (?, ?, ?, ?, ?)';
return false;
}
public function setPasswordReset($email) {
$date = date("Y-m-d H:i:s");
$reset_code = random_int(0, 999999999);
$reset_code_hash = $this->token->hash($reset_code);
$query = "UPDATE `users` SET `password_reset_code`=?, `password_reset_date`=? WHERE `email`=?;";
$reset = $this->db->execute($query, array(
$reset_code,
$date,
$email,
));
if ($reset) {
$user_data = $this->getUserDataByEmailForPasswordReset($email);
if ($user_data) {
$to = $email;
$subject = "Here's your Lexiconga password reset link";
$message = "Hello " . $user_data['public_name'] . "\r\n\r\nSomeone has requested a password reset link for your Lexiconga account. If it was you, you can reset your password by going to the link below and entering a new password for yourself:\r\n";
$message .= "https://" . $_SERVER['HTTP_HOST'] . "/passwordreset.php?account=" . $user_data['id'] . "&code=" . $reset_code_hash . "\r\n\r\n";
$message .= "If it wasn't you who requested the link, you can ignore this email since it was only sent to you, but you might want to consider changing your password when you have a chance.\r\n\r\n";
$message .= "The password link will only be valid for today until you use it.\r\n\r\n";
$message .= "Thanks!\r\nThe Lexiconga Admins";
$header = "From: Lexiconga Password Reset <donotreply@" . $_SERVER['HTTP_HOST'] . ">\r\n"
. "Reply-To: help@" . $_SERVER['HTTP_HOST'] . "\r\n"
. "X-Mailer: PHP/" . phpversion();
if (mail($to, $subject, $message, $header)) {
return true;
} else {
return array(
'error' => 'Could not send email to ' . $email,
);
}
}
} else {
return array(
'error' => $this->db->last_error_info,
);
}
return false;
}
public function checkPasswordReset($id, $code) {
$date = date("Y-m-d");
$daterange = "'" . $date . " 00:00:00' AND '" . $date . " 23:59:59'";
$unhashed_code = $this->token->unhash($code);
$query = "SELECT * FROM `users` WHERE `id`=? AND `password_reset_code`=? AND `password_reset_date` BETWEEN " . $daterange . ";";
$stmt = $this->db->query($query, array(
$id,
$unhashed_code,
));
$results = $stmt->fetchAll();
if ($stmt && $results) {
return count($results) === 1;
} else {
return false;
}
}
public function resetPassword($password, $id) {
$id = intval($id);
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$query = "UPDATE `users` SET `password`=?, `old_password`=null, `password_reset_code`=null WHERE `id`=?;";
return $this->db->execute($query, array(
$password_hash,
$id,
));
}
private function generateUserToken ($user_id, $dictionary_id) {
$user_data = array(
'id' => intval($user_id),
@ -277,14 +345,27 @@ VALUES (?, ?, ?, ?, ?)';
}
private function hasMembership ($id) {
$current_membership = "SELECT * FROM memberships WHERE user=$id AND start_date>=CURRENT_TIMESTAMP AND CURRENT_TIMESTAMP<expire_date";
$current_membership = "SELECT * FROM memberships WHERE user=$id AND start_date>=current_timestamp() AND current_timestamp()<expire_date";
return $this->db->query($current_membership)->rowCount() > 0;
}
private function upgradePassword ($password) {
private function upgradePassword($password, $user) {
$new_password = password_hash($password, PASSWORD_DEFAULT);
$update_query = 'UPDATE users SET old_password=NULL, password=? WHERE id=' . $user['id'];
$stmt = $this->db->query($update_query, array($new_password));
return $stmt->rowCount() === 1;
}
private function getUserDataByEmailForPasswordReset($email) {
$query = 'SELECT id, public_name FROM users WHERE email=?';
$stmt = $this->db->query($query, array($email));
$result = $stmt->fetch();
if ($stmt && $result) {
return array(
'id' => $result['id'],
'public_name' => $result['public_name'] ? $result['public_name'] : 'Lexiconger',
);
}
return false;
}
}

View File

@ -1,4 +1,8 @@
<?php
define('DB_NAME', 'lexiconga_db_name');
define('DB_USER', 'lexiconga_db_username');
define('DB_PASSWORD', 'user_password');
define('DB_PASSWORD', 'user_password');
define('LOGIN_FAILURES_ALLOWED', 10);
define('LOGIN_FAILURES_LOCKOUT_MINUTES', 60);

View File

@ -1,6 +1,7 @@
<?php
require_once('./Response.php');
require_once('./User.php');
require_once(realpath(dirname(__FILE__) . '/./config.php'));
require_once(realpath(dirname(__FILE__) . '/./Response.php'));
require_once(realpath(dirname(__FILE__) . '/./User.php'));
$inputJSON = file_get_contents('php://input');
$inputJSON = strip_tags($inputJSON);
@ -39,6 +40,26 @@ switch ($action) {
), 400);
}
case 'login': {
session_start();
if (isset($_SESSION['unlock'])) {
if (time() < $_SESSION['unlock']) {
$seconds_left = ($_SESSION['unlock'] - time());
$minutes_left = floor($seconds_left / 60);
$seconds_left = $seconds_left % 60;
return Response::json(array(
'data' => 'Too many failed login attempts. You must wait another '
. ($minutes_left > 0 ? $minutes_left . ' minutes ' : '')
. ($minutes_left > 0 && $seconds_left > 0 ? 'and ' : '')
. ($seconds_left > 0 ? $seconds_left . ' seconds ' : '')
. 'until you can log in again.',
'error' => true,
), 403);
} else {
unset($_SESSION['failures']);
unset($_SESSION['unlock']);
}
}
if (isset($request['email']) && isset($request['password'])) {
$user = new User();
$user_data = $user->logIn($request['email'], $request['password']);
@ -48,8 +69,22 @@ switch ($action) {
'error' => false,
), 200);
}
if (!isset($_SESSION['failures'])) {
$_SESSION['failures'] = 0;
}
$_SESSION['failures']++;
if ($_SESSION['failures'] >= LOGIN_FAILURES_ALLOWED) {
$_SESSION['unlock'] = time() + (LOGIN_FAILURES_LOCKOUT_MINUTES * 60);
return Response::json(array(
'data' => 'Too many failed login attempts. You must wait ' . LOGIN_FAILURES_LOCKOUT_MINUTES . ' minutes until you can log in again.',
'error' => true,
), 403);
}
return Response::json(array(
'data' => 'Could not log in: incorrect data',
'data' => 'Incorrect email or password.<br>After ' . (LOGIN_FAILURES_ALLOWED - $_SESSION['failures']) . ' more failures, you will be locked out for ' . LOGIN_FAILURES_LOCKOUT_MINUTES . ' minutes.',
'error' => true,
), 401);
}
@ -384,6 +419,58 @@ switch ($action) {
'error' => true,
), 400);
}
case 'initiate-password-reset': {
if (isset($request['email'])) {
$user = new User();
$password_reset = $user->setPasswordReset($request['email']);
if ($password_reset === true) {
return Response::json(array(
'data' => $password_reset,
'error' => false,
), 200);
}
if (isset($password_reset['error'])) {
return Response::json(array(
'data' => $password_reset['error'],
'error' => true,
), 500);
}
return Response::json(array(
'data' => 'Could not send password reset key: email not found',
'error' => true,
), 401);
}
return Response::json(array(
'data' => 'Could not send password reset key: required data missing',
'error' => true,
), 400);
}
case 'password-reset': {
if (isset($request['account']) && isset($request['password'])) {
$user = new User();
$password_reset = $user->resetPassword($request['password'], $request['account']);
if ($password_reset === true) {
return Response::json(array(
'data' => $password_reset,
'error' => false,
), 200);
}
if (isset($password_reset['error'])) {
return Response::json(array(
'data' => $password_reset['error'],
'error' => true,
), 500);
}
return Response::json(array(
'data' => 'Could not send password reset key: email not found',
'error' => true,
), 401);
}
return Response::json(array(
'data' => 'Could not send password reset key: required data missing',
'error' => true,
), 400);
}
default: {
return Response::html('Hi!');

View File

@ -0,0 +1,274 @@
<?php
$incoming = isset($_POST['incoming']) ? json_decode($_POST['incoming'], true) : false;
$outgoing = isset($_GET['outgoing']);
header('Access-Control-Allow-Origin: *');
header('Expires: Sun, 01 Nov 2015 22:46:51 GMT');
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
define("VERSION1_DATABASE_SERVERNAME", "host");
define("VERSION1_DATABASE_USERNAME", "username");
define("VERSION1_DATABASE_PASSWORD", "password");
define("VERSION1_DATABASE_NAME", "database");
define("MIGRATE_TO", "remote_url");
define("NEW_DATABASE_SERVERNAME", "host");
define("NEW_DATABASE_USERNAME", "username");
define("NEW_DATABASE_PASSWORD", "password");
define("NEW_DATABASE_NAME", "database");
if ($outgoing) {
echo '<pre>' . PHP_EOL;
echo 'Starting migration from ' . VERSION1_DATABASE_SERVERNAME . ' db ' . VERSION1_DATABASE_NAME . PHP_EOL . 'To ' . MIGRATE_TO . PHP_EOL . PHP_EOL;
$dbconnection = new PDO('mysql:host=' . VERSION1_DATABASE_SERVERNAME . ';dbname=' . VERSION1_DATABASE_NAME . ';charset=utf8', VERSION1_DATABASE_USERNAME, VERSION1_DATABASE_PASSWORD);
$dbconnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// $dbconnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$dbconnection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
try {
$users_stmt = $dbconnection->prepare('SELECT * FROM `users`');
$users_stmt->execute();
if ($users_stmt) {
$users = $users_stmt->fetchAll();
if ($users !== false) {
echo count($users) . ' Users' . PHP_EOL;
foreach($users as $user) {
$send_user_response = send('user', $user);
if ($send_user_response === "GOT USER") {
echo PHP_EOL . 'User ' . $user['id'] . ' sent successfully' . PHP_EOL;
$dictionaries_stmt = $dbconnection->prepare('SELECT * FROM `dictionaries` WHERE `user`=?');
$dictionaries_stmt->execute(array(intval($user['id'])));
if ($dictionaries_stmt) {
$dictionaries = $dictionaries_stmt->fetchAll();
if ($dictionaries !== false) {
echo 'User ' . $user['id'] . ' has ' . count($dictionaries) . ' Dictionaries' . PHP_EOL;
$send_dictionaries_response = send('dictionaries', $dictionaries, array('user' => $user['id']));
if ($send_dictionaries_response === 'GOT DICTIONARIES') {
echo 'Dictionaries sent successfully' . PHP_EOL;
foreach($dictionaries as $dictionary) {
$words_stmt = $dbconnection->prepare('SELECT * FROM `words` WHERE `dictionary`=?');
$words_stmt->execute(array(intval($dictionary['id'])));
if ($words_stmt) {
$words = $words_stmt->fetchAll();
if ($words !== false) {
$send_words_response = send('words', $words);
if ($send_words_response === 'GOT WORDS') {
echo 'Words for Dictionary ' . $dictionary['id'] . ' sent successfully' . PHP_EOL;
} else {
echo var_export($send_words_response, true) . PHP_EOL . PHP_EOL;
}
} else {
echo '$words_stmt->fetchAll() failed.' . PHP_EOL . var_export($words_stmt->errorInfo(), true) . PHP_EOL . PHP_EOL;
}
} else {
echo '$words_stmt failed' . PHP_EOL . PHP_EOL;
}
}
} else {
echo var_export($send_dictionaries_response, true) . PHP_EOL . PHP_EOL;
}
} else {
echo '$dictionaries_stmt->fetchAll() failed.' . PHP_EOL . var_export($dictionaries_stmt->errorInfo(), true) . PHP_EOL . PHP_EOL;
}
} else {
echo '$dictionaries_stmt failed' . PHP_EOL . PHP_EOL;
}
} else {
var_dump($send_user_response) . PHP_EOL . PHP_EOL;
}
}
} else {
echo '$users_stmt->fetchAll() failed.' . PHP_EOL . var_export($users_stmt->errorInfo(), true) . PHP_EOL . PHP_EOL;
}
} else {
echo '$users_stmt failed' . PHP_EOL . PHP_EOL;
}
}
catch (PDOException $ex) {
echo var_export($ex, true) . PHP_EOL . PHP_EOL;
}
echo '</pre>';
} else if ($incoming !== false) {
$type = isset($_POST['type']) ? $_POST['type'] : false;
if ($type !== false) {
$dbconnection = new PDO('mysql:host=' . NEW_DATABASE_SERVERNAME . ';dbname=' . NEW_DATABASE_NAME . ';charset=utf8', NEW_DATABASE_USERNAME, NEW_DATABASE_PASSWORD);
$dbconnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
switch($type) {
case 'user': {
if ($incoming === null) {
echo 'Invalid JSON';
} else {
$user_stmt = $dbconnection->prepare('INSERT INTO `users`(`id`, `email`, `old_password`, `password`, `public_name`, `current_dictionary`, `allow_email`, `last_login`, `password_reset_code`, `password_reset_date`, `created_on`) VALUES (?,?,?,?,?,?,?,?,?,?,?)');
$user_stmt->execute(array(
intval($incoming['id']),
$incoming['email'],
$incoming['password'],
null,
$incoming['public_name'],
intval($incoming['current_dictionary']),
intval($incoming['allow_email']),
$incoming['last_login'],
null,
null,
$incoming['created_on'],
));
if ($user_stmt) {
echo 'GOT USER';
} else {
var_dump($user_stmt->errorInfo());
}
}
break;
}
case 'dictionaries': {
if ($incoming === null) {
echo 'Invalid JSON';
} else {
if (count($incoming) === 0) {
echo 'Need new dictionary for user ' . $_POST['user'] . ': ';
$new_id = mt_rand(1000, 999999999);
$user = intval($_POST['user']);
$insert_dictionary_query = "INSERT INTO dictionaries (id, user, description, created_on) VALUES (?, ?, ?, ?)";
$insert_dictionary_stmt = $dbconnection->prepare($insert_dictionary_query);
$insert_dictionary = $insert_dictionary_stmt->execute(array($new_id, $user, 'A new dictionary.', time()));
if ($insert_dictionary) {
$insert_linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, exceptions, orthography_notes, grammar_notes) VALUES ($new_id, ?, ?, ?, ?)";
$insert_linguistics_stmt = $dbconnection->prepare($insert_linguistics_query);
$insert_linguistics = $insert_linguistics_stmt->execute(array('Noun,Adjective,Verb', '', '', ''));
if ($insert_linguistics === true) {
$update_query = 'UPDATE users SET current_dictionary=? WHERE id=?';
$update_stmt = $dbconnection->prepare($update_query);
$update = $update_stmt->execute(array($new_id, $user));
if ($update) {
echo 'CREATED DICTIONARY';
} else {
var_dump($update_stmt);
}
} else {
var_dump($insert_linguistics_stmt->errorInfo());
}
} else {
var_dump($insert_dictionary_stmt->errorInfo());
}
} else {
$dictionaries_query = 'INSERT INTO `dictionaries`(`id`, `user`, `name`, `specification`, `description`, `allow_duplicates`, `case_sensitive`, `sort_by_definition`, `theme`, `is_public`, `last_updated`, `created_on`) VALUES ';
$linguistics_query = "INSERT INTO dictionary_linguistics (dictionary, parts_of_speech, exceptions, orthography_notes, grammar_notes) VALUES ";
$dictionaries_params = array();
$linguistics_params = array();
foreach($incoming as $dictionary) {
$dictionaries_query .= '(?,?,?,?,?,?,?,?,?,?,?,?), ';
// `id`, `user`, `name`, `description`, `words`, `next_word_id`, `allow_duplicates`, `case_sensitive`, `parts_of_speech`, `sort_by_equivalent`, `is_complete`, `is_public`, `last_updated`, `created_on`
$dictionaries_params[] = intval($dictionary['id']);
$dictionaries_params[] = intval($dictionary['user']);
$dictionaries_params[] = $dictionary['name'];
$dictionaries_params[] = 'Dictionary';
$dictionaries_params[] = fixStupidOldNonsense($dictionary['description']);
$dictionaries_params[] = intval($dictionary['allow_duplicates']);
$dictionaries_params[] = intval($dictionary['case_sensitive']);
$dictionaries_params[] = intval($dictionary['sort_by_equivalent']);
$dictionaries_params[] = 'default';
$dictionaries_params[] = intval($dictionary['is_public']);
$dictionaries_params[] = $dictionary['last_updated'] ? strtotime($dictionary['last_updated']) : null;
$dictionaries_params[] = strtotime($dictionary['created_on']);
$linguistics_query .= '(?, ?, ?, ?, ?), ';
$linguistics_params[] = intval($dictionary['id']);
$linguistics_params[] = $dictionary['parts_of_speech'];
$linguistics_params[] = '';
$linguistics_params[] = '';
$linguistics_params[] = '';
}
$dictionaries_stmt = $dbconnection->prepare(trim($dictionaries_query, ', '));
$dictionaries_stmt->execute($dictionaries_params);
if ($dictionaries_stmt) {
$linguistics_stmt = $dbconnection->prepare(trim($linguistics_query, ', '));
$linguistics_stmt->execute($linguistics_params);
if ($linguistics_stmt) {
echo 'GOT DICTIONARIES';
} else {
var_dump($linguistics_stmt->errorInfo());
}
} else {
var_dump($dictionaries_stmt->errorInfo());
}
}
}
break;
}
case 'words': {
if ($incoming === null) {
echo 'Invalid JSON';
} else {
if (count($incoming) > 0) {
$words_query = 'INSERT INTO `words`(`dictionary`, `word_id`, `name`, `pronunciation`, `part_of_speech`, `definition`, `details`, `last_updated`, `created_on`) VALUES ';
$words_params = array();
foreach($incoming as $word) {
$words_query .= '(?,?,?,?,?,?,?,?,?), ';
// `dictionary`, `word_id`, `name`, `pronunciation`, `part_of_speech`, `simple_definition`, `long_definition`, `last_updated`, `created_on`
$words_params[] = intval($word['dictionary']);
$words_params[] = intval($word['word_id']);
$words_params[] = $word['name'];
$words_params[] = $word['pronunciation'];
$words_params[] = $word['part_of_speech'];
$words_params[] = $word['simple_definition'];
$words_params[] = fixStupidOldNonsense($word['long_definition']);
$words_params[] = $word['last_updated'] ? strtotime($word['last_updated']) : null;
$words_params[] = strtotime($word['created_on']);
}
$words_stmt = $dbconnection->prepare(trim($words_query, ', '));
$words_stmt->execute($words_params);
if ($words_stmt) {
echo 'GOT WORDS';
} else {
var_dump($words_stmt->errorInfo());
}
} else {
echo 'NO WORDS';
}
}
break;
}
}
}
} else {
echo 'no' . PHP_EOL . PHP_EOL;
}
function send($type, $data, $additional_data = array()) {
$send_data = array_merge(array('type' => $type, 'incoming' => json_encode($data)), $additional_data);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, MIGRATE_TO . '/api/migrate.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($send_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);
return $server_output;
}
function fixStupidOldNonsense($markdown) {
$markdown = str_replace('&quot;', '"', $markdown);
$markdown = str_replace('&apos;', "'", $markdown);
$markdown = str_replace('&#92;', '\\', $markdown);
$markdown = str_replace('<br>', '\\n', $markdown);
return $markdown;
}

View File

@ -1,24 +0,0 @@
<?php
$view = isset($_GET['view']) ? $_GET['view'] : false;
$dict = isset($_GET['dict']) ? $_GET['dict'] : false;
switch ($view) {
case 'publicview': {
$html = file_get_contents('../view.html');
if ($dict !== false) {
require_once('./Dictionary.php');
$dictionary = new Dictionary();
$dictionary_data = $dictionary->getPublicDictionaryDetails($dict);
if ($dictionary_data !== false) {
$dictionary_data['words'] = $dictionary->getPublicDictionaryWords($dict);
$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);
$html = str_replace('{{dict_json}}', addslashes($dictionary_json), $html);
}
echo $html;
}
break;
}
}

26
src/php/passwordreset.php Normal file
View File

@ -0,0 +1,26 @@
<?php
require_once(realpath(dirname(__FILE__) . '/./api/config.php'));
require_once(realpath(dirname(__FILE__) . '/./api/Db.php'));
require_once(realpath(dirname(__FILE__) . '/./api/User.php'));
require_once(realpath(dirname(__FILE__) . '/./api/Response.php'));
$html = file_get_contents('./template-passwordreset.html');
$content = '<p>Sorry, this password reset link is not valid.</p>';
if (isset($_GET['account']) && isset($_GET['code'])) {
$user = new User();
if ($user->checkPasswordReset($_GET['account'], $_GET['code'])) {
$content = '<label>New Password<br>
<input type="password" id="newPassword" maxlength="100">
</label>
<label>Confirm Password<br>
<input type="password" id="newConfirm" maxlength="100">
</label>
<input type="hidden" id="account" value="' . $_GET['account'] . '">
<section id="newPasswordErrorMessages"></section>
<button id="newPasswordSubmit" class="button">Update Password</button>';
}
}
$html = str_replace('{{content}}', $content, $html);
return Response::html($html);

158
src/php/router.php Normal file
View File

@ -0,0 +1,158 @@
<?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/PublicDictionary.php'));
$dictionary = new PublicDictionary($dict);
$dictionary_data = $dictionary->details;
if ($dictionary_data !== false) {
$dictionary_data['words'] = $dictionary->words;
$html = str_replace('{{dict}}', $dict, $html);
$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);
$html = str_replace('{{dict_name}}', 'Error: Dictionary Not Found', $html);
$html = str_replace('{{public_name}}', 'Error', $html);
$html = str_replace('{{dict_json}}', '{"name": "Error:", "specification": "Dictionary Not Found", "words": []}', $html);
}
return Response::html($html);
}
break;
}
case 'word': {
$html = file_get_contents(realpath(dirname(__FILE__) . '/./template-view.html'));
$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/PublicDictionary.php'));
$dictionary = new PublicDictionary($dict, true);
$dictionary_data = $dictionary->details;
if ($dictionary_data !== false) {
$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(
'name' => 'Error: Word Not Found',
'pronunciation' => '',
'partOfSpeech' => '',
'definition' => 'No word with the id ' . $word . ' was found in the ' . $dictionary_name,
'details' => '',
'lastUpdated' => null,
'createdOn' => null,
'wordId' => null,
);
}
$dictionary_data['words'] = array($word_data);
$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(utf8ize($dictionary_data));
$html = str_replace('{{dict_json}}', addslashes($dictionary_json), $html);
} else {
$html = str_replace('{{dict}}', 'error', $html);
$html = str_replace('{{dict_name}}', 'Error: Dictionary Not Found', $html);
$html = str_replace('{{public_name}}', 'Error', $html);
$html = str_replace('{{dict_json}}', '{"name": "Error:", "specification": "Dictionary Not Found", "words": []}', $html);
}
return Response::html($html);
}
break;
}
default: {
$html = file_get_contents(realpath(dirname(__FILE__) . '/./template-index.html'));
$announcements = file_get_contents(realpath(dirname(__FILE__) . '/./announcements.json'));
$announcements = json_decode($announcements, true);
$announcements_html = '';
foreach ($announcements as $announcement) {
$expire = strtotime($announcement['expire']);
if (time() < $expire) {
$announcements_html .= '<article class="announcement"' . (isset($announcement['dismissId']) ? ' id="announcement-' . $announcement['dismissId'] . '"' : '') . ' data-expires="' . $announcement['expire'] . '">
<a class="close-button" title="Dismiss Announcement">&times;&#xFE0E;</a>
<h4>' . $announcement['header'] . '</h4>
' . $announcement['body'] . '
</article>';
}
}
$html = str_replace('{{announcements}}', $announcements_html, $html);
$upup_files = array(
'goodie-bag.js',
'src.js',
'main.css',
'help.html',
'privacy.html',
'terms.html',
'usage.html',
'ipa-table.html',
'favicon.png',
);
$files = array_map(function($file_name) use($upup_files) {
foreach($upup_files as $index => $upup_file) {
$file_pieces = explode('.', $upup_file);
if (substr($file_name, 0, strlen($file_pieces[0])) === $file_pieces[0]
&& substr($file_name, -strlen($file_pieces[1])) === $file_pieces[1]) {
return str_replace('\\', '/', $file_name);
}
}
return false;
}, scandir('.'));
$files = array_filter($files);
$upup_insert = "<script src=\"upup.min.js\"></script>
<script>
window.onload = (function (oldLoad) {
oldLoad && oldLoad();
if (UpUp) {
UpUp.start({
'cache-version': '2.2.1',
'content-url': 'offline.html',
'assets': [
\"" . implode('","', $files) . "\"
],
});
}
})(window.onload);
</script>";
$html = str_replace('{{upup_insert}}', $upup_insert, $html);
$imported_from_http = '';
if (isset($_POST['oldDictionaryFromHTTP'])) {
$imported_from_http = '<script>window.dictionaryImportedFromHTTP = "' . addslashes($_POST['oldDictionaryFromHTTP']) . '";</script>';
}
$html = str_replace('{{imported_from_http}}', $imported_from_http, $html);
return Response::html($html);
break;
}
}

View File

@ -13,4 +13,19 @@
}
}
}
}
details summary {
cursor: pointer;
h4 {
display: inline-block;
margin: 5px 0;
}
}
.share-link {
margin-left: $general-padding !important;
line-height: 16px !important;
padding: 1px 3px 3px !important;
}

View File

@ -1,7 +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;
border-bottom: 1px solid $mid;
padding: 5px calc($general-padding / 2);
margin: 0 0 5px;
&#top {
@ -14,7 +25,7 @@ main {
width: 90%;
max-width: 1000px;
min-height: 400px;
margin: 0 auto;
margin: 0 auto ($header-height * 1.25);
#sideColumn,
#mainColumn {
@ -29,13 +40,12 @@ main {
#mainColumn {
width: 64%;
margin: 0;
margin: $general-padding 0 0;
}
article {
width: 95%;
margin: 10px auto;
border: $border;
dl {
padding: 0 $general-padding;
@ -46,6 +56,5 @@ main {
footer {
display: block;
padding: $general-padding;
border-top: 1px solid $mid;
margin: 5px 0 0;
}

View File

@ -1,30 +1,37 @@
p, span {
&.red {
color: $red;
}
a {
text-decoration: underline;
}
p, span {
&.bold {
font-weight: bold;
}
}
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,25 +40,47 @@ label {
width: 100%;
}
.label-button {
@extend .button;
textarea {
min-height: 100px;
}
.label-button {
display: inline-block;
padding: 3px 9px;
border-radius: 3px;
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: 0.8;
}
}
.label-help-button {
@extend .button;
display: inline-block;
padding: 3px 9px;
border-radius: 3px;
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: 0.8;
}
}
}
@ -62,38 +91,60 @@ ul {
.tag {
display: inline-block;
padding: 3px 9px;
border: $border;
border-radius: 3px;
background-color: $light;
line-height: 30px;
line-height: 1.5;
&.small {
font-size: 80%;
line-height: 25px;
line-height: 1.3;
}
&.red {
background-color: $red;
color: $white;
&+.tag {
margin-top: 5px;
}
}
span .tag {
@extend .tag;
border-radius: 3px 0 0 3px;
display: inline-block;
padding: 3px 9px;
border-radius: 3px;
line-height: 1.5;
&.small {
font-size: 80%;
line-height: 1.3;
}
&+.tag {
border-left: none;
border-radius: 0 3px 3px 0;
background-color: $white;
}
&+span .tag {
margin-top: 5px;
}
}
.button {
@extend .tag;
display: inline-block;
padding: 3px 9px;
border-radius: 3px;
line-height: 1.5;
cursor: pointer;
user-select: none;
text-decoration: none;
font-weight: bold;
&.small {
font-size: 80%;
line-height: 1.3;
}
&+.button {
margin-top: 5px;
}
}
.modal {
@ -102,6 +153,7 @@ span .tag {
left: 0;
bottom: 0;
right: 0;
z-index: 10;
.modal-background {
position: absolute;
@ -109,7 +161,6 @@ span .tag {
left: 0;
bottom: 0;
right: 0;
background-color: #000000;
opacity: 0.5;
}
@ -122,8 +173,6 @@ span .tag {
max-width: 100%;
height: 600px;
max-height: 100%;
background-color: $white;
border: $border;
border-radius: 5px;
.close-button {
@ -134,30 +183,19 @@ span .tag {
font-size: 200%;
cursor: pointer;
user-select: none;
text-decoration: none;
}
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
display: inline-block;
list-style: none;
margin: 0 2px -1px;
padding: 10px $general-padding;
border-top: $border;
border-left: $border;
border-right: $border;
border-radius: 5px 5px 0 0;
background-color: $mid;
cursor: pointer;
&.active {
background-color: $white;
border-bottom: 1px solid $white;
}
}
}
@ -198,6 +236,25 @@ span .tag {
}
}
.fadeout {
overflow: hidden;
animation-name: shut;
animation-duration: 0.3s;
animation-timing-function: linear;
animation-iteration-count: 1;
animation-fill-mode: both;
}
@keyframes shut {
0% {
opacity: 1;
max-height: 200px;
}
100% {
opacity: 0;
max-height: 0px;
}
}
.pagination {
position: relative;
text-align: center;

View File

@ -2,6 +2,9 @@
#title {
display: inline-block;
margin: 3px $general-padding 3px 0;
width: 180px;
max-height: $header-height - $general-padding;
vertical-align: middle;
}
#openSearchModal {
@ -25,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;
@ -42,8 +45,7 @@
left: unset;
bottom: unset;
right: unset;
background-color: $white;
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 {
@ -100,16 +102,14 @@
#wordForm {
position: fixed;
top: auto;
top: $header-height + $general-padding;
width: 31%;
max-width: 320px;
padding: 10px;
background-color: $light;
border: $border;
border-radius: 5px;
max-height: 80%;
overflow-y: auto;
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
z-index: 8;
}
.edit-form {
@ -117,11 +117,12 @@
max-width: 500px;
}
#detailsSection {
padding: $general-padding;
background-color: $white;
border: $border;
#mainColumn {
border-radius: 5px;
}
#detailsSection {
padding: $general-padding $general-padding 0;
#dictionaryName {
margin-top: 0;
@ -135,8 +136,6 @@
list-style: none;
margin: 0;
padding: 10px $general-padding;
border: $border;
background-color: $light;
cursor: pointer;
&:first-child {
@ -148,15 +147,10 @@
&:not(:first-child) {
border-left: none;
}
&.active {
background-color: #bababa;
}
}
}
#detailsPanel {
background-color: $white;
padding: $general-padding;
max-height: 400px;
overflow-y: auto;
@ -169,9 +163,23 @@
}
.announcement {
position: relative;
padding: 0 $general-padding $general-padding;
text-align: center;
.close-button {
position: absolute;
top: 5px;
right: 5px;
font-size: 25px;
line-height: 10px;
cursor: pointer;
text-decoration: none;
}
}
.entry {
background-color: $light;
.word {
display: inline-block;
margin: 3px 0;
@ -189,6 +197,7 @@
header {
position: relative;
padding-right: 70px;
.word-option-button {
position: absolute;
@ -202,15 +211,12 @@
position: absolute;
top: 3px;
right: 3px;
background-color: $white;
border: $border;
border-radius: 5px;
.word-option {
padding: 10px 25px;;
&:hover {
background-color: $light;
cursor: pointer;
}
}
@ -275,7 +281,11 @@ $nav-font-height: 16px;
#editDescription {
width: 100%;
height: 280px;
height: 240px;
}
#editCustomCSS {
font-family: 'Courier New', Courier, monospace;
}
}
@ -339,13 +349,12 @@ $nav-font-height: 16px;
bottom: $general-padding;
right: $general-padding;
max-width: 300px;
z-index: 15;
.message {
position: relative;
display: block;
padding: $general-padding ($general-padding * 2) $general-padding $general-padding;
background-color: $light;
border: $border;
border-radius: 5px;
margin-bottom: 5px;
@ -353,10 +362,6 @@ $nav-font-height: 16px;
margin-bottom: 0;
}
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
position: absolute;
top: 5px;
@ -364,6 +369,7 @@ $nav-font-height: 16px;
font-size: 25px;
line-height: 10px;
cursor: pointer;
text-decoration: none;
&:before {
content: '';
@ -372,7 +378,6 @@ $nav-font-height: 16px;
right: -2px;
width: 20px;
height: 20px;
background-color: #455455;
opacity: 0.5;
transform-origin: center left;
transform: scaleX(0);
@ -391,10 +396,19 @@ $nav-font-height: 16px;
}
#bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 5;
text-align: center;
padding: calc($general-padding / 2);
a {
color: #000000;
text-decoration: none;
}
.separator {
display: inline;
}
}

View File

@ -2,12 +2,4 @@ $font: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubunt
$header-height: 60px;
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #d42932;
$border: 1px solid $dark;
$general-padding: 20px;

View File

@ -1,8 +1,8 @@
@media (max-width: 750px) {
html, body {
font-size: 90%;
line-height: 90%;
font-size: 12pt;
line-height: 1;
}
header {
@ -12,10 +12,11 @@ header {
}
main {
margin-bottom: $general-padding;
#sideColumn {
display: block;
width: 0;
height: block;
margin: 0;
overflow: visible;
}
@ -27,7 +28,7 @@ main {
article {
dl {
padding: 0 ($general-padding / 2);
padding: 0 calc($general-padding / 2);
dd {
margin: 0 ($general-padding * 0.75);

View File

@ -1,21 +1,21 @@
@media (max-width: 750px) {
.tag {
padding: 2px 6px;
font-size: 90%;
line-height: 120%;
// .tag {
// padding: 2px 6px;
// font-size: 90%;
// line-height: 120%;
&.small {
font-size: 70%;
line-height: 100%;
}
}
// &.small {
// font-size: 70%;
// line-height: 100%;
// }
// }
.button {
@extend .tag;
cursor: pointer;
user-select: none;
}
// .button {
// @extend .tag;
// cursor: pointer;
// user-select: none;
// }
.tabs {
li {
@ -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 {

View File

@ -2,7 +2,7 @@
#top {
#title {
font-size: 13pt;
width: 150px;
margin-right: 10px;
}
@ -14,30 +14,28 @@
#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;
background-color: #00de00;
border: $border;
border-radius: 0 3px 3px 0;
color: $white;
font-size: 30px;
line-height: 24px;
font-weight: bold;
@ -51,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 {
@ -63,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);
}
}
@ -84,7 +88,27 @@
}
#editDescription {
width: 100%;
height: 260px;
height: 220px;
}
}
#bottom {
position: relative;
bottom: unset;
.separator {
display: block;
visibility: hidden;
}
}
#bottom {
position: relative;
bottom: unset;
.separator {
display: block;
visibility: hidden;
}
}

265
src/scss/themes/_blue.scss Normal file
View File

@ -0,0 +1,265 @@
#blueTheme {
$dark: #757575;
$mid: #a0a0a0;
$light: #cecece;
$white: #ffffff;
$red: #de0000;
$header-color: #3333ee;
$background-color: #4444ff;
$footer-color: #2222cc;
$link-color: #3333aa;
$button-color: #6868dd;
$message-color: #7777dd;
$word-form-color: #3232bb;
$dictionary-color: #3535cc;
$entry-color: #5252dd;
$input-color: #4444ff;
$details-color: #7777ee;
$modal-color: #4444dd;
$border: 1px solid $dark;
background-color: $background-color;
color: $white;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $white;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $white;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
color: $white;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $white;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $white;
&.red {
background-color: $red;
color: $white;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px #000000;
#title {
#lexi {
fill: lighten($background-color, 25);
}
#conga {
fill: lighten($footer-color, 25);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px #000000;
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px #000000;
a {
color: $white;
}
}
}
@media (max-width: 750px) {
#blueTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $light;
}
}
}

265
src/scss/themes/_dark.scss Normal file
View File

@ -0,0 +1,265 @@
#darkTheme {
$dark: #757575;
$mid: #a0a0a0;
$light: #cecece;
$white: #ffffff;
$red: #de0000;
$header-color: #111111;
$background-color: #222222;
$footer-color: #000000;
$link-color: #bababa;
$button-color: #464646;
$message-color: #555555;
$word-form-color: #101010;
$dictionary-color: #111111;
$entry-color: #303030;
$input-color: #222222;
$details-color: #555555;
$modal-color: #222222;
$border: 1px solid $dark;
background-color: $background-color;
color: $light;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $light;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $light;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
color: $light;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $light;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $light;
&.red {
background-color: $red;
color: $light;
}
}
.modal {
.modal-background {
background-color: $dark;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: none;
#title {
#lexi {
fill: $mid;
}
#conga {
fill: $dark;
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: none;
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: none;
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: none;
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: none;
a {
color: $light;
}
}
}
@media (max-width: 750px) {
#darkTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $light;
}
}
}

View File

@ -0,0 +1,264 @@
#defaultTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$header-color: #eacc9d;
$background-color: #e6cfaa;
$footer-color: #cb6318;
$link-color: #a01000;
$button-color: #dcb078;
$message-color: #c0c088;
$word-form-color: #ba5536;
$dictionary-color: #bd7251;
$entry-color: #d7ad7d;
$input-color: #efdfc0;
$details-color: #f2d5b2;
$modal-color: #f2d5b2;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $white;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: $white;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px rgba(50, 50, 50, 0.75);
#title {
#lexi {
fill: #ff5500;
}
#conga {
fill: #d70000;
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px rgba(50, 50, 50, 0.75);
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#defaultTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $white;
}
}
}

264
src/scss/themes/_grape.scss Normal file
View File

@ -0,0 +1,264 @@
#grapeTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #de0000;
$header-color: #cc9dea;
$background-color: #cfaae6;
$footer-color: #6318cb;
$link-color: #1000a0;
$button-color: #b078dc;
$message-color: #c088c0;
$word-form-color: #5536ba;
$dictionary-color: #7251bd;
$entry-color: #ad7dd7;
$input-color: #dfc0ef;
$details-color: #d5b2f2;
$modal-color: #d5b2f2;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $white;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: $white;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px rgba(50, 50, 50, 0.75);
#title {
#lexi {
fill: darken($background-color, 25);
}
#conga {
fill: darken($footer-color, 25);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px rgba(50, 50, 50, 0.75);
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#grapeTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $white;
}
}
}

265
src/scss/themes/_green.scss Normal file
View File

@ -0,0 +1,265 @@
#greenTheme {
$dark: #757575;
$mid: #a0a0a0;
$light: #cecece;
$white: #ffffff;
$red: #b42032;
$header-color: #55aa33;
$background-color: #70b044;
$footer-color: #559900;
$link-color: #007700;
$button-color: #acee79;
$message-color: #eeee77;
$word-form-color: #54aa32;
$dictionary-color: #57ad23;
$entry-color: #96cf52;
$input-color: #aaee88;
$details-color: #99dd77;
$modal-color: #80cc54;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
color: #000000;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: #000000;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: #000000;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px #000000;
#title {
#lexi {
fill: lighten($background-color, 25);
}
#conga {
fill: darken($footer-color, 10);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px #000000;
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px #000000;
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#greenTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $light;
}
}
}

264
src/scss/themes/_light.scss Normal file
View File

@ -0,0 +1,264 @@
#lightTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #c94557;
$header-color: $mid;
$background-color: $white;
$footer-color: $dark;
$link-color: #0055ff;
$button-color: $light;
$message-color: $mid;
$word-form-color: $mid;
$dictionary-color: $mid;
$entry-color: $light;
$input-color: $white;
$details-color: $white;
$modal-color: $light;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $white;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: $white;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px rgba(50, 50, 50, 0.75);
#title {
#lexi {
fill: darken($dark, 15);
}
#conga {
fill: darken($dark, 50);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px rgba(50, 50, 50, 0.75);
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#lightTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $white;
}
}
}

264
src/scss/themes/_mint.scss Normal file
View File

@ -0,0 +1,264 @@
#mintTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$header-color: #9deacc;
$background-color: #aae6cf;
$footer-color: #18cb63;
$link-color: #00a010;
$button-color: #78dcb0;
$message-color: #88c0c0;
$word-form-color: #36ba55;
$dictionary-color: #51bd72;
$entry-color: #7dd7ad;
$input-color: #c0efdf;
$details-color: #b2f2d5;
$modal-color: #b2f2d5;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $white;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: $white;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px rgba(50, 50, 50, 0.75);
#title {
#lexi {
fill: darken($background-color, 35);
}
#conga {
fill: darken($footer-color, 25);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px rgba(50, 50, 50, 0.75);
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#mintTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $white;
}
}
}

265
src/scss/themes/_red.scss Normal file
View File

@ -0,0 +1,265 @@
#redTheme {
$dark: #757575;
$mid: #a0a0a0;
$light: #cecece;
$white: #ffffff;
$red: #ffffff;
$header-color: #ee3333;
$background-color: #ff4444;
$footer-color: #cc2222;
$link-color: #aa0000;
$button-color: #dd6868;
$message-color: #dd7777;
$word-form-color: #bb3232;
$dictionary-color: #cc3535;
$entry-color: #dd5252;
$input-color: #ff4444;
$details-color: #ee7777;
$modal-color: #dd4444;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
color: #000000;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: #000000;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: #000000;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px #000000;
#title {
#lexi {
fill: lighten($background-color, 25);
}
#conga {
fill: darken($footer-color, 10);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px #000000;
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px #000000;
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#redTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $light;
}
}
}

265
src/scss/themes/_royal.scss Normal file
View File

@ -0,0 +1,265 @@
#royalTheme {
$dark: #757575;
$mid: #a0a0a0;
$light: #cecece;
$white: #ffffff;
$red: #de0000;
$header-color: #aa33ee;
$background-color: #bb44ff;
$footer-color: #8822cc;
$link-color: #ffaaff;
$button-color: #9968dd;
$message-color: #9977dd;
$word-form-color: #7732bb;
$dictionary-color: #8835cc;
$entry-color: #9952dd;
$input-color: #bb44ff;
$details-color: #aa77ee;
$modal-color: #9944dd;
$border: 1px solid $dark;
background-color: $background-color;
color: $white;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $white;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $white;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
color: $white;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: $white;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: $white;
&.red {
background-color: $red;
color: $white;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px #000000;
#title {
#lexi {
fill: lighten($background-color, 25);
}
#conga {
fill: darken($footer-color, 20);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px #000000;
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px #000000;
a {
color: $white;
}
}
}
@media (max-width: 750px) {
#royalTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $light;
}
}
}

View File

@ -0,0 +1,265 @@
#yellowTheme {
$dark: #757575;
$mid: #a0a0a0;
$light: #cecece;
$white: #ffffff;
$red: #de0000;
$header-color: #eeaa33;
$background-color: #ffbb44;
$footer-color: #cc8822;
$link-color: #ffff77;
$button-color: #dd9968;
$message-color: #dd9977;
$word-form-color: #bb7732;
$dictionary-color: #cc8835;
$entry-color: #dd9952;
$input-color: #ffbb44;
$details-color: #eeaa77;
$modal-color: #dd9944;
$border: 1px solid $dark;
background-color: $background-color;
color: #000000;
a {
color: $link-color;
}
p, span {
&.red {
color: $red;
}
}
label {
.label-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
.label-help-button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
text-decoration: none;
}
}
input, textarea, select, option {
background-color: $input-color;
color: #000000;
border: 1px solid $dark;
}
.tag {
border: $border;
background-color: $button-color;
&.red {
background-color: $red;
color: #000000;
}
}
span .tag {
border: $border;
background-color: $button-color;
&+.tag {
background-color: lighten($button-color, 25);
border-left: none;
}
}
.button {
border: 1px solid darken($button-color, 2);
background-color: $button-color;
color: #000000;
&.red {
background-color: $red;
color: #000000;
}
}
.modal {
.modal-background {
background-color: #000000;
}
.modal-content {
background-color: $modal-color;
border: 1px solid darken($modal-color, 10);
}
}
.tabs {
ul {
border-bottom: $border;
}
li {
border-top: $border;
border-left: $border;
border-right: $border;
background-color: $button-color;
&.active {
background-color: $modal-color;
border-bottom: 1px solid $modal-color;
}
}
}
header {
border-bottom: 1px solid $mid;
}
main {
article {
border: $border;
}
}
footer {
border-top: 1px solid $mid;
}
#top {
background-color: $header-color;
border-bottom: 1px solid darken($header-color, 2);
box-shadow: 0px 4px 5px 0px #000000;
#title {
#lexi {
fill: lighten($background-color, 25);
}
#conga {
fill: darken($footer-color, 25);
}
}
#openSearchModal {
cursor: pointer;
}
#searchModal {
.modal-content {
section+footer {
background-color: $modal-color;
}
}
}
}
#wordForm {
background-color: $word-form-color;
border: 1px solid darken($word-form-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#mainColumn {
background-color: $dictionary-color;
border: 1px solid darken($dictionary-color, 2);
box-shadow: 4px 4px 5px 0px #000000;
}
#detailsSection {
nav ul {
li {
border: 1px solid darken($button-color, 20);
background-color: $button-color;
&.active {
background-color: lighten($button-color, 15);
}
}
}
#detailsPanel {
background-color: $details-color;
}
}
.announcement {
background-color: saturate(lighten($footer-color, 20%), 20%);
box-shadow: 4px 4px 5px 0px #000000;
}
.entry {
background-color: $entry-color;
border: 1px solid darken($entry-color, 20);
header {
border-bottom: 1px solid darken($entry-color, 20);
.word-option-button {
background-color: darken($entry-color, 10);
border: 1px solid darken($entry-color, 20);
}
.word-option-list {
background-color: darken($entry-color, 5);
border: 1px solid darken($entry-color, 10);
.word-option {
&:hover {
background-color: lighten($entry-color, 5);
}
}
}
}
.edit-form {
.button {
background-color: darken($button-color, 10);
}
}
}
#messagingSection {
.message {
background-color: $message-color;
border: $border;
&.error {
background-color: lighten($red, 0.75);
}
.close-button {
&:before {
background-color: #455455;
}
}
}
}
#bottom {
background-color: $footer-color;
border-top: 1px solid darken($footer-color, 2);
box-shadow: 0px -4px 5px 0px #000000;
a {
color: #000000;
}
}
}
@media (max-width: 750px) {
#yellowTheme {
$dark: #bababa;
$mid: #dedede;
$light: #efefef;
$white: #ffffff;
$red: #b42032;
$border: 1px solid $dark;
#mobileWordFormShow {
background-color: #00de00;
border: $border;
color: $light;
}
}
}

View File

@ -16,12 +16,13 @@ CREATE TABLE IF NOT EXISTS `dictionaries` (
`allow_duplicates` tinyint(1) NOT NULL DEFAULT 0,
`case_sensitive` tinyint(1) NOT NULL DEFAULT 0,
`sort_by_definition` tinyint(1) NOT NULL DEFAULT 0,
`is_complete` 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,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=500 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
) ENGINE=MyISAM AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DELIMITER $$
CREATE TRIGGER IF NOT EXISTS `delete_dictionary_parts` AFTER DELETE ON `dictionaries` FOR EACH ROW BEGIN
DELETE FROM words WHERE words.dictionary=old.id;
@ -33,16 +34,19 @@ DELIMITER ;
CREATE TABLE IF NOT EXISTS `dictionary_linguistics` (
`dictionary` int(11) NOT NULL,
`parts_of_speech` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'Comma-separated',
`consonants` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`vowels` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`blends` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Space-separated',
`onset` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
`nucleus` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
`coda` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'Comma-separated',
`exceptions` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`orthography_notes` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`grammar_notes` text COLLATE utf8_unicode_ci NOT NULL COMMENT 'Markdown',
`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',
`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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -62,13 +66,13 @@ CREATE TABLE IF NOT EXISTS `users` (
`public_name` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Someone',
`current_dictionary` int(11) DEFAULT NULL,
`allow_email` tinyint(1) NOT NULL DEFAULT 1,
`last_login` int(11) DEFAULT NULL,
`last_login` datetime DEFAULT NULL,
`password_reset_code` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`password_reset_date` int(11) DEFAULT NULL,
`created_on` int(11) NOT NULL,
`password_reset_date` timestamp NULL DEFAULT NULL,
`created_on` datetime DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=200 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
) ENGINE=MyISAM AUTO_INCREMENT=500 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DELIMITER $$
CREATE TRIGGER IF NOT EXISTS `Delete_User_Dictionaries` AFTER DELETE ON `users` FOR EACH ROW DELETE FROM dictionaries WHERE dictionaries.user = old.id
$$
@ -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;

View File

@ -1,2 +0,0 @@
// https://parceljs.org/code_splitting.html
export function aaa () {console.log('aaa');};

465
template-index.html Normal file
View File

@ -0,0 +1,465 @@
<!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>Lexiconga</title>
<meta name="description" content="The quick and easy dictionary builder for constructed languages.">
<meta name="keywords" content="conlanging, dictionary, dictionaries, lexicon, conlangs, constructed languages, glossopoeia, builder, app, tool">
<meta property="og:url" content="https://lexicon.ga/">
<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/social.jpg">
<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="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<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">
{{imported_from_http}}
<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">
<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>
<input id="openSearchModal" title="Open Search Panel" placeholder="🔍&#xFE0E; Search"> <span id="searchResults"></span>
<section id="searchModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<label>Search Term
<input id="searchBox" placeholder="Search term">
</label>
<a id="searchButton" class="small button">Search</a>
<a id="clearSearchButton" class="small red button">Clear</a>
<a class="small button" onclick="var options=document.getElementById('searchOptions').style;options.display=options.display=='block'?'none':'block';">
Toggle Options
</a>
</section>
<footer id="searchOptions" style="display:none;">
<div class="split">
<div class="quarter category">
<h3>Search For</h3>
</div>
<div class="three-quarter options">
<label>Case-Sensitive
<input type="checkbox" id="searchCaseSensitive">
</label>
<label>Ignore Diacritics/Accents
<input type="checkbox" id="searchIgnoreDiacritics">
</label>
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
<label>Translations
<input type="checkbox" id="searchOrthography">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include in Search</h3>
</div>
<div class="three-quarter options">
<label>Word Name
<input type="checkbox" checked id="searchIncludeName">
</label>
<label>Definition
<input type="checkbox" checked id="searchIncludeDefinition">
</label>
<label>Details
<input type="checkbox" checked id="searchIncludeDetails">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include Only</h3>
</div>
<div class="three-quarter options" id="searchPartsOfSpeech"></div>
</div>
</footer>
</div>
</section>
<div id="headerMenu">
<a id="settingsButton" class="button">Settings</a>
<a id="loginCreateAccountButton" class="button">Log In&nbsp;/ Create Account</a>
</div>
<div style="clear:both;"></div>
</header>
<main>
<aside id="sideColumn">
<div id="mobileWordFormShow">+</div>
<form id="wordForm">
<label>Word<span class="red">*</span><br>
<input id="wordName" maxlength="200">
</label>
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
<input id="wordPronunciation" class="ipa-field" maxlength="200"><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
</label>
<label>Part of Speech<br>
<select id="wordPartOfSpeech" class="part-of-speech-select"></select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition" maxlength="2500" placeholder="Equivalent words">
</label>
<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>
</aside>
<section id="mainColumn">
{{announcements}}
<section id="detailsSection">
<h1 id="dictionaryName">Dictionary Name</h1>
<nav>
<ul>
<li>Description</li><li>Details</li><li>Stats</li><li id="editDictionaryButton">Edit</li>
</ul>
</nav>
<article id="detailsPanel" style="display:none;">
<p>The dictionary details</p>
</article>
</section>
<section class="pagination"></section>
<section id="entries">
<article class="entry">
<header>
<h4 class="word">Loading Words</h4>
</header>
<dl>
<dt class="definition">Please Wait...</dt>
</dl>
</article>
</section>
<section class="pagination"></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="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>
<section id="settingsModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<form class="split two">
<div>
<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
Pronunciation fields.</small>
</label>
<label>Use Hotkeys
<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">
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
<option value="red">Red</option>
<option value="royal">Royal</option>
<option value="mint">Mint</option>
<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>
</form>
</section>
<footer>
<a class="button" id="settingsSave">Save</a>
<a class="button" id="settingsSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
<section id="editModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<nav class="tabs">
<ul>
<li class="active">Description</li><li>Details</li><li>Settings</li><li>Actions</li>
</ul>
</nav>
<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>
</label>
</section>
<section id="editDetailsTab" style="display:none;">
<label>Parts of Speech <small>(Comma Separated List)</small><br>
<input id="editPartsOfSpeech" maxlength="2500" placeholder="Noun,Adjective,Verb">
</label>
<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>&nbsp;
<small>Leave blank for default (case-insensitive ASCII/Unicode sorting)</small>
</label>
<h2>Phonology</h2>
<div class="split three">
<div>
<label>Consonants<br>
<small>(Space separated list)</small><br>
<input id="editConsonants" class="ipa-field" maxlength="100" placeholder="p b m n t ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Vowels<br>
<small>(Space separated list)</small><br>
<input id="editVowels" class="ipa-field" maxlength="100" placeholder="æ u e ɪ ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Polyphthongs&nbsp;/ Blends<br>
<small>(Space separated list)</small><br>
<input id="editBlends" class="ipa-field" maxlength="100" placeholder="ai ou ue ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
</div>
<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>
<small>(Comma separated list)</small><br>
<input id="editOnset" maxlength="100" placeholder="Consonants,Vowels">
</label>
</div>
<div>
<label>Nucleus<br>
<small>(Comma separated list)</small><br>
<input id="editNucleus" maxlength="100" placeholder="Vowels,Blends">
</label>
</div>
<div>
<label>Coda<br>
<small>(Comma separated list)</small><br>
<input id="editCoda" maxlength="100" placeholder="Any">
</label>
</div>
</div>
<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>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editOrthography"></textarea>
</label>
<h2>Grammar</h2>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editGrammar"></textarea>
</label>
</section>
<section id="editSettingsTab" style="display:none;">
<label>Prevent Duplicate Words
<input type="checkbox" id="editPreventDuplicates"><br>
<small>Checking this box will prevent the creation of words with the exact same spelling.</small>
</label>
<label>Words are Case-Sensitive
<input type="checkbox" id="editCaseSensitive"><br>
<small>Checking this box will allow the creation of words with the exact same spelling if their capitalization is different.</small>
</label>
<label>Sort by Definition
<input type="checkbox" id="editSortByDefinition"><br>
<small>Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.</small>
</label>
<label>Theme
<select id="editTheme">
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
<option value="red">Red</option>
<option value="royal">Royal</option>
<option value="mint">Mint</option>
<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;">
<h2>Import&nbsp;/ Export</h2>
<div class="split two">
<div>
<p>
<label class="button">Import JSON <input type="file" id="importDictionaryFile" accept="application/json, .dict"><br>
<small>Import a previously-exported <code>JSON</code> file.</small>
</label>
</p>
<p>
<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,%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>
<p>
<a class="button" id="exportDictionaryButton">Export JSON</a><br>
<small>Export your work as a <code>JSON</code> file to re-import later.</small>
</p>
<p>
<a class="button" id="exportWordsButton">Export Words</a><br>
<small>Export a CSV file of your words.</small>
</p>
</div>
</div>
<p>
<a class="red button" id="deleteDictionaryButton">Delete Dictionary</a><br>
<small>This will permanently delete your current dictionary, and it will not be possible to return it if you have not backed it up!</small>
</p>
</section>
<footer>
<a class="button" id="editSave">Save</a>
<a class="button" id="editSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section>
<div id="messagingSection"></div>
{{upup_insert}}
</body>
</html>

View File

@ -0,0 +1,55 @@
<!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>Password Reset | Lexiconga</title>
<link rel="icon" href="processedImages/favicon.png" type="image/x-icon" />
<link rel="stylesheet" href="src/main.scss" />
<script src="src/js/account/passwordReset.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">
<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">Reset your Password</h2>
<article id="detailsPanel" style="max-height:unset;">
{{content}}
</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>

164
template-view.html Normal file
View File

@ -0,0 +1,164 @@
<!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>{{dict_name}} | Lexiconga</title>
<meta property="description" content="A Lexiconga dictionary by {{public_name}}">
<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="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/social.jpg">
<meta name="twitter:card" content="summary">
<meta name="twitter:image:alt" content="Lexiconga logo">
<script>window.currentDictionary = JSON.parse('{{dict_json}}');</script>
<link rel="icon" href="processedImages/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="src/main.scss">
<script src="src/js/view/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">
<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>
<input id="openSearchModal" title="Open Search Panel" placeholder="🔍&#xFE0E; Search"> <span id="searchResults"></span>
<section id="searchModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<label>Search Term
<input id="searchBox" placeholder="Search term">
</label>
<a id="searchButton" class="small button">Search</a>
<a id="clearSearchButton" class="small red button">Clear</a>
<a class="small button" onclick="var options=document.getElementById('searchOptions').style;options.display=options.display=='block'?'none':'block';">
Toggle Options
</a>
</section>
<footer id="searchOptions" style="display:none;">
<div class="split">
<div class="quarter category">
<h3>Search For</h3>
</div>
<div class="three-quarter options">
<label>Case-Sensitive
<input type="checkbox" id="searchCaseSensitive">
</label>
<label>Ignore Diacritics/Accents
<input type="checkbox" id="searchIgnoreDiacritics">
</label>
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
<label>Translations
<input type="checkbox" id="searchOrthography">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include in Search</h3>
</div>
<div class="three-quarter options">
<label>Word Name
<input type="checkbox" checked id="searchIncludeName">
</label>
<label>Definition
<input type="checkbox" checked id="searchIncludeDefinition">
</label>
<label>Details
<input type="checkbox" checked id="searchIncludeDetails">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include Only</h3>
</div>
<div class="three-quarter options" id="searchPartsOfSpeech"></div>
</div>
</footer>
</div>
</section>
<div style="clear:both;"></div>
</header>
<main>
<section id="mainColumn">
<section id="detailsSection">
<a id="dictionaryShare" href="./" class="button" title="Link to Dictionary" style="float:right;">&#10150;</a>
<h2 id="dictionaryName">{{dict_name}}</h2>
<h4>Created by {{public_name}}</h4>
<nav>
<ul>
<li>Description</li><li>Details</li><li>Stats</li>
</ul>
</nav>
<article id="detailsPanel" style="display:none;">
<p>Loading Dictionary Details</p>
</article>
</section>
<section class="pagination"></section>
<section id="entries">
<article class="entry">
<header>
<h4 class="word">Loading Words</h4>
</header>
<dl>
<dt class="definition">Please Wait...</dt>
</dl>
</article>
</section>
<section class="pagination"></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="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>
<div id="messagingSection"></div>
</body>
</html>

340
view.html
View File

@ -1,340 +0,0 @@
<!DOCTYPE html>
<html>
<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>{{dict_name}} | Lexiconga</title>
<meta property="og:url" content="http://lexicon.ga/{{dict}}" />
<meta property="og:type" content="article" />
<meta property="og:title" content="{{dict_name}}" />
<meta property="og:description" content="A Lexiconga dictionary by {{public_name}}" />
<!--meta property="og:image" content="http://lexicon.ga/images/logo.svg" /-->
<script>window.currentDictionary = JSON.parse('{{dict_json}}');</script>
<script src="src/js/view/index.js"></script>
</head>
<body>
<header id="top">
<h1 id="title">Lexiconga</h1>
<input id="openSearchModal" placeholder="🔍&#xFE0E; Search"> <span id="searchResults"></span>
<section id="searchModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<label>Search Term
<input id="searchBox" placeholder="Search term">
</label>
<a id="searchButton" class="small button">Search</a>
<a id="clearSearchButton" class="small red button">Clear</a>
<a class="small button" onclick="var options=document.getElementById('searchOptions').style;options.display=options.display=='block'?'none':'block';">
Toggle Options
</a>
</section>
<footer id="searchOptions" style="display:none;">
<div class="split">
<div class="quarter category">
<h3>Search For</h3>
</div>
<div class="three-quarter options">
<label>Case-Sensitive
<input type="checkbox" id="searchCaseSensitive">
</label>
<label>Ignore Diacritics/Accents
<input type="checkbox" id="searchIgnoreDiacritics">
</label>
<label>Exact Words
<input type="checkbox" id="searchExactWords">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include in Search</h3>
</div>
<div class="three-quarter options">
<label>Word Name
<input type="checkbox" checked id="searchIncludeName">
</label>
<label>Definition
<input type="checkbox" checked id="searchIncludeDefinition">
</label>
<label>Details
<input type="checkbox" checked id="searchIncludeDetails">
</label>
</div>
</div>
<div class="split">
<div class="quarter category">
<h3>Include Only</h3>
</div>
<div class="three-quarter options" id="searchPartsOfSpeech"></div>
</div>
</footer>
</div>
</section>
<!-- div id="headerMenu">
<a id="settingsButton" class="button">Settings</a>
<a id="loginCreateAccountButton" class="button">Log In&nbsp;/ Create Account</a>
</div -->
<div style="clear:both;"></div>
</header>
<main>
<!--aside id="sideColumn">
<div id="mobileWordFormShow">+</div>
<form id="wordForm">
<label>Word<span class="red">*</span><br>
<input id="wordName" maxlength="200">
</label>
<label>Pronunciation<a class="label-button ipa-table-button">IPA Chart</a><br>
<input id="wordPronunciation" class="ipa-field" maxlength="200"><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
</label>
<label>Part of Speech<br>
<select id="wordPartOfSpeech" class="part-of-speech-select"></select>
</label>
<label>Definition<span class="red">*</span><br>
<input id="wordDefinition" maxlength="2500" placeholder="Equivalent words">
</label>
<label>Details<span class="red">*</span><a class="label-button maximize-button">Maximize</a><br>
<textarea id="wordDetails" placeholder="Markdown formatting allowed"></textarea>
</label>
<div id="wordErrorMessage"></div>
<a class="button" id="addWordButton">Add Word</a>
</form>
</aside -->
<section id="mainColumn">
<section id="detailsSection">
<h2 id="dictionaryName">Dictionary Name</h2>
<nav>
<ul>
<li>Description</li><li>Details</li><li>Stats</li><!-- li id="editDictionaryButton">Edit</li -->
</ul>
</nav>
<article id="detailsPanel" style="display:none;">
<p>The dictionary details</p>
</article>
</section>
<section class="pagination"></section>
<section id="entries">
<article class="entry">
<header>
<h4 class="word">Loading Words</h4>
</header>
<dl>
<dt class="definition">Please Wait...</dt>
</dl>
</article>
</section>
<section class="pagination"></section>
</section>
</main>
<footer id="bottom">
<a href="https://buymeacoff.ee/robbieantenesse" target="_blank" class="small button">Support Lexiconga</a>
<a href="https://blog.lexicon.ga" target="_blank" class="small button">Blog</a>
<a href="https://github.com/Alamantus/Lexiconga/issues" target="_blank" class="small button">Issues</a>
<a href="https://github.com/Alamantus/Lexiconga/releases" target="_blank" class="small button">Updates</a>
|
<a class="button" id="helpInfoButton">Help</a>
<a class="button" id="termsInfoButton">Terms</a>
<a class="button" id="privacyInfoButton">Privacy</a>
</footer>
<!-- section id="settingsModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<section>
<form class="split two">
<div>
<h3>General Settings</h3>
<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
Pronunciation fields.</small>
</label>
<label>Use Hotkeys
<input id="settingsUseHotkeys" type="checkbox" checked><br />
<small>Check this to enable keyboard combinations to perform different helpful actions.</small>
</label>
<label>Theme
<select disabled>
<option selected value="default">Default</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="royal">Royal</option>
</select>
</label>
<div id="accountSettings"></div>
</div>
<div id="accountActions"></div>
</form>
</section>
<footer>
<a class="button" id="settingsSave">Save</a>
<a class="button" id="settingsSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section -->
<!-- section id="editModal" class="modal" style="display:none;">
<div class="modal-background" onclick="this.parentElement.style.display='none';"></div>
<div class="modal-content">
<a class="close-button" onclick="this.parentElement.parentElement.style.display='none';">&times;&#xFE0E;</a>
<nav class="tabs">
<ul>
<li class="active">Description</li><li>Details</li><li>Settings</li><li>Actions</li>
</ul>
</nav>
<section id="editDescriptionTab">
<label>Name<br>
<input id="editName" maxlength="50">
</label>
<label>Specification<br>
<input id="editSpecification" maxlength="50">
</label>
<label>Description<a class="label-button maximize-button">Maximize</a><br>
<textarea id="editDescription"></textarea>
</label>
</section>
<section id="editDetailsTab" style="display:none;">
<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>
<h3>Phonology</h3>
<div class="split three">
<div>
<label>Consonants<br>
<small>(Space separated list)</small><br>
<input id="editConsonants" class="ipa-field" maxlength="100" placeholder="p b m n t ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Vowels<br>
<small>(Space separated list)</small><br>
<input id="editVowels" class="ipa-field" maxlength="100" placeholder="æ u e ɪ ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
<div>
<label>Polyphthongs&nbsp;/ Blends<br>
<small>(Space separated list)</small><br>
<input id="editBlends" class="ipa-field" maxlength="100" placeholder="ai ou ue ..."><br>
<a class="label-help-button ipa-field-help-button">Field Help</a>
<a class="label-button ipa-table-button">IPA Chart</a>
</label>
</div>
</div>
<h3>Phonotactics</h3>
<div class="split three">
<div>
<label>Onset<br>
<small>(Comma separated list)</small><br>
<input id="editOnset" maxlength="100" placeholder="Consonants,Vowels">
</label>
</div>
<div>
<label>Nucleus<br>
<small>(Comma separated list)</small><br>
<input id="editNucleus" maxlength="100" placeholder="Vowels,Blends">
</label>
</div>
<div>
<label>Coda<br>
<small>(Comma separated list)</small><br>
<input id="editCoda" maxlength="100" placeholder="Any">
</label>
</div>
</div>
<label>Exceptions <small>(Markdown-enabled)</small><br>
<textarea id="editExceptions"></textarea>
</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>
<label>Notes <small>(Markdown-enabled)</small><a class="label-button maximize-button">Maximize</a><br>
<textarea id="editGrammar"></textarea>
</label>
</section>
<section id="editSettingsTab" style="display:none;">
<label>Prevent Duplicate Words
<input type="checkbox" id="editPreventDuplicates"><br>
<small>Checking this box will prevent the creation of words with the exact same spelling.</small>
</label>
<label>Words are Case-Sensitive
<input type="checkbox" id="editCaseSensitive"><br>
<small>Checking this box will allow the creation of words with the exact same spelling if their capitalization is different.</small>
</label>
<label>Sort by Definition
<input type="checkbox" id="editSortByDefinition"><br>
<small>Checking this box will sort the words in alphabetical order based on the Definition instead of the Word.</small>
</label>
</section>
<section id="editActionsTab" style="display:none;">
<h3>Import&nbsp;/ Export</h3>
<div class="split two">
<div>
<p>
<label class="button">Import JSON <input type="file" id="importDictionaryFile" accept="application/json, .dict"><br>
<small>Import a previously-exported <code>JSON</code> file.</small>
</label>
</p>
<p>
<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>
</p>
</div>
<div>
<p>
<a class="button" id="exportDictionaryButton">Export JSON</a><br>
<small>Export your work as a <code>JSON</code> file to re-import later.</small>
</p>
<p>
<a class="button" id="exportWordsButton">Export Words</a><br>
<small>Export a CSV file of your words.</small>
</p>
</div>
</div>
<p>
<a class="red button" id="deleteDictionaryButton">Delete Dictionary</a><br>
<small>This will permanently delete your current dictionary, and it will not be possible to return it if you have not backed it up!</small>
</p>
</section>
<footer>
<a class="button" id="editSave">Save</a>
<a class="button" id="editSaveAndClose">Save &amp; Close</a>
<a class="red button" onclick="this.parentElement.parentElement.parentElement.style.display='none';">Close Without Saving</a>
</footer>
</div>
</section -->
<div id="messagingSection"></div>
</body>
</html>

4787
yarn.lock

File diff suppressed because it is too large Load Diff