Compare commits

..

No commits in common. "f9f4c79ca2b5e4cd28c8020186ebc22673a63c87" and "c2cb1f861d751fe54240682492b60d533f4e39da" have entirely different histories.

17 changed files with 158 additions and 409 deletions

View File

@ -1,7 +1,6 @@
use std::fs; use std::fs;
use std::fs::File; use std::io::ErrorKind;
use std::collections::HashSet; use std::process::exit;
use std::fmt;
use reqwest; use reqwest;
use rusqlite::{Connection, Transaction, ErrorCode}; use rusqlite::{Connection, Transaction, ErrorCode};
@ -14,83 +13,29 @@ use serde_json;
use crate::language::Language; use crate::language::Language;
use crate::entry::{WiktionaryEntries, WiktionaryEntry}; use crate::entry::{WiktionaryEntries, WiktionaryEntry};
use crate::entry::Form; use crate::entry::Form;
use crate::{DB_DIR, CACHE_DIR, MAJOR, MINOR, PATCH}; use crate::{MAJOR, MINOR, PATCH};
use crate::util;
pub enum DbError { const DB_DIR: &str = "/usr/share/inflectived/";
AccessDenied, const CACHE_DIR: &str = "/var/cache/";
}
impl fmt::Display for DbError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DbError::AccessDenied => write!(f, "Access denied"),
}
}
}
/// A database of Wiktionary entries /// A database of Wiktionary entries
pub struct WordDb { pub struct WordDb {
db_path: String, db_path: String
pub installed_langs: Vec<Language>,
pub installable_langs: Vec<Language>
} }
impl WordDb { impl WordDb {
pub fn new(db_name: &str) -> Self { pub fn new(db_name: &str) -> Self {
let db_path = format!("{}/{}", DB_DIR, db_name); let mut db_path = String::from(DB_DIR);
db_path.push_str(db_name);
let conn = Connection::open(&db_path).unwrap(); Self { db_path }
let mut installed_langs: Vec<Language> = Vec::new();
let statement = conn.prepare(
"SELECT code, name, major, minor, patch
FROM langs"
);
if let Ok(mut statement) = statement {
let mut rows = statement.query([]).unwrap();
while let Some(row) = rows.next().unwrap() {
installed_langs.push(Language::from_row(&row));
}
installed_langs.sort();
}
Self {
db_path,
installed_langs,
installable_langs: Language::list_langs(),
}
} }
pub fn connect(&self) -> Connection { pub fn connect(&self) -> Connection {
Connection::open(&self.db_path).unwrap() Connection::open(&self.db_path).unwrap()
} }
pub fn list_available(&self) -> String { pub fn clean_tables(&mut self, lang: &Language) {
let mut list = String::new();
for lang in &self.installable_langs {
list.push_str(&format!(" - {} ({})\n", &lang.name, &lang.code));
}
list
}
pub fn get_lang(&self, code: &str) -> Option<Language> {
for lang in &self.installable_langs {
if lang.code == code {
return Some(lang.clone())
}
}
None
}
pub fn clean_tables(&mut self, lang: &Language) -> Result<(), DbError> {
let mut conn = self.connect(); let mut conn = self.connect();
let transaction = conn.transaction().unwrap(); let transaction = conn.transaction().unwrap();
@ -105,8 +50,10 @@ impl WordDb {
)", []) { )", []) {
match e { match e {
SqliteFailure(f, _) => match f.code { SqliteFailure(f, _) => match f.code {
ErrorCode::ReadOnly => { ErrorCode::ReadOnly => {
return Err(DbError::AccessDenied) eprintln!("Could not write to database: Permission denied");
eprintln!("Please run as root");
exit(1);
}, },
_ => panic!("{}", e) _ => panic!("{}", e)
}, },
@ -114,18 +61,7 @@ impl WordDb {
} }
} }
if let Err(e) = transaction.execute( transaction.execute("DELETE FROM langs WHERE code = ?", [&lang.code]).unwrap();
"DELETE FROM langs WHERE code = ?", [&lang.code]) {
match e {
SqliteFailure(f, _) => match f.code {
ErrorCode::ReadOnly => {
return Err(DbError::AccessDenied)
},
_ => panic!("{}", e)
},
_ => panic!("{}", e)
}
};
transaction.execute(&format!("DROP TABLE IF EXISTS {0}_words", &lang.code), []).unwrap(); transaction.execute(&format!("DROP TABLE IF EXISTS {0}_words", &lang.code), []).unwrap();
transaction.execute(&format!("DROP TABLE IF EXISTS {0}_types", &lang.code), []).unwrap(); transaction.execute(&format!("DROP TABLE IF EXISTS {0}_types", &lang.code), []).unwrap();
@ -136,6 +72,14 @@ impl WordDb {
name TINYTEXT UNIQUE NOT NULL name TINYTEXT UNIQUE NOT NULL
)", &lang.code), []).unwrap(); )", &lang.code), []).unwrap();
for type_ in &lang.types {
transaction.execute(&format!("
INSERT INTO {0}_types ( name )
VALUES (
?
)", &lang.code), [type_]).unwrap();
}
transaction.execute(&format!(" transaction.execute(&format!("
CREATE TABLE {0}_words ( CREATE TABLE {0}_words (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -147,13 +91,16 @@ impl WordDb {
)", &lang.code), []).unwrap(); )", &lang.code), []).unwrap();
transaction.execute(&format!(" transaction.execute(&format!("
CREATE INDEX {0}_word_index CREATE INDEX word_index
ON {0}_words (word) ON {0}_words (word)
", &lang.code), []).unwrap(); ", &lang.code), []).unwrap();
transaction.commit().unwrap(); transaction.execute("
INSERT INTO langs (code, name, major, minor, patch)
VALUES (?, ?, ?, ?, ?)
", params![&lang.code, &lang.name, MAJOR, MINOR, PATCH]).unwrap();
Ok(()) transaction.commit().unwrap();
} }
pub fn insert_entry(&self, transaction: &Transaction, lang: &Language, entry: &WiktionaryEntry) { pub fn insert_entry(&self, transaction: &Transaction, lang: &Language, entry: &WiktionaryEntry) {
@ -164,7 +111,7 @@ impl WordDb {
(SELECT id FROM {0}_types WHERE name = ?) (SELECT id FROM {0}_types WHERE name = ?)
)", &lang.code), )", &lang.code),
params![entry.word, params![entry.word,
entry.unparsed_json, entry.parsed_json.to_string(),
entry.type_] entry.type_]
).unwrap(); ).unwrap();
} }
@ -195,7 +142,7 @@ impl WordDb {
).unwrap(); ).unwrap();
for entry in entries.iter() { for entry in entries.iter() {
if let Some(forms) = entry.parse_json()["forms"].as_array() { if let Some(forms) = entry.parsed_json["forms"].as_array() {
let mut forms_vec: Vec<Form> = Vec::new(); let mut forms_vec: Vec<Form> = Vec::new();
for form in forms { for form in forms {
@ -222,10 +169,7 @@ impl WordDb {
let mut senses: Vec<Value> = Vec::new(); let mut senses: Vec<Value> = Vec::new();
for form in forms { for form in forms {
let mut tags = match &form.tags { let mut tags = form.tags.clone();
Some(tags) => tags.clone(),
None => Vec::new()
};
tags.push(String::from("form-of")); tags.push(String::from("form-of"));
tags.push(String::from("auto-generated")); tags.push(String::from("auto-generated"));
@ -236,10 +180,7 @@ impl WordDb {
} }
], ],
"glosses": [ "glosses": [
match &form.tags { form.tags.join(" ")
Some(tags) => tags.join(" "),
None => String::from("")
}
], ],
"tags": tags "tags": tags
})); }));
@ -253,7 +194,7 @@ impl WordDb {
let new_entry = WiktionaryEntry::new(forms[0].form.clone(), let new_entry = WiktionaryEntry::new(forms[0].form.clone(),
entry.type_.clone(), entry.type_.clone(),
entry_json.to_string()); entry_json);
self.insert_entry(&transaction, lang, &new_entry); self.insert_entry(&transaction, lang, &new_entry);
} }
@ -265,84 +206,57 @@ impl WordDb {
transaction.commit().unwrap(); transaction.commit().unwrap();
} }
fn insert_types(&mut self, lang: &Language, entries: &WiktionaryEntries) { fn try_create_dir(&self, dir: &str) {
let mut conn = self.connect(); match fs::create_dir(dir) {
let transaction = conn.transaction().unwrap(); Err(e) => match e.kind() {
ErrorKind::AlreadyExists => {},
let mut types = HashSet::new(); _ => panic!("{}", e)
},
for entry in entries.iter() { _ => {}
types.insert(&entry.type_);
} }
for type_ in types {
transaction.execute(&format!("
INSERT INTO {0}_types ( name )
VALUES (?)", &lang.code), [type_]).unwrap();
}
transaction.commit().unwrap();
} }
fn insert_version(&mut self, lang: &Language) { pub async fn upgrade_lang(&mut self, lang: &Language) {
let mut conn = self.connect(); self.try_create_dir(DB_DIR);
let transaction = conn.transaction().unwrap();
transaction.execute("
INSERT INTO langs (code, name, major, minor, patch)
VALUES (?, ?, ?, ?, ?)
", params![&lang.code, &lang.name, MAJOR, MINOR, PATCH]).unwrap();
transaction.commit().unwrap();
}
pub async fn upgrade_lang(&mut self, lang: &Language) -> Result<(), DbError> {
util::try_create_dir(DB_DIR);
println!("Trying to read cached data..."); println!("Trying to read cached data...");
let cache_file = format!("{}/{}.json", CACHE_DIR, &lang.name); let mut cache_file = String::from(CACHE_DIR);
cache_file.push_str("Polish.json");
let mut cached_data = File::open(&cache_file); let cached_data = fs::read_to_string(&cache_file);
let mut request = None; let mut request = None;
if let Err(_) = cached_data { if let Err(_) = cached_data {
let url = format!("https://kaikki.org/dictionary/{0}/kaikki.org-dictionary-{0}.json", request = Some(reqwest::get("https://kaikki.org/dictionary/Polish/kaikki.org-dictionary-Polish.json"));
&lang.name);
request = Some(reqwest::get(url));
} }
println!("Cleaning tables..."); println!("Cleaning tables...");
self.clean_tables(lang)?; self.clean_tables(lang);
let data;
if let Some(request) = request { if let Some(request) = request {
// Actually, the request was sent before // Actually, the request was sent before
println!("Requesting data..."); println!("Requesting data...");
let data = request.await.unwrap().text().await.unwrap(); data = request.await.unwrap().text().await.unwrap();
if cfg!(unix) {
println!("Caching data..."); println!("Caching data...");
util::try_create_dir(CACHE_DIR); self.try_create_dir(CACHE_DIR);
fs::write(&cache_file, &data).unwrap(); fs::write(&cache_file, &data).unwrap();
}
cached_data = File::open(&cache_file); }
else {
data = cached_data.unwrap();
} }
println!("Parsing data..."); println!("Parsing data...");
let entries = WiktionaryEntries::parse_data(cached_data.unwrap()); let entries = WiktionaryEntries::parse_data(data);
println!("Inserting types..."); println!("Inserting data...");
self.insert_types(lang, &entries);
println!("Inserting entries...");
self.insert_entries(lang, &entries); self.insert_entries(lang, &entries);
println!("Generating \"form-of\" entries..."); println!("Generating \"form-of\" entries...");
self.generate_entries(lang, &entries); self.generate_entries(lang, &entries);
println!("Inserting version...");
self.insert_version(lang);
println!("Done"); println!("Done");
Ok(())
} }
} }

View File

@ -1,5 +1,3 @@
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::cmp; use std::cmp;
use std::slice::Iter; use std::slice::Iter;
use serde_json::Value; use serde_json::Value;
@ -9,7 +7,7 @@ use serde::Deserialize;
pub struct WiktionaryEntry { pub struct WiktionaryEntry {
pub word: String, pub word: String,
pub type_: String, pub type_: String,
pub unparsed_json: String pub parsed_json: Value,
} }
impl cmp::PartialEq for WiktionaryEntry { impl cmp::PartialEq for WiktionaryEntry {
@ -34,8 +32,6 @@ impl cmp::Ord for WiktionaryEntry {
impl WiktionaryEntry { impl WiktionaryEntry {
pub fn parse(unparsed_json: &str) -> Self { pub fn parse(unparsed_json: &str) -> Self {
// We could keep this in memory, but for bigger language databases
// it's going to crash the program
let json: Value = serde_json::from_str(unparsed_json).unwrap(); let json: Value = serde_json::from_str(unparsed_json).unwrap();
let word = String::from(json["word"].as_str().unwrap()); let word = String::from(json["word"].as_str().unwrap());
@ -44,33 +40,27 @@ impl WiktionaryEntry {
Self { Self {
word, word,
type_, type_,
unparsed_json: String::from(unparsed_json) parsed_json: json
} }
} }
pub fn new(word: String, type_: String, unparsed_json: String) -> Self { pub fn new(word: String, type_: String, parsed_json: Value) -> Self {
Self { Self {
word, word,
type_, type_,
unparsed_json parsed_json
} }
} }
pub fn parse_json(&self) -> Value {
serde_json::from_str(&self.unparsed_json).unwrap()
}
} }
pub struct WiktionaryEntries(Vec<WiktionaryEntry>); pub struct WiktionaryEntries(Vec<WiktionaryEntry>);
impl WiktionaryEntries { impl WiktionaryEntries {
pub fn parse_data(data: File) -> Self { pub fn parse_data(data: String) -> Self {
let reader = BufReader::new(data);
let mut entries: Vec<WiktionaryEntry> = Vec::new(); let mut entries: Vec<WiktionaryEntry> = Vec::new();
for line in reader.lines() { for line in data.lines() {
entries.push(WiktionaryEntry::parse(&line.unwrap())); entries.push(WiktionaryEntry::parse(line));
} }
Self(entries) Self(entries)
@ -84,7 +74,7 @@ impl WiktionaryEntries {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Form { pub struct Form {
pub form: String, pub form: String,
pub tags: Option<Vec<String>>, pub tags: Vec<String>,
pub source: Option<String>, pub source: Option<String>,
} }

View File

@ -1,69 +1,16 @@
use std::cmp::{PartialEq, PartialOrd, Ordering}; #[derive(Debug)]
use rusqlite::Row;
use serde::Serialize;
use crate::version::Version;
#[derive(Serialize, Debug, Clone)]
pub struct Language { pub struct Language {
pub code: String, // ISO 639-2 pub code: String,
pub name: String, // English name pub name: String,
pub version: Option<Version> pub types: Vec<String>
} }
impl Language { impl Language {
pub fn new(code: &str, name: &str) -> Self { pub fn new(code: &str, name: &str, types: Vec<String>) -> Self {
Self { Self {
code: String::from(code), code: String::from(code),
name: String::from(name), name: String::from(name),
version: None types
} }
} }
pub fn from_row(row: &Row) -> Self {
Self {
code: row.get(0).unwrap(),
name: row.get(1).unwrap(),
version: Some(Version(row.get(2).unwrap(),
row.get(3).unwrap(),
row.get(4).unwrap()))
}
}
pub fn list_langs() -> Vec<Self> {
// Keep this list sorted by name
let langs = vec![
Self::new("eng", "English"),
Self::new("fre", "French"),
Self::new("ger", "German"),
Self::new("ita", "Italian"),
Self::new("pol", "Polish"),
Self::new("por", "Portuguese"),
Self::new("rus", "Russian"),
Self::new("spa", "Spanish"),
];
langs
}
}
impl PartialEq for Language {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Language {}
impl Ord for Language {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for Language {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.name.cmp(&other.name))
}
} }

View File

@ -1,26 +1,18 @@
#![feature(slice_group_by, path_try_exists)] #![feature(slice_group_by)]
use std::process::exit;
use std::fs;
//mod database; //mod database;
use rocket::routes; use rocket::routes;
use rocket::fs::FileServer; use rocket::fs::FileServer;
use clap::{App, AppSettings, Arg, SubCommand}; use clap::{App, AppSettings, Arg, SubCommand};
//use database::WordDb; //use database::WordDb;
mod database; mod database;
mod language; mod language;
mod entry; mod entry;
mod views; mod views;
mod version;
mod util;
use database::{WordDb, DbError}; use database::WordDb;
use language::Language;
const DB_DIR: &str = "/usr/share/inflectived";
const CACHE_DIR: &str = "/var/cache/inflectived";
const FRONTEND_DIR: &str = "/opt/inflectived";
const MAJOR: i32 = 0; const MAJOR: i32 = 0;
const MINOR: i32 = 1; const MINOR: i32 = 1;
@ -64,42 +56,46 @@ async fn main() {
let mut db = WordDb::new("inflectived.db"); let mut db = WordDb::new("inflectived.db");
let lang = Language::new("pl",
"Polish",
vec![String::from("adj"),
String::from("noun"),
String::from("verb"),
String::from("character"),
String::from("suffix"),
String::from("prefix"),
String::from("conj"),
String::from("adv"),
String::from("infix"),
String::from("name"),
String::from("phrase"),
String::from("prep_phrase"),
String::from("intj"),
String::from("det"),
String::from("prep"),
String::from("proverb"),
String::from("abbrev"),
String::from("num"),
String::from("pron"),
String::from("punct"),
String::from("interfix"),
String::from("particle")]);
match matches.subcommand() { match matches.subcommand() {
("upgrade", matches) => { ("upgrade", _) => { db.upgrade_lang(&lang).await; },
let lang = db.get_lang(matches.unwrap().value_of("LANG").unwrap()); ("run", _) => {
if let None = lang {
eprintln!("The requested language is not available.");
eprintln!("Available languages:");
eprint!("{}", db.list_available());
exit(1);
}
if let Err(e) = db.upgrade_lang(&lang.unwrap()).await {
match e {
DbError::AccessDenied => {
eprintln!("Permission denied. Please run as root.");
exit(1);
}
}
}
},
("run", _matches) => {
let figment = rocket::Config::figment() let figment = rocket::Config::figment()
.merge(("address", "0.0.0.0")); .merge(("address", "0.0.0.0"));
let mut app = rocket::custom(figment) rocket::custom(figment)
.manage(db) .manage(db)
.mount("/", routes![views::get_entries, .mount("/static", FileServer::from("static/"))
views::get_entries_like, .mount("/", routes![views::get_entries,
views::get_langs, views::get_entries_like,
views::frontend]); views::get_langs,
views::frontend])
if let Ok(_) = fs::try_exists(FRONTEND_DIR) { .launch()
app = app.mount("/static", FileServer::from(FRONTEND_DIR)); .await.unwrap();
}
app.launch().await.unwrap();
}, },
_ => {} _ => {}
} }

View File

@ -1,12 +0,0 @@
use std::fs;
use std::io::ErrorKind;
pub fn try_create_dir(dir: &str) {
match fs::create_dir(dir) {
Err(e) => match e.kind() {
ErrorKind::AlreadyExists => {},
_ => panic!("{}", e)
},
_ => {}
}
}

View File

@ -1,36 +0,0 @@
use std::cmp::{PartialEq, PartialOrd, Ordering};
use serde::Serialize;
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct Version(pub u32, pub u32, pub u32);
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.0 > other.0 {
return Some(Ordering::Greater);
}
if self.0 < other.0 {
return Some(Ordering::Less);
}
if self.1 > other.1 {
return Some(Ordering::Greater);
}
if self.1 < other.1 {
return Some(Ordering::Less);
}
if self.2 > other.2 {
return Some(Ordering::Greater);
}
if self.2 < other.2 {
return Some(Ordering::Less);
}
Some(Ordering::Equal)
}
}

View File

@ -8,14 +8,12 @@ use rocket::serde::json::Json;
use rusqlite::params; use rusqlite::params;
use crate::database::WordDb; use crate::database::WordDb;
use crate::language::Language;
use crate::FRONTEND_DIR;
#[get("/")] #[get("/")]
pub fn frontend() -> content::Html<String> { pub fn frontend() -> Option<content::Html<String>> {
match fs::read_to_string(&format!("{}/{}", FRONTEND_DIR, "index.html")) { match fs::read_to_string("static/index.html") {
Ok(file) => content::Html(file), Ok(file) => Some(content::Html(file)),
Err(_) => content::Html(String::from("<h1>No web frontend installed.</h1>")) Err(_) => None
} }
} }
@ -74,16 +72,18 @@ pub fn get_entries_like(db: &State<WordDb>, lang: &str, like: &str, limit: usize
} }
#[get("/langs?<installed>")] #[get("/langs?<installed>")]
pub fn get_langs(db: &State<WordDb>, installed: bool) -> Json<Vec<Language>> { pub fn get_langs(db: &State<WordDb>, installed: bool) -> Json<Vec<String>> {
let mut langs: Vec<Language> = Vec::new(); let conn = db.connect();
let mut langs: Vec<String> = Vec::new();
if installed { if installed {
for lang in &db.installed_langs { let mut statement = conn.prepare("SELECT name FROM langs").unwrap();
langs.push(lang.clone())
} let mut rows = statement.query([]).unwrap();
} else {
for lang in &db.installable_langs { while let Some(row) = rows.next().unwrap() {
langs.push(lang.clone()) langs.push(row.get(0).unwrap());
} }
} }

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="icon" href="/static/favicon.png"> <link rel="icon" href="/static/favicon.png">
<link rel="stylesheet" href="static/jquery-ui-1.13.0/jquery-ui.min.css"> <link rel="stylesheet" href="static/jquery-ui-1.13.0/jquery-ui.min.css">
<link rel="stylesheet" href="static/jquery-ui-1.13.0/jquery-ui.theme.min.css"> <link rel="stylesheet" href="static/jquery-ui-1.13.0/jquery-ui.theme.min.css">
@ -19,9 +19,9 @@
<div class="ui-widget" id="search-widget"> <div class="ui-widget" id="search-widget">
<input id="search-bar" placeholder="Search..." type="text" autocorrect="off" autocapitalize="none" autofocus> <input id="search-bar" placeholder="Search..." type="text" autocorrect="off" autocapitalize="none" autofocus>
</div> </div>
<select id="langs"> <button type="submit" class="ui-button ui-widget" id="search">
</select> <span class="ui-icon ui-icon-search"></span>
<!--<button id="menu"></button>--> </button>
</form> </form>
<div class="container" id="ajax-content"> <div class="container" id="ajax-content">
</div> </div>

View File

@ -1,64 +1,27 @@
$(document).ready(() => { $(document).ready(() => {
let selectedLang = null; let polishSchemas = null;
let schema = null;
let langs = null;
$.ajax({ $.ajax({
url: `/langs?installed`, url: '/static/schemas/polish.json',
success: data => { success: data => {
langs = data; polishSchemas = data
if(window.location.hash) {
const selectedLangCode = localStorage.selectedLangCode; getWord();
}
let options = '';
langs.forEach(lang => {
if(selectedLangCode && lang.code == selectedLangCode) {
options += `<option value="${lang.code}" selected>${lang.name}</option>`;
} else {
options += `<option value="${lang.code}">${lang.name}</option>`;
}
});
$('#langs').html(options);
setLang($('#langs').val());
} }
}); });
$('#langs').on('change', e => {
setLang(e.target.value);
});
function setLang(code) {
const lang = langs.find(lang => lang.code == code);
localStorage.selectedLangCode = code;
selectedLang = lang;
$.ajax({
url: `/static/schemas/${lang.name}.json`,
success: data => {
polishSchemas = data
if(window.location.hash) {
getWord();
}
}
});
}
const searchBar = $('#search-bar');
const searchForm = $('#search-form');
const ajaxContent = $('#ajax-content');
window.onhashchange = () => { window.onhashchange = () => {
getWord(); getWord();
}; };
const searchBar = $('#search-bar');
searchBar.autocomplete({ searchBar.autocomplete({
appendTo: '#search-form', appendTo: '#search-form',
source: (request, response) => { source: (request, response) => {
$.ajax({ $.ajax({
url: `/langs/${selectedLang.code}/words?like=${request.term}&limit=20&offset=0`, url: '/langs/pl/words?like=' + request.term + '&limit=20&offset=0',
success: data => response(data) success: data => response(data)
}) })
}, },
@ -69,39 +32,33 @@ $(document).ready(() => {
setTimeout(() => e.currentTarget.select(), 100); setTimeout(() => e.currentTarget.select(), 100);
}); });
searchForm.on('submit', e => { $('#search-form').on('submit', e => {
e.preventDefault(); e.preventDefault();
const word = e.target[0].value let word = e.target[0].value
window.location.hash = `#${word}`; window.location.hash = `#${word}`;
}); });
function getWord() { function getWord() {
const word = window.location.hash.replace('#', ''); let word = window.location.hash.replace('#', '');
if (word) { $.ajax({
document.title = `Inflective - ${decodeURIComponent(word)}`; url: '/langs/pl/words/' + word,
$.ajax({ success: (data) => {
url: `/langs/${selectedLang.code}/words/${word}`, $('#ajax-content').html(generateHtml(word, data))
},
success: (data) => { error: err => console.error(err)
ajaxContent.html(generateHtml(word, data)); })
},
error: err => console.error(err) window.scrollTo(0, 0);
}) searchBar.select();
searchBar.autocomplete('close');
window.scrollTo(0, 0); // Sometimes autocomplete opens after close was called
searchBar.select(); // A better fix should be made
searchBar.autocomplete('close'); setTimeout(() => searchBar.autocomplete('close'), 1000);
// Sometimes autocomplete opens after close was called
// A better fix should be made
setTimeout(() => searchBar.autocomplete('close'), 1000);
} else {
ajaxContent.html('');
}
} }
function getCells(forms, tags) { function getCells(forms, tags) {

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[] Italian.json

View File

@ -1 +0,0 @@
[]

View File

@ -385,12 +385,12 @@
{ {
"colspan": 1, "colspan": 1,
"rowspan": 1, "rowspan": 1,
"tags": ["masculine", "animate", "singular", "accusative"] "tags": ["masculine", "inanimate", "singular", "accusative"]
}, },
{ {
"colspan": 1, "colspan": 1,
"rowspan": 1, "rowspan": 1,
"tags": ["masculine", "inanimate", "singular", "accusative"] "tags": ["masculine", "animate", "singular", "accusative"]
}, },
{ {
"colspan": 1, "colspan": 1,