├── .gitignore ├── composer.json ├── composer.lock ├── docker-compose.yml.example ├── docker ├── nginx │ ├── Dockerfile │ └── aprelendo.conf └── php │ ├── Dockerfile │ ├── error-reporting.ini │ ├── uploads.ini │ └── xdebug.ini ├── license.md ├── readme.md ├── src ├── Includes │ ├── Classes │ │ ├── AIBot.php │ │ ├── Achievements.php │ │ ├── ArchivedTexts.php │ │ ├── AudioFile.php │ │ ├── AudioPlayer.php │ │ ├── AudioPlayerForEbooks.php │ │ ├── AudioPlayerForTexts.php │ │ ├── Card.php │ │ ├── Connect.php │ │ ├── Conversion.php │ │ ├── Curl.php │ │ ├── DBEntity.php │ │ ├── Dictionaries.php │ │ ├── EbookFile.php │ │ ├── EmailSender.php │ │ ├── ExampleSentences.php │ │ ├── File.php │ │ ├── Gems.php │ │ ├── InternalException.php │ │ ├── Language.php │ │ ├── Likes.php │ │ ├── Log.php │ │ ├── LogAudioStreams.php │ │ ├── LogFileUploads.php │ │ ├── Pagination.php │ │ ├── PopularSources.php │ │ ├── Preferences.php │ │ ├── RSSFeed.php │ │ ├── Reader.php │ │ ├── ReportedTexts.php │ │ ├── SM2.php │ │ ├── SearchParameters.php │ │ ├── SearchTextsParameters.php │ │ ├── SearchWordsParameters.php │ │ ├── SecureEncryption.php │ │ ├── SharedTextTable.php │ │ ├── SharedTexts.php │ │ ├── Table.php │ │ ├── TextTable.php │ │ ├── Texts.php │ │ ├── TextsUtilities.php │ │ ├── Token.php │ │ ├── Url.php │ │ ├── User.php │ │ ├── UserAuth.php │ │ ├── UserException.php │ │ ├── UserPassword.php │ │ ├── UserRegistrationManager.php │ │ ├── Videos.php │ │ ├── VoiceRSS.php │ │ ├── WordDailyGoal.php │ │ ├── WordFrequency.php │ │ ├── WordStats.php │ │ ├── WordTable.php │ │ ├── Words.php │ │ └── WordsUtilities.php │ ├── checklogin.php │ └── dbinit.php ├── config │ ├── aprelendo-schema.sql │ └── config-example.php └── public │ ├── 401.php │ ├── 403.php │ ├── 404.php │ ├── 500.php │ ├── aboutus.php │ ├── accountactivation.php │ ├── achievementsmodal.php │ ├── activitymonitor.php │ ├── addebook.php │ ├── addrss.php │ ├── addtext.php │ ├── addvideo.php │ ├── ajax │ ├── addtext.php │ ├── addword.php │ ├── archivetext.php │ ├── contact.php │ ├── deleteaccount.php │ ├── ebookposition.php │ ├── editlanguage.php │ ├── exportwords.php │ ├── fetchaudiostream.php │ ├── fetchrssfeeds.php │ ├── fetchurl.php │ ├── fetchvideo.php │ ├── forgotpassword.php │ ├── getaireply.php │ ├── getcards.php │ ├── getdicuris.php │ ├── getebook.php │ ├── getebookaudio.php │ ├── getstats.php │ ├── getuserwords.php │ ├── getwordfreq.php │ ├── google_oauth.php │ ├── login.php │ ├── register.php │ ├── removetext.php │ ├── removeword.php │ ├── reporttext.php │ ├── savepreferences.php │ ├── saveuserprofile.php │ ├── togglelike.php │ ├── updatecard.php │ └── updateuserscore.php │ ├── attributions.php │ ├── contact.php │ ├── css │ ├── 401.css │ ├── 401.min.css │ ├── 403.css │ ├── 403.min.css │ ├── 404.css │ ├── 404.min.css │ ├── 500.css │ ├── 500.min.css │ ├── ebooks.css │ ├── ebooks.min.css │ ├── styles.css │ └── styles.min.css │ ├── donate.php │ ├── editlanguage.php │ ├── eucookies.php │ ├── exampledics.php │ ├── extensions.php │ ├── footer.php │ ├── forgotpassword.php │ ├── head.php │ ├── header.php │ ├── img │ ├── avatar-pablo.jpg │ ├── backgrounds │ │ ├── pattern-wallpaper.png │ │ └── welcome-page-background.jpg │ ├── email-logo.png │ ├── favicons │ │ ├── apple-touch-icon.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── favicon.svg │ │ ├── site.webmanifest │ │ ├── web-app-manifest-192x192.png │ │ └── web-app-manifest-512x512.png │ ├── flags │ │ ├── ar.svg │ │ ├── bg.svg │ │ ├── ca.svg │ │ ├── cs.svg │ │ ├── da.svg │ │ ├── de.svg │ │ ├── el.svg │ │ ├── en.svg │ │ ├── es.svg │ │ ├── fr.svg │ │ ├── he.svg │ │ ├── hi.svg │ │ ├── hr.svg │ │ ├── hu.svg │ │ ├── it.svg │ │ ├── ja.svg │ │ ├── ko.svg │ │ ├── nl.svg │ │ ├── no.svg │ │ ├── pl.svg │ │ ├── pt.svg │ │ ├── ro.svg │ │ ├── ru.svg │ │ ├── sk.svg │ │ ├── sl.svg │ │ ├── sv.svg │ │ ├── tr.svg │ │ ├── un.svg │ │ ├── vi.svg │ │ └── zh.svg │ ├── gamification │ │ ├── achievements │ │ │ ├── gems-100.png │ │ │ ├── gems-100k.png │ │ │ ├── gems-10k.png │ │ │ ├── gems-1k.png │ │ │ ├── gems-1m.png │ │ │ ├── gems-200.png │ │ │ ├── gems-200k.png │ │ │ ├── gems-25k.png │ │ │ ├── gems-3k.png │ │ │ ├── gems-500.png │ │ │ ├── gems-500k.png │ │ │ ├── gems-50k.png │ │ │ ├── gems-5k.png │ │ │ ├── streak-1095.png │ │ │ ├── streak-14.png │ │ │ ├── streak-180.png │ │ │ ├── streak-1825.png │ │ │ ├── streak-2.png │ │ │ ├── streak-30.png │ │ │ ├── streak-365.png │ │ │ ├── streak-7.png │ │ │ ├── streak-730.png │ │ │ ├── streak-90.png │ │ │ ├── words-100.png │ │ │ ├── words-10k.png │ │ │ ├── words-12k.png │ │ │ ├── words-15k.png │ │ │ ├── words-17k.png │ │ │ ├── words-1k.png │ │ │ ├── words-20k.png │ │ │ ├── words-25k.png │ │ │ ├── words-2k.png │ │ │ ├── words-3k.png │ │ │ ├── words-4k.png │ │ │ ├── words-500.png │ │ │ ├── words-5k.png │ │ │ └── words-7k.png │ │ ├── daily-goal-streak.png │ │ ├── gems.png │ │ ├── streak.png │ │ └── words-today.png │ ├── logo-long.png │ ├── logo.png │ └── other │ │ ├── cookie-dude.gif │ │ ├── immersion-banner.jpg │ │ └── reading-banner.jpg │ ├── index.php │ ├── js │ ├── achievementsmodal.js │ ├── achievementsmodal.min.js │ ├── actionbtns.js │ ├── actionbtns.min.js │ ├── addebook.js │ ├── addebook.min.js │ ├── addrss.js │ ├── addrss.min.js │ ├── addtext.js │ ├── addtext.min.js │ ├── addvideo.js │ ├── addvideo.min.js │ ├── audioplayer.js │ ├── audioplayer.min.js │ ├── contact.js │ ├── contact.min.js │ ├── cookies.js │ ├── cookies.min.js │ ├── dictation.js │ ├── dictation.min.js │ ├── dictionaries.js │ ├── dictionaries.min.js │ ├── epubjs │ │ └── epub.min.js │ ├── eucookies.js │ ├── eucookies.min.js │ ├── forgotpassword.js │ ├── forgotpassword.min.js │ ├── helpers.js │ ├── helpers.min.js │ ├── languages.js │ ├── languages.min.js │ ├── likes.js │ ├── likes.min.js │ ├── listtexts.js │ ├── listtexts.min.js │ ├── listwords.js │ ├── listwords.min.js │ ├── login.js │ ├── login.min.js │ ├── matomo.js │ ├── matomo.min.js │ ├── password.js │ ├── password.min.js │ ├── preferences.js │ ├── preferences.min.js │ ├── register.js │ ├── register.min.js │ ├── showaibotmodal.js │ ├── showaibotmodal.min.js │ ├── showebook.js │ ├── showebook.min.js │ ├── showimportwordsmodal.js │ ├── showimportwordsmodal.min.js │ ├── showofflinevideo.js │ ├── showofflinevideo.min.js │ ├── showreadersettingsmodal.js │ ├── showreadersettingsmodal.min.js │ ├── showreporttextmodal.js │ ├── showreporttextmodal.min.js │ ├── showtext.js │ ├── showtext.min.js │ ├── showvideo.js │ ├── showvideo.min.js │ ├── stats.js │ ├── stats.min.js │ ├── study.js │ ├── study.min.js │ ├── subtitles-parser │ │ ├── subtitles.parser.js │ │ └── subtitles.parser.min.js │ ├── tooltips.js │ ├── tooltips.min.js │ ├── underlinewords.js │ ├── underlinewords.min.js │ ├── userprofile.js │ ├── userprofile.min.js │ ├── videoplayer.js │ ├── videoplayer.min.js │ ├── wordselection.js │ ├── wordselection.min.js │ ├── ytvideoplayer.js │ └── ytvideoplayer.min.js │ ├── languages.php │ ├── listlanguages.php │ ├── listsharedtexts.php │ ├── listsources.php │ ├── listtexts.php │ ├── listwords.php │ ├── login.php │ ├── logout.php │ ├── preferences.php │ ├── privacy.php │ ├── register.php │ ├── sharedtexts.php │ ├── showaibotmodal.php │ ├── showdicactionmenu.php │ ├── showebook.php │ ├── showimportwordsmodal.php │ ├── showofflinevideo.php │ ├── showreadersettingsmodal.php │ ├── showreporttextmodal.php │ ├── showtext.php │ ├── showvideo.php │ ├── simpleheader.php │ ├── sources.php │ ├── stats.php │ ├── study.php │ ├── termsofservice.php │ ├── texts.php │ ├── textstats.php │ ├── totalreading.php │ ├── userprofile.php │ └── words.php └── templates ├── password-reset.html └── welcome.html /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | vendor/ 3 | src/config/config.php 4 | src/public/tests/ 5 | uploads/* 6 | todo.md 7 | .rsyncignore 8 | **/*.log 9 | docker-compose.yml -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "phpmailer/phpmailer": "^6.9" 4 | }, 5 | "autoload": { 6 | "psr-4": { 7 | "Aprelendo\\":"src/Includes/Classes/" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docker-compose.yml.example: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | container_name: aprelendo_nginx 4 | build: 5 | context: . 6 | dockerfile: docker/nginx/Dockerfile 7 | ports: 8 | - 80:80 9 | - 443:443 10 | volumes: 11 | - ./docker/nginx/aprelendo.conf:/etc/nginx/conf.d/aprelendo.conf 12 | - .:/var/www/aprelendo 13 | php: 14 | container_name: aprelendo_php 15 | build: 16 | context: . 17 | dockerfile: docker/php/Dockerfile 18 | extra_hosts: 19 | - "host.docker.internal:host-gateway" 20 | volumes: 21 | #- .:/var/www/aprelendo ## only use this if on development env 22 | - ./docker/php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini 23 | - ./docker/php/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini 24 | - ./docker/php/error-reporting.ini:/usr/local/etc/php/conf.d/error-reporting.ini 25 | mysql: 26 | image: mariadb:latest 27 | container_name: aprelendo_db 28 | environment: 29 | MYSQL_ROOT_PASSWORD: 'root_password' ## change as necessary 30 | MYSQL_USER: 'aprelendo_user' ## change and keep the same as in src/config/config.php 31 | MYSQL_PASSWORD: 'aprelendo_user_password' ## change and keep the same as in src/config/config.php 32 | MYSQL_DATABASE: 'aprelendo' 33 | volumes: 34 | - mysqldata:/var/lib/mysql 35 | ports: 36 | - 3306:3306 37 | phpmyadmin: 38 | image: phpmyadmin/phpmyadmin 39 | container_name: phpmyadmin 40 | restart: always 41 | links: 42 | - mysql 43 | environment: 44 | PMA_HOST: mysql 45 | PMA_PORT: 3306 46 | MYSQL_ROOT_PASSWORD: 'root_password' ## change as necessary 47 | UPLOAD_LIMIT: 64M 48 | ports: 49 | - 8081:80 50 | volumes: 51 | mysqldata: {} 52 | -------------------------------------------------------------------------------- /docker/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | RUN openssl req -x509 -nodes -days 365 \ 4 | -subj "/C=CA/ST=ON/O=Aprelendo/CN=aprelendo.com" \ 5 | -newkey rsa:2048 -keyout /etc/nginx/cert.key \ 6 | -out /etc/nginx/cert.pem; 7 | -------------------------------------------------------------------------------- /docker/nginx/aprelendo.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl http2; 3 | listen [::]:443 ssl http2; 4 | 5 | #listen 80 default_server; 6 | 7 | client_max_body_size 20M; 8 | 9 | ## SSL self-signed certificate 10 | ssl_certificate /etc/nginx/cert.pem; 11 | ssl_certificate_key /etc/nginx/cert.key; 12 | 13 | # Add headers to serve security related headers 14 | # Before enabling Strict-Transport-Security headers please read into this 15 | # topic first. 16 | #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;"; 17 | #add_header X-Content-Type-Options nosniff; 18 | #add_header X-XSS-Protection "1; mode=block"; 19 | #add_header X-Robots-Tag none; 20 | #add_header X-Download-Options noopen; 21 | #add_header X-Permitted-Cross-Domain-Policies none; 22 | #add_header Referrer-Policy "no-referrer"; 23 | 24 | charset UTF-8; 25 | charset_types text/xml text/plain text/vnd.wap.wml application/x-javascript image/svg+xml application/rss+xml text/css application/javascript application/json; 26 | 27 | root /var/www/aprelendo/src/public; 28 | 29 | index index.php index.html index.htm; 30 | 31 | error_page 401 /401; 32 | error_page 403 /403; 33 | error_page 404 /404; 34 | error_page 500 502 503 504 /500; 35 | 36 | location ~ \.php$ { 37 | fastcgi_pass php:9000; 38 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 39 | include fastcgi_params; 40 | fastcgi_intercept_errors on; 41 | } 42 | 43 | location / { 44 | try_files $uri $uri.html $uri/ @extensionless-php; 45 | index index.php; 46 | } 47 | 48 | location @extensionless-php { 49 | rewrite ^(.*)$ $1.php last; 50 | } 51 | } 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:fpm-alpine 2 | 3 | RUN apk add --no-cache autoconf gcc g++ linux-headers make python3 py3-pip python3-dev shadow 4 | 5 | # Set working directory 6 | WORKDIR /opt/venv 7 | 8 | # Create and activate virtual environment 9 | ENV VIRT_ENV=/opt/venv 10 | RUN python3 -m venv "$VIRT_ENV" 11 | ENV PATH="$VIRT_ENV/bin:$PATH" 12 | 13 | # Upgrade pip and setuptools within the virtual environment 14 | RUN source bin/activate && pip install --upgrade pip setuptools && deactivate 15 | 16 | # Install youtube_transcript_api within the virtual environment 17 | RUN source bin/activate && pip install youtube_transcript_api && deactivate 18 | 19 | # Install PHP extensions 20 | RUN docker-php-ext-install pdo pdo_mysql 21 | RUN pecl install xdebug && docker-php-ext-enable xdebug 22 | 23 | # Install Composer 24 | COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer 25 | 26 | # Set the working directory 27 | WORKDIR /var/www/aprelendo/ 28 | 29 | # Copy the composer.json and composer.lock files to the working directory 30 | COPY composer.json composer.lock /var/www/aprelendo/ 31 | 32 | # Install the PHP dependencies 33 | RUN composer install --no-scripts --no-autoloader 34 | 35 | # Make sure www-data group and user have ID 33, same as in Ubuntu. 36 | RUN groupmod -g 33 www-data && \ 37 | usermod -u 33 -g 33 www-data 38 | 39 | # Copy the rest of the application code 40 | COPY --chown=www-data:www-data . /var/www/aprelendo/ 41 | 42 | # Ensure correct permissions again after copy 43 | RUN chown -R www-data:www-data /var/www/aprelendo/ 44 | 45 | # Run the autoloader 46 | RUN composer dump-autoload --optimize 47 | -------------------------------------------------------------------------------- /docker/php/error-reporting.ini: -------------------------------------------------------------------------------- 1 | error_reporting=E_ALL 2 | -------------------------------------------------------------------------------- /docker/php/uploads.ini: -------------------------------------------------------------------------------- 1 | file_uploads = On 2 | memory_limit = 64M 3 | upload_max_filesize = 64M 4 | post_max_size = 64M 5 | max_execution_time = 600 6 | -------------------------------------------------------------------------------- /docker/php/xdebug.ini: -------------------------------------------------------------------------------- 1 | zend_extension=xdebug 2 | 3 | [xdebug] 4 | xdebug.mode=debug 5 | xdebug.client_host=host.docker.internal 6 | -------------------------------------------------------------------------------- /src/Includes/Classes/ArchivedTexts.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class ArchivedTexts extends Texts 24 | { 25 | /** 26 | * Constructor 27 | * 28 | * @param \PDO $pdo 29 | * @param int $user_id 30 | * @param int $lang_id 31 | */ 32 | public function __construct(\PDO $pdo, int $user_id, int $lang_id) 33 | { 34 | parent::__construct($pdo, $user_id, $lang_id); 35 | $this->table = 'archived_texts'; 36 | } // end __construct() 37 | 38 | /** 39 | * Unarchives text by using their $ids 40 | * 41 | * @param array $text_ids 42 | * @return void 43 | */ 44 | public function unarchive(array $text_ids): void 45 | { 46 | $id_params = str_repeat("?,", count($text_ids)-1) . "?"; 47 | 48 | $sql = "INSERT INTO `texts` SELECT * FROM `{$this->table}` WHERE `id` IN ($id_params)"; 49 | $this->sqlExecute($sql, $text_ids); 50 | 51 | $sql = "DELETE FROM `{$this->table}` WHERE `id` IN ($id_params)"; 52 | $this->sqlExecute($sql, $text_ids); 53 | } 54 | } // end unarchive() 55 | -------------------------------------------------------------------------------- /src/Includes/Classes/AudioFile.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class AudioFile extends File 24 | { 25 | /** 26 | * Constructor 27 | * @param string $file_name 28 | */ 29 | public function __construct(string $file_name) 30 | { 31 | parent::__construct($file_name); 32 | $this->allowed_extensions = ['mp3', 'ogg']; 33 | $this->max_size = 2097152; // 2 MB 34 | } // end __construct() 35 | } 36 | -------------------------------------------------------------------------------- /src/Includes/Classes/AudioPlayer.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class AudioPlayer 24 | { 25 | protected string $audio_uri; 26 | 27 | /** 28 | * Constructor 29 | * Calls createFullReader or createMiniReader depending on the nr. of args 30 | */ 31 | public function __construct(string $audio_url) 32 | { 33 | $this->audio_uri = $audio_url; 34 | } // end __construct() 35 | 36 | /** 37 | * Return audio MIME type based on URI file extension 38 | * 39 | * @return string 40 | */ 41 | protected function getAudioMimeType(): string { 42 | // Get file extension 43 | $file_extension = pathinfo($this->audio_uri, PATHINFO_EXTENSION); 44 | 45 | // Map file extensions to audio types 46 | $audio_types = array( 47 | 'mp3' => 'audio/mpeg', 48 | 'ogg' => 'audio/ogg', 49 | 'wav' => 'audio/wav', 50 | 'aac' => 'audio/aac', 51 | 'm4a' => 'audio/x-m4a', 52 | 'webm' => 'audio/webm', 53 | 'flac' => 'audio/flac', 54 | 'opus' => 'audio/opus', 55 | ); 56 | 57 | // Set the appropriate MIME type based on the file extension 58 | return isset($audio_types[$file_extension]) ? $audio_types[$file_extension] : 'audio/mpeg'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Includes/Classes/Conversion.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class Conversion 24 | { 25 | /** 26 | * Converts JSON to CSV 27 | * 28 | * @param string $json in JSON format 29 | * @return string in CSV format 30 | */ 31 | public static function jsonToCsv(string $json) 32 | { 33 | return implode(',', json_decode($json)); 34 | } // end jsonToCsv() 35 | 36 | /** 37 | * Converts Array to CSV 38 | * 39 | * @param array 40 | * @return string in CSV format 41 | */ 42 | public static function arrayToCsv(array $array): string 43 | { 44 | if (is_array($array)) { 45 | return "'" . implode("','", $array) . "'"; 46 | } else { 47 | return "'$array'"; 48 | } 49 | } // end arrayToCsv() 50 | 51 | /** 52 | * Convert an array to XML 53 | * @param array $array 54 | * @param SimpleXMLElement $xml 55 | */ 56 | public static function arrayToXml($array, &$xml) 57 | { 58 | foreach ($array as $key => $value) { 59 | if (is_int($key)) { 60 | $key = "e"; 61 | } 62 | if (is_array($value)) { 63 | $label = $xml->addChild($key); 64 | self::arrayToXml($value, $label); 65 | } else { 66 | $xml->addChild($key, $value); 67 | } 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Includes/Classes/EbookFile.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class EbookFile extends File 24 | { 25 | /** 26 | * Constructor 27 | * @param string $file_name 28 | */ 29 | public function __construct(string $file_name) 30 | { 31 | parent::__construct($file_name); 32 | $this->allowed_extensions = ['epub']; 33 | $this->max_size = 2097152; // 2 MB 34 | } // end __construct() 35 | } 36 | -------------------------------------------------------------------------------- /src/Includes/Classes/EmailSender.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | use PHPMailer\PHPMailer\PHPMailer; 24 | use PHPMailer\PHPMailer\SMTP; 25 | 26 | class EmailSender 27 | { 28 | public $mail; 29 | 30 | /** 31 | * Constructor 32 | */ 33 | public function __construct() 34 | { 35 | $this->mail = new PHPMailer(true); // passing true enables exceptions 36 | $this->mail->CharSet = 'UTF-8'; 37 | $this->mail->SMTPDebug = SMTP::DEBUG_OFF; 38 | $this->mail->isSMTP(); 39 | $this->mail->Host = EMAIL_HOST; 40 | $this->mail->SMTPAuth = true; 41 | $this->mail->Username = EMAIL_SENDER_USERNAME; 42 | $this->mail->Password = EMAIL_SENDER_PASSWORD; 43 | $this->mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; 44 | $this->mail->Port = 587; 45 | 46 | $this->mail->setFrom(EMAIL_SENDER, 'Aprelendo'); 47 | } // end __construct() 48 | } 49 | -------------------------------------------------------------------------------- /src/Includes/Classes/InternalException.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | use Exception; 24 | 25 | class InternalException extends Exception { 26 | public function getJsonError(): string 27 | { 28 | return $this->encodeJsonError('Oops! There was an unexpected error processing your request.'); 29 | } 30 | 31 | protected function encodeJsonError(string $error_msg): string 32 | { 33 | $error = ['error_msg' => $error_msg]; 34 | 35 | // Set the JSON content type header 36 | header('Content-Type: application/json'); 37 | 38 | // Encode the error array as JSON and return it 39 | return json_encode($error); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Includes/Classes/Log.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | abstract class Log extends DBEntity 24 | { 25 | private int $user_id = 0; 26 | 27 | /** 28 | * Constructor 29 | * 30 | * @param \PDO $pdo 31 | * @param int $user_id 32 | * 33 | */ 34 | public function __construct(\PDO $pdo, int $user_id) 35 | { 36 | parent::__construct($pdo); 37 | $this->user_id = $user_id; 38 | } // end __construct() 39 | 40 | /** 41 | * Gets today's records for the current user in the log table 42 | * 43 | * @return int 44 | */ 45 | public function countTodayRecords(): int 46 | { 47 | $sql = "SELECT COUNT(*) AS `exists` 48 | FROM `{$this->table}` 49 | WHERE `user_id` = ? 50 | AND `date_created` = CURRENT_DATE()"; 51 | 52 | $row = $this->sqlFetch($sql, [$this->user_id]); 53 | 54 | return $row['exists']; 55 | } // end countTodayRecords() 56 | 57 | /** 58 | * Adds log record for current user 59 | * 60 | * @return bool 61 | */ 62 | public function addRecord(): void 63 | { 64 | $sql = "INSERT INTO `{$this->table}` (`user_id`, `date_created`) VALUES (?, CURRENT_DATE())"; 65 | $this->sqlExecute($sql, [$this->user_id]); 66 | 67 | $this->purgeOldRecords(); // if successful, purge old records 68 | } // end addRecord() 69 | 70 | /** 71 | * Remove old log records 72 | * 73 | * @return void 74 | */ 75 | private function purgeOldRecords() 76 | { 77 | $sql = "DELETE FROM `{$this->table}` WHERE `date_created` < NOW() - INTERVAL 2 DAY"; 78 | $this->sqlExecute($sql, []); 79 | } // end purgeOldRecords() 80 | } 81 | -------------------------------------------------------------------------------- /src/Includes/Classes/LogAudioStreams.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class LogAudioStreams extends Log 24 | { 25 | /** 26 | * Constructor 27 | * 28 | * @param \PDO $pdo 29 | * @param int $user_id 30 | */ 31 | public function __construct(\PDO $pdo, int $user_id) 32 | { 33 | parent::__construct($pdo, $user_id); 34 | $this->table = 'log_audio_streams'; 35 | } // end __construct() 36 | } 37 | -------------------------------------------------------------------------------- /src/Includes/Classes/LogFileUploads.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class LogFileUploads extends Log 24 | { 25 | /** 26 | * Constructor 27 | * 28 | * @param \PDO $pdo 29 | * @param int $user_id 30 | */ 31 | public function __construct(\PDO $pdo, int $user_id) 32 | { 33 | parent::__construct($pdo, $user_id); 34 | $this->table = 'log_file_uploads'; 35 | } // end __construct() 36 | } 37 | -------------------------------------------------------------------------------- /src/Includes/Classes/SearchParameters.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Aprelendo; 23 | 24 | abstract class SearchParameters 25 | { 26 | public string $search_text; 27 | public int $offset; 28 | public int $limit; 29 | public int $sort_by; 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param string $text 35 | */ 36 | public function __construct( 37 | string $search_text, 38 | int $offset, 39 | int $limit, 40 | int $sort_by 41 | ) { 42 | $this->search_text = $search_text; 43 | $this->offset = $offset; 44 | $this->limit = $limit; 45 | $this->sort_by = $sort_by; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/Includes/Classes/SearchWordsParameters.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Aprelendo; 23 | 24 | class SearchWordsParameters extends SearchParameters 25 | { 26 | /** 27 | * Constructor 28 | * 29 | * @param string $text 30 | */ 31 | public function __construct( 32 | string $search_text, 33 | int $sort_by, 34 | int $offset = 0, 35 | int $limit = 1000000 36 | ) { 37 | parent::__construct($search_text, $offset, $limit, $sort_by); 38 | } 39 | 40 | /** 41 | * Converts sorting patterns selected by user (expressed as an integer value in the sort menu) 42 | * to valid SQL strings 43 | * 44 | * @return string 45 | */ 46 | public function buildSortSQL(): string 47 | { 48 | $result = ''; 49 | switch ($this->sort_by) { 50 | case '0': // new first 51 | $result = '`id` DESC'; 52 | break; 53 | case '1': // old first 54 | $result = '`id`'; 55 | break; 56 | case '2': // learned first 57 | $result = '`status`'; 58 | break; 59 | case '3': // learning first 60 | $result = '`status` DESC'; 61 | break; 62 | case '10': // words first 63 | $result = '`is_phrase` DESC'; 64 | break; 65 | case '11': // phrases first 66 | $result = '`is_phrase`'; 67 | break; 68 | default: 69 | $result = ''; 70 | break; 71 | } 72 | return $result; 73 | } // end buildSortSQL() 74 | } 75 | -------------------------------------------------------------------------------- /src/Includes/Classes/SharedTextTable.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Aprelendo; 23 | 24 | class SharedTextTable extends TextTable 25 | { 26 | /** 27 | * Constructor 28 | * 29 | * @param array $rows 30 | */ 31 | public function __construct(array $rows) 32 | { 33 | parent::__construct($rows, false); 34 | $this->headings = ['Title']; 35 | $this->col_widths = ['69px', '']; 36 | $this->action_menu = []; 37 | $this->sort_menu = [ 38 | 'mSortByNew' => 'New first', 39 | 'mSortByOld' => 'Old first', 40 | 'mSortByMoreLikes' => 'More likes first', 41 | 'mSortByLessLikes' => 'Less likes first' 42 | ]; 43 | $this->is_shared = true; 44 | $this->has_chkbox = false; 45 | } // end __construct() 46 | } 47 | -------------------------------------------------------------------------------- /src/Includes/Classes/UserException.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class UserException extends InternalException { 24 | public function getJsonError(): string { 25 | return $this->encodeJsonError($this->getMessage()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Includes/Classes/UserPassword.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | namespace Aprelendo; 22 | 23 | class UserPassword 24 | { 25 | /** 26 | * Check if $password = $hashed_password 27 | * Meaning the password provided by user is equal to the one stored in the database 28 | * 29 | * @param string $password 30 | * @return boolean 31 | */ 32 | public static function verify(string $password, string $hashed_password): bool 33 | { 34 | try { 35 | if (!password_verify($password, $hashed_password)) { 36 | throw new UserException('Username and password combination is incorrect. Please try again.'); 37 | } 38 | 39 | return true; 40 | } catch (\Exception $e) { 41 | throw new UserException($e->getMessage()); 42 | } 43 | } // end verify() 44 | 45 | /** 46 | * Creates hash for a given password 47 | * 48 | * @param string $password 49 | * @return string 50 | */ 51 | public static function createHash(string $password): string { 52 | return password_hash($password, PASSWORD_BCRYPT, ['cost' => 11]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Includes/Classes/WordsUtilities.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Aprelendo; 23 | 24 | abstract class WordsUtilities 25 | { 26 | /** 27 | * Exports words to a CSV file 28 | * 29 | * It exports either the whole set of words corresponding to a user & language combination, 30 | * or the specific subset that results from applying additional filters (e.g. $search_text). 31 | * Results are ordered using $order_by. 32 | * 33 | * @param SearchWordsParameters $search_params 34 | * @return void 35 | */ 36 | public static function exportToCSV(array $words): void 37 | { 38 | $headers = ['Words', 'Status', 'Freq_Level']; 39 | 40 | $fp = fopen('php://output', 'w'); 41 | 42 | if ($fp) { 43 | header('Content-Type: text/csv'); 44 | header('Content-Disposition: attachment; filename="export.csv"'); 45 | header('Pragma: no-cache'); 46 | header('Expires: 0'); 47 | fputcsv($fp, $headers); 48 | 49 | foreach ($words as $word) { 50 | fputcsv($fp, [$word['word'], $word['status'], $word['freq_level']]); 51 | } 52 | 53 | fclose($fp); 54 | } 55 | } // end exportToCSV() 56 | } 57 | -------------------------------------------------------------------------------- /src/Includes/checklogin.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | use Aprelendo\User; 22 | use Aprelendo\UserAuth; 23 | 24 | if (!isset($user)) { 25 | require_once 'dbinit.php'; // connect to database 26 | 27 | $user = new User($pdo); 28 | $user_auth = new UserAuth($user); 29 | $user_is_logged = $user_auth->isLoggedIn(); 30 | $pdo->exec("SET time_zone='{$user->time_zone}';"); // use user local time zone 31 | 32 | if (!$user_is_logged && !isset($no_redirect)) { 33 | header('Location:/login.php'); 34 | exit; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Includes/dbinit.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | // perform all database initialization here, in a single file 23 | 24 | require_once dirname(dirname(__FILE__)) . '/config/config.php'; 25 | require_once dirname(APP_ROOT) . '/vendor/autoload.php'; 26 | 27 | use Aprelendo\Connect; 28 | 29 | try { 30 | $db_connection = new Connect(DB_DRIVER, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_CHARSET); 31 | $pdo = $db_connection->connect(); 32 | } catch (\Exception $e) { 33 | http_response_code(500); 34 | exit; 35 | } 36 | -------------------------------------------------------------------------------- /src/public/401.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Aprelendo: Learn languages with your favorite texts, ebooks and videos 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |

401

29 |

unauthorized

30 |

You tried to access a protected resource without providing proper authentication credentials.

31 |

home

32 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/public/403.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Aprelendo: Learn languages with your favorite texts, ebooks and videos 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |

403

29 |

forbidden

30 |

The page or resource you were trying to reach is forbidden for some reason.

31 |

home

32 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/public/404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Aprelendo: Learn languages with your favorite texts, ebooks and videos 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |

404

29 |

not found

30 |

The page you are trying to open could not be found on the server.

31 |

home

32 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/public/500.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Aprelendo: Learn languages with your favorite texts, ebooks and videos 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |

500

29 |

internal server error

30 |

The description of this error pretty much says it all ...

31 |

home

32 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/public/ajax/archivetext.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Texts; 30 | use Aprelendo\ArchivedTexts; 31 | use Aprelendo\Language; 32 | use Aprelendo\Words; 33 | use Aprelendo\InternalException; 34 | use Aprelendo\UserException; 35 | 36 | try { 37 | $user_id = $user->id; 38 | $lang_id = $user->lang_id; 39 | 40 | // if text is archived using green button at the end, update learning status of words first 41 | if (isset($_POST['words'])) { 42 | $words_table = new Words($pdo, $user_id, $lang_id); 43 | $words_table->updateByName($_POST['words']); 44 | } 45 | 46 | // if text is not shared, then archive or unarchive text accordingly 47 | if (!empty($_POST['textIDs']) && !empty($_POST['archivetext'])) { 48 | $lang = new Language($pdo, $user_id); 49 | $lang->loadRecordById($user->lang_id); 50 | $text_ids = json_decode($_POST['textIDs']); 51 | 52 | if ($_POST['archivetext'] === 'true') { //archive text 53 | $texts_table = new Texts($pdo, $user_id, $lang_id); 54 | $texts_table->archive($text_ids); 55 | } else { // unarchive text 56 | $texts_table = new ArchivedTexts($pdo, $user_id, $lang_id); 57 | $texts_table->unarchive($text_ids); 58 | } 59 | } 60 | } catch (InternalException | UserException $e) { 61 | echo $e->getJsonError(); 62 | } 63 | -------------------------------------------------------------------------------- /src/public/ajax/deleteaccount.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\InternalException; 30 | use Aprelendo\UserException; 31 | 32 | try { 33 | $user->delete(); 34 | } catch (InternalException | UserException $e) { 35 | echo $e->getJsonError(); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/public/ajax/ebookposition.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Texts; 25 | use Aprelendo\InternalException; 26 | use Aprelendo\UserException; 27 | 28 | try { 29 | if (!empty($_POST['mode'])) { 30 | $text = new Texts($pdo, $user->id, $user->lang_id); 31 | 32 | if ($_POST['mode'] == "GET") { 33 | $text->loadRecord($_POST['id']); 34 | 35 | $result['audio_pos'] = $text->audio_pos; 36 | $result['text_pos'] = $text->text_pos; 37 | 38 | echo json_encode($result); 39 | } elseif ($_POST['mode'] == "SAVE") { 40 | $result['audio_pos'] = !empty($_POST['audio_pos']) ? $_POST['audio_pos'] : null; 41 | $result['text_pos'] = !empty($_POST['text_pos']) ? $_POST['text_pos'] : null; 42 | 43 | $text->update($_POST['id'], $result); 44 | } 45 | } 46 | } catch (InternalException | UserException $e) { 47 | echo $e->getJsonError(); 48 | } 49 | -------------------------------------------------------------------------------- /src/public/ajax/editlanguage.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Language; 30 | use Aprelendo\InternalException; 31 | use Aprelendo\UserException; 32 | 33 | try { 34 | if (isset($_POST['id'])) { 35 | $lang = new Language($pdo, $user->id); 36 | $lang->loadRecordById($_POST['id']); 37 | $lang->editRecord($_POST); 38 | } 39 | } catch (InternalException | UserException $e) { 40 | echo $e->getJsonError(); 41 | } 42 | -------------------------------------------------------------------------------- /src/public/ajax/exportwords.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Words; 25 | use Aprelendo\SearchWordsParameters; 26 | use Aprelendo\WordsUtilities; 27 | use Aprelendo\InternalException; 28 | use Aprelendo\UserException; 29 | 30 | $user_id = $user->id; 31 | $lang_id = $user->lang_id; 32 | 33 | try { 34 | // set search criteria, if any 35 | $search_text = isset($_GET['s']) ? $_GET['s'] : ''; 36 | $sort_by = isset($_GET['o']) ? $_GET['o'] : 0; 37 | 38 | // export to csv 39 | $words_table = new Words($pdo, $user_id, $lang_id); 40 | $search_params = new SearchWordsParameters($search_text, $sort_by); 41 | $words = $words_table->search($search_params); 42 | 43 | WordsUtilities::exportToCSV($words); 44 | 45 | } catch (UserException $e) { 46 | http_response_code($e->getCode()); 47 | } 48 | -------------------------------------------------------------------------------- /src/public/ajax/fetchurl.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | require_once '../../Includes/dbinit.php'; // connect to database 23 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 24 | 25 | use Aprelendo\Curl; 26 | use Aprelendo\InternalException; 27 | use Aprelendo\UserException; 28 | 29 | try { 30 | if (!empty($_GET['url'])) { 31 | $url = $_GET['url']; 32 | $url = Curl::getFinalUrl($url); 33 | $file_contents = Curl::getUrlContents($url); 34 | $file_lang = extractLang($file_contents); 35 | $result = $file_contents ? ['url' => $url, 'lang' => $file_lang, 'file_contents' => $file_contents] : ''; 36 | header('Content-Type: application/json'); 37 | echo json_encode($result); 38 | } else { 39 | throw new UserException('Error retrieving that URL. Please check it is not empty or malformed.'); 40 | } 41 | } catch (InternalException | UserException $e) { 42 | echo $e->getJsonError(); 43 | } 44 | 45 | function extractLang($html) { 46 | $doc = new DOMDocument(); 47 | @$doc->loadHTML($html); // Suppress warnings for malformed HTML 48 | 49 | $html_tag = $doc->getElementsByTagName('html')->item(0); 50 | 51 | if ($html_tag && $html_tag->hasAttribute('lang')) { 52 | $lang = $html_tag->getAttribute('lang'); 53 | 54 | // Normalize to two-letter code 55 | return strtolower(substr($lang, 0, 2)); 56 | } 57 | 58 | return ''; // Return empty if no lang attribute is found 59 | } 60 | -------------------------------------------------------------------------------- /src/public/ajax/fetchvideo.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // check if user is logged in and set $user object 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Videos; 30 | use Aprelendo\InternalException; 31 | use Aprelendo\UserException; 32 | 33 | try { 34 | if (!empty($_POST['video_id'])) { 35 | $video_id = $_POST['video_id']; 36 | $video = new Videos($pdo, $user->id, $user->lang_id); 37 | echo $video->fetchVideo($user->lang, $video_id); 38 | } else { 39 | throw new UserException('Error retrieving that URL. Please check it is not empty or malformed'); 40 | } 41 | 42 | } catch (InternalException | UserException $e) { 43 | echo $e->getJsonError(); 44 | } 45 | -------------------------------------------------------------------------------- /src/public/ajax/getaireply.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\AIBot; 25 | use Aprelendo\UserException; 26 | 27 | header('Content-Type: text/plain'); 28 | header('Cache-Control: no-cache'); 29 | header('X-Accel-Buffering: no'); // Disable buffering in Nginx 30 | header('Connection: keep-alive'); 31 | 32 | try { 33 | if (!isset($_POST['prompt']) || empty($_POST['prompt'])) { 34 | throw new UserException('Error: Empty or malformed prompt.'); 35 | } 36 | 37 | $ai_bot = new AIBot($user->hf_token, $user->lang, $user->native_lang); 38 | 39 | // Stream the AI response 40 | $ai_bot->streamReply($_POST['prompt']); 41 | } catch (UserException $e) { 42 | echo "Error: " . $e->getMessage(); 43 | } 44 | -------------------------------------------------------------------------------- /src/public/ajax/getcards.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Card; 25 | use Aprelendo\InternalException; 26 | use Aprelendo\UserException; 27 | 28 | try { 29 | // initialize variables 30 | $user_id = $user->id; 31 | $lang_id = $user->lang_id; 32 | 33 | $card = new Card($pdo, $user_id, $lang_id); 34 | 35 | if (!isset($_POST['word']) || empty($_POST['word'])) { 36 | $limit = $_POST['limit']; 37 | $result = $card->getWordsUserIsLearning((int)$limit); 38 | } else { 39 | $result = $card->getExampleSentencesForWord($_POST['word']); 40 | } 41 | 42 | echo json_encode($result); 43 | } catch (InternalException | UserException $e) { 44 | echo $e->getJsonError(); 45 | } 46 | -------------------------------------------------------------------------------- /src/public/ajax/getdicuris.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Language; 25 | use Aprelendo\InternalException; 26 | use Aprelendo\UserException; 27 | 28 | try { 29 | $lang = new Language($pdo, $user->id); 30 | $lang->loadRecordById($user->lang_id); 31 | 32 | $result['dictionary_uri'] = $lang->dictionary_uri; 33 | $result['img_dictionary_uri'] = $lang->img_dictionary_uri; 34 | $result['translator_uri'] = $lang->translator_uri; 35 | 36 | echo json_encode($result); 37 | } catch (InternalException | UserException $e) { 38 | echo $e->getJsonError(); 39 | } 40 | -------------------------------------------------------------------------------- /src/public/ajax/getebook.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Texts; 25 | use Aprelendo\EbookFile; 26 | use Aprelendo\UserException; 27 | 28 | try { 29 | $user_id = $user->id; 30 | $lang_id = $user->lang_id; 31 | $id = (int)$_GET['id']; 32 | 33 | $text = new Texts($pdo, $user_id, $lang_id); 34 | $text->loadRecord($id); 35 | $file_name = $text->source_uri; 36 | 37 | if (!empty($file_name)) { 38 | $ebook_file = new EbookFile($file_name); 39 | $ebook_content = $ebook_file->get(); 40 | if (!$ebook_content) { 41 | throw new UserException('Book content is empty.', 404); 42 | } 43 | } else { 44 | throw new UserException('Empty file name.', 404); 45 | } 46 | } catch (\Exception $e) { 47 | // catches UserException but also possible Exceptions from fileread() in $ebook_file->get() 48 | http_response_code($e->getCode()); 49 | } 50 | -------------------------------------------------------------------------------- /src/public/ajax/getebookaudio.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Texts; 25 | use Aprelendo\InternalException; 26 | use Aprelendo\UserException; 27 | 28 | try { 29 | $text = new Texts($pdo, $user->id, $user->lang_id); 30 | $text->loadRecord($_GET['id']); 31 | $audio_uri = $text->audio_uri; 32 | 33 | if (!empty($audio_uri)) { 34 | echo $audio_uri; 35 | } else { 36 | throw new UserException("File not found", 404); 37 | } 38 | } catch (UserException $e) { 39 | http_response_code($e->getCode()); 40 | } 41 | -------------------------------------------------------------------------------- /src/public/ajax/getstats.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // loads User class & check if user is logged in 23 | 24 | use Aprelendo\WordStats; 25 | use Aprelendo\InternalException; 26 | use Aprelendo\UserException; 27 | 28 | try { 29 | if ($_GET['type'] === "words") { 30 | $stats = new WordStats($pdo, $user->id, $user->lang_id); 31 | $result = $stats->getTotals(); 32 | } 33 | 34 | echo json_encode($result); 35 | } catch (InternalException | UserException $e) { 36 | echo $e->getJsonError(); 37 | } 38 | -------------------------------------------------------------------------------- /src/public/ajax/getuserwords.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Words; 30 | use Aprelendo\Language; 31 | use Aprelendo\WordFrequency; 32 | use Aprelendo\InternalException; 33 | use Aprelendo\UserException; 34 | 35 | try { 36 | if (isset($_POST['txt'])) { 37 | $text = html_entity_decode($_POST['txt']); 38 | $result['text'] = $text; 39 | 40 | $user_words = new Words($pdo, $user->id, $user->lang_id); 41 | $result['user_words'] = $user_words->getAll(10); // 10 = words first, then phrases 42 | 43 | $lang = new Language($pdo, $user->id); 44 | $lang->loadRecordById($user->lang_id); 45 | 46 | if ($lang->show_freq_words) { 47 | $word_freq = new WordFrequency($pdo, $lang->name); 48 | $freq_words = $word_freq->getHighFrequencyList(); 49 | $result['high_freq'] = \array_column($freq_words, 'word'); 50 | } 51 | 52 | echo json_encode($result); 53 | } 54 | } catch (InternalException | UserException $e) { 55 | echo $e->getJsonError(); 56 | } 57 | -------------------------------------------------------------------------------- /src/public/ajax/getwordfreq.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_GET) || empty($_GET)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\WordFrequency; 30 | use Aprelendo\InternalException; 31 | use Aprelendo\UserException; 32 | 33 | try { 34 | $user_id = $user->id; 35 | 36 | $word_freq = new WordFrequency($pdo, $user->lang); 37 | echo $word_freq->get($_GET['word']); 38 | } catch (InternalException | UserException $e) { 39 | echo $e->getJsonError(); 40 | } 41 | -------------------------------------------------------------------------------- /src/public/ajax/google_oauth.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | require_once '../../Includes/dbinit.php'; // connect to database 23 | 24 | use Aprelendo\User; 25 | use Aprelendo\UserAuth; 26 | use Aprelendo\UserRegistrationManager; 27 | use Aprelendo\InternalException; 28 | use Aprelendo\UserException; 29 | 30 | try { 31 | if (!empty($_POST['id']) && !empty($_POST['email'])) { 32 | $google_id = $_POST['id']; //Google ID 33 | $google_email = $_POST['email']; //Email ID 34 | $google_name = $_POST['name']; //Name 35 | // $google_profile_pic = $_POST['pic']; //Profile Pic URL 36 | $time_zone = $_POST['time-zone']; // browser time zone 37 | 38 | $user = new User($pdo); 39 | 40 | // check if google email is already in db 41 | $user->loadRecordByEmail($google_email); 42 | $user_auth = new UserAuth($user); 43 | 44 | if (!empty($user->email)) { 45 | // user already exists 46 | $user_auth->login($user->name, '', $google_id); 47 | } else { 48 | // new user 49 | $user_data = [ 50 | 'username' => $google_name, 51 | 'email' => $google_email, 52 | 'password' => $google_id 53 | ]; 54 | 55 | $user_reg = new UserRegistrationManager($user); 56 | $user_reg->register($user_data); 57 | $user->updateGoogleId($google_id, $google_email); 58 | $user_auth->login($google_name, '', $google_id); 59 | } 60 | } 61 | } catch (InternalException | UserException $e) { 62 | echo $e->getJsonError(); 63 | } 64 | -------------------------------------------------------------------------------- /src/public/ajax/login.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | 23 | // check that $_POST is set & not empty 24 | if (!isset($_POST) || empty($_POST)) { 25 | exit; 26 | } 27 | 28 | use Aprelendo\User; 29 | use Aprelendo\UserAuth; 30 | use Aprelendo\InternalException; 31 | use Aprelendo\UserException; 32 | 33 | try { 34 | if (isset($_POST['username']) && isset($_POST['password'])) { 35 | $user = new User($pdo); 36 | $user_auth = new UserAuth($user); 37 | $user_auth->login($_POST['username'], $_POST['password']); 38 | } else { 39 | throw new UserException('Either username, email or password were not provided. Please try again.'); 40 | } 41 | } catch (InternalException | UserException $e) { 42 | echo $e->getJsonError(); 43 | } 44 | -------------------------------------------------------------------------------- /src/public/ajax/removetext.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Texts; 30 | use Aprelendo\ArchivedTexts; 31 | use Aprelendo\InternalException; 32 | use Aprelendo\UserException; 33 | 34 | try { 35 | if (isset($_POST['textIDs']) && isset($_POST['is_archived'])) { 36 | $text_ids = json_decode($_POST['textIDs']); 37 | $is_archived = $_POST['is_archived']; 38 | $user_id = $user->id; 39 | $lang_id = $user->lang_id; 40 | 41 | // decide wether we are deleting an archived text or not 42 | if ($is_archived) { 43 | $texts_table = new ArchivedTexts($pdo, $user_id, $lang_id); 44 | } else { 45 | $texts_table = new Texts($pdo, $user_id, $lang_id); 46 | } 47 | 48 | $texts_table->delete($text_ids); 49 | } else { 50 | throw new UserException('There was an error in the parameters provided to remove this text'); 51 | } 52 | } catch (InternalException | UserException $e) { 53 | echo $e->getJsonError(); 54 | } 55 | -------------------------------------------------------------------------------- /src/public/ajax/removeword.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Words; 30 | use Aprelendo\ExampleSentences; 31 | use Aprelendo\InternalException; 32 | use Aprelendo\UserException; 33 | 34 | try { 35 | $user_id = $user->id; 36 | $lang_id = $user->lang_id; 37 | 38 | if (isset($_POST['word'])) { 39 | // deletes word by 'name'; used by showtext.php 40 | $words_table = new Words($pdo, $user_id, $lang_id); 41 | $words_table->deleteByName($_POST['word']); 42 | } elseif (isset($_POST['wordIDs'])) { 43 | // deletes word by id; used by listwords.php 44 | $words_table = new Words($pdo, $user_id, $lang_id); 45 | $words_table->delete($_POST['wordIDs']); 46 | } 47 | } catch (InternalException | UserException $e) { 48 | echo $e->getJsonError(); 49 | } 50 | -------------------------------------------------------------------------------- /src/public/ajax/reporttext.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\ReportedTexts; 30 | use Aprelendo\SharedTexts; 31 | use Aprelendo\EmailSender; 32 | use Aprelendo\InternalException; 33 | use Aprelendo\UserException; 34 | 35 | try { 36 | if ($_POST['text_id'] && $_POST['reason']) { 37 | $report_text = new ReportedTexts($pdo, $_POST['text_id'], $user->id); 38 | $report_text->add($_POST['reason']); 39 | 40 | $shared_text = new SharedTexts($pdo, $user->id, $user->lang_id); 41 | $shared_text->loadRecord($_POST['text_id']); 42 | 43 | // create & send email 44 | $subject = 'Reported text - ID ' . $shared_text->id; 45 | 46 | $message = "\r\n\r\nText title: " . $shared_text->title; 47 | $message .= "\r\n\r\nReport reason: " . $_POST['reason']; 48 | $message .= "\r\n\r\nUser who reported: " . $user->name . "\r\n\r\n"; 49 | 50 | $email_sender = new EmailSender(); 51 | 52 | $email_sender->mail->addReplyTo(SUPPORT_EMAIL); 53 | $email_sender->mail->addAddress(SUPPORT_EMAIL); 54 | $email_sender->mail->Subject = $subject; 55 | $email_sender->mail->Body = $message; 56 | $email_sender->mail->isHTML(false); 57 | 58 | $email_sender->mail->send(); 59 | } 60 | } catch (InternalException | UserException $e) { 61 | echo $e->getJsonError(); 62 | } 63 | -------------------------------------------------------------------------------- /src/public/ajax/savepreferences.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | use Aprelendo\Preferences; 25 | use Aprelendo\InternalException; 26 | use Aprelendo\UserException; 27 | 28 | // check that $_POST is set & not empty 29 | if (!isset($_POST) || empty($_POST)) { 30 | exit; 31 | } 32 | 33 | // save preferences to database 34 | try { 35 | $pref = new Preferences($pdo, $user->id); 36 | $pref->edit( 37 | $_POST['fontfamily'], 38 | $_POST['fontsize'], 39 | $_POST['lineheight'], 40 | $_POST['alignment'], 41 | $_POST['mode'], 42 | $_POST['assistedlearning'] 43 | ); 44 | } catch (InternalException | UserException $e) { 45 | echo $e->getJsonError(); 46 | } 47 | -------------------------------------------------------------------------------- /src/public/ajax/togglelike.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Likes; 30 | use Aprelendo\InternalException; 31 | use Aprelendo\UserException; 32 | 33 | try { 34 | if ($_POST['id']) { 35 | $like = new Likes($pdo, $_POST['id'], $user->id, $user->lang_id); 36 | $like->toggle(); 37 | } 38 | } catch (InternalException | UserException $e) { 39 | echo $e->getJsonError(); 40 | } 41 | -------------------------------------------------------------------------------- /src/public/ajax/updatecard.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Words; 30 | use Aprelendo\SM2; 31 | use Aprelendo\InternalException; 32 | use Aprelendo\UserException; 33 | 34 | try { 35 | $user_id = $user->id; 36 | $lang_id = $user->lang_id; 37 | 38 | if (!empty($_POST['word']) && isset($_POST['answer'])) { 39 | $answer = (int)$_POST['answer']; 40 | $word = $_POST['word']; 41 | 42 | $words_table = new Words($pdo, $user_id, $lang_id); 43 | $words_table->loadRecordByWord($word); 44 | $sm2 = new SM2($words_table->easiness, $words_table->repetitions, $words_table->review_interval); 45 | $sm2->processReview($answer); 46 | 47 | $words_table->updateSM2( 48 | $word, 49 | $sm2->getInterval(), 50 | $sm2->getEasiness(), 51 | $sm2->getRepetitions() 52 | ); 53 | 54 | $words_table->updateStatus($word, $answer); 55 | } 56 | } catch (InternalException | UserException $e) { 57 | echo $e->getJsonError(); 58 | } 59 | -------------------------------------------------------------------------------- /src/public/ajax/updateuserscore.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // load $user & $user_auth objects & check if user is logged 23 | 24 | // check that $_POST is set & not empty 25 | if (!isset($_POST) || empty($_POST)) { 26 | exit; 27 | } 28 | 29 | use Aprelendo\Gems; 30 | use Aprelendo\InternalException; 31 | use Aprelendo\UserException; 32 | 33 | try { 34 | if (isset($_POST['words']) || isset($_POST['texts'])) { 35 | $user_id = $user->id; 36 | $lang_id = $user->lang_id; 37 | 38 | $gems = new Gems($pdo, $user_id, $lang_id, $user->time_zone); 39 | $new_gems = $gems->updateScore($_POST); 40 | 41 | $result = ['gems_earned' => $new_gems]; 42 | header('Content-Type: application/json'); 43 | echo json_encode($result); 44 | } 45 | } catch (InternalException | UserException $e) { 46 | echo $e->getJsonError(); 47 | } 48 | -------------------------------------------------------------------------------- /src/public/css/401.min.css: -------------------------------------------------------------------------------- 1 | body{background:linear-gradient(to right,#F37335,#da865b);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#fff;margin:0;padding:0;}.page-wrap{margin:0 auto;width:100%;text-align:center;}p,a{font-size:1em;}h2,a{text-transform:uppercase;}a{color:#fff;text-decoration:none;font-weight:bold;display:inline-block;width:88px;height:44px;line-height:44px;border-radius:5px;border:2px solid #fff;transition:all .5s ease;}a:hover{color:#F37335;background:#fff;border:2px solid #fff;width:132px;}@media (min-width:320px){h1{font-size:2em}h2{font-size:1.5em}}@media (min-width:480px){h1{font-size:3em}h2{font-size:1.75em}}@media (min-width:600px){h1{font-size:4em}h2{font-size:2em}}@media (min-width:801px){h1{font-size:5em}h2{font-size:3em}}@media (min-width:1025px){h1{font-size:6em}h2{font-size:4em}}@media (min-width:1281px){h1{font-size:7em}h2{font-size:5em}} -------------------------------------------------------------------------------- /src/public/css/403.min.css: -------------------------------------------------------------------------------- 1 | body{background:linear-gradient(to right,#CB2D3E,#EF473A);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#fff;margin:0;padding:0;}.page-wrap{margin:0 auto;width:100%;text-align:center;}p,a{font-size:1em;}h2,a{text-transform:uppercase;}a{color:#fff;text-decoration:none;font-weight:bold;display:inline-block;width:88px;height:44px;line-height:44px;border-radius:5px;border:2px solid #fff;transition:all .5s ease;}a:hover{color:#CB2D3E;background:#fff;border:2px solid #fff;width:132px;}@media (min-width:320px){h1{font-size:2em}h2{font-size:1.5em}}@media (min-width:480px){h1{font-size:3em}h2{font-size:1.75em}}@media (min-width:600px){h1{font-size:4em}h2{font-size:2em}}@media (min-width:801px){h1{font-size:5em}h2{font-size:3em}}@media (min-width:1025px){h1{font-size:6em}h2{font-size:4em}}@media (min-width:1281px){h1{font-size:7em}h2{font-size:5em}} -------------------------------------------------------------------------------- /src/public/css/404.min.css: -------------------------------------------------------------------------------- 1 | body{background:linear-gradient(to right,#0062E6,#33AEFF);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#fff;margin:0;padding:0;}.page-wrap{margin:0 auto;width:100%;text-align:center;}p,a{font-size:1em;}h2,a{text-transform:uppercase;}a{color:#fff;text-decoration:none;font-weight:bold;display:inline-block;width:88px;height:44px;line-height:44px;border-radius:5px;border:2px solid #fff;transition:all .5s ease;}a:hover{color:#0062E6;background:#fff;border:2px solid #fff;width:132px;}@media (min-width:320px){h1{font-size:2em}h2{font-size:1.5em}}@media (min-width:480px){h1{font-size:3em}h2{font-size:1.75em}}@media (min-width:600px){h1{font-size:4em}h2{font-size:2em}}@media (min-width:801px){h1{font-size:5em}h2{font-size:3em}}@media (min-width:1025px){h1{font-size:6em}h2{font-size:4em}}@media (min-width:1281px){h1{font-size:7em}h2{font-size:5em}} -------------------------------------------------------------------------------- /src/public/css/500.min.css: -------------------------------------------------------------------------------- 1 | body{background:linear-gradient(to right,#ffd89b,#fff29b);font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#333;margin:0;padding:0;}.page-wrap{margin:0 auto;width:100%;text-align:center;}p,a{font-size:1em;}h2,a{text-transform:uppercase;}a{color:#333;text-decoration:none;font-weight:bold;display:inline-block;width:88px;height:44px;line-height:44px;border-radius:5px;border:2px solid #333333;transition:all .5s ease;}a:hover{color:#ffd89b;background:#333;border:2px solid #333;width:132px;}@media (min-width:320px){h1{font-size:2em}h2{font-size:1.5em}}@media (min-width:480px){h1{font-size:3em}h2{font-size:1.75em}}@media (min-width:600px){h1{font-size:4em}h2{font-size:2em}}@media (min-width:801px){h1{font-size:5em}h2{font-size:3em}}@media (min-width:1025px){h1{font-size:6em}h2{font-size:4em}}@media (min-width:1281px){h1{font-size:7em}h2{font-size:5em}} -------------------------------------------------------------------------------- /src/public/eucookies.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | if (!isset($_COOKIE['accept_cookies'])) { ?> 22 | 23 | 24 | 25 | 27 | 28 |
29 | Cookie Gif 30 |

We use cookies to enhance your experience on our site. By continuing to browse, you agree to our use 31 | of cookies as detailed in our Privacy Policy and Terms of Service.

33 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/public/img/avatar-pablo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/avatar-pablo.jpg -------------------------------------------------------------------------------- /src/public/img/backgrounds/pattern-wallpaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/backgrounds/pattern-wallpaper.png -------------------------------------------------------------------------------- /src/public/img/backgrounds/welcome-page-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/backgrounds/welcome-page-background.jpg -------------------------------------------------------------------------------- /src/public/img/email-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/email-logo.png -------------------------------------------------------------------------------- /src/public/img/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/public/img/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /src/public/img/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/favicons/favicon.ico -------------------------------------------------------------------------------- /src/public/img/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Aprelendo", 3 | "short_name": "Aprelendo", 4 | "icons": [ 5 | { 6 | "src": "/img/favicons/web-app-manifest-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "maskable" 10 | }, 11 | { 12 | "src": "/img/favicons/web-app-manifest-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | } 17 | ], 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } -------------------------------------------------------------------------------- /src/public/img/favicons/web-app-manifest-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/favicons/web-app-manifest-192x192.png -------------------------------------------------------------------------------- /src/public/img/favicons/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/favicons/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /src/public/img/flags/ar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/img/flags/bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/img/flags/ca.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/cs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/img/flags/da.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/de.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/el.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/en.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/fr.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/he.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/hi.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 64 | 70 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/public/img/flags/hu.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/it.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/ja.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/public/img/flags/ko.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 62 | 68 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/public/img/flags/nl.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/no.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/pl.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/pt.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/public/img/flags/ro.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/ru.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/public/img/flags/sk.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/public/img/flags/sl.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 58 | 62 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/public/img/flags/sv.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/tr.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 53 | 56 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/public/img/flags/un.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/img/flags/vi.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/flags/zh.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 34 | 36 | 42 | 43 | 46 | 50 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-100.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-100k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-100k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-10k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-10k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-1k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-1k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-1m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-1m.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-200.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-200k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-200k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-25k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-25k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-3k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-3k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-500.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-500k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-500k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-50k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-50k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/gems-5k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/gems-5k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-1095.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-1095.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-14.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-180.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-1825.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-1825.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-2.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-30.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-365.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-365.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-7.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-730.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-730.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/streak-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/streak-90.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-100.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-10k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-10k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-12k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-12k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-15k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-15k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-17k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-17k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-1k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-1k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-20k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-20k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-25k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-25k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-2k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-2k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-3k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-3k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-4k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-4k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-500.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-5k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-5k.png -------------------------------------------------------------------------------- /src/public/img/gamification/achievements/words-7k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/achievements/words-7k.png -------------------------------------------------------------------------------- /src/public/img/gamification/daily-goal-streak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/daily-goal-streak.png -------------------------------------------------------------------------------- /src/public/img/gamification/gems.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/gems.png -------------------------------------------------------------------------------- /src/public/img/gamification/streak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/streak.png -------------------------------------------------------------------------------- /src/public/img/gamification/words-today.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/gamification/words-today.png -------------------------------------------------------------------------------- /src/public/img/logo-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/logo-long.png -------------------------------------------------------------------------------- /src/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/logo.png -------------------------------------------------------------------------------- /src/public/img/other/cookie-dude.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/other/cookie-dude.gif -------------------------------------------------------------------------------- /src/public/img/other/immersion-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/other/immersion-banner.jpg -------------------------------------------------------------------------------- /src/public/img/other/reading-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/usemoslinux/aprelendo/2b070b26315ab40665860e6f1820a27d93b184d4/src/public/img/other/reading-banner.jpg -------------------------------------------------------------------------------- /src/public/js/achievementsmodal.min.js: -------------------------------------------------------------------------------- 1 | function create_achievements_modal(){$(".modal").each((function(){const t=$(this).find(".modal-split");if(t.length>1){t.hide(),t.eq(0).show();let e=document.createElement("button");e.setAttribute("type","button"),e.setAttribute("class","btn btn-primary"),e.setAttribute("style","display: none;"),e.innerHTML="Back";let n=document.createElement("button");n.setAttribute("type","button"),n.setAttribute("class","btn btn-primary"),n.innerHTML="Next",$(this).find(".modal-footer").append(e).append(n);let o=0;$(n).click((function(){0==o&&$(e).show(),o==t.length-2&&$(n).text("Close").removeClass("btn-primary").addClass("btn-secondary"),o0&&(o--,t.hide(),t.eq(o).show())}))}else{let t=document.createElement("button");t.setAttribute("type","button"),t.setAttribute("class","btn btn-secondary"),t.innerHTML="Close",$(this).find(".modal-footer").append(t),$(t).click((function(){$("#modal-achievements").modal("hide")}))}}))}$(document).ready((function(){create_achievements_modal()})); -------------------------------------------------------------------------------- /src/public/js/actionbtns.min.js: -------------------------------------------------------------------------------- 1 | const ActionBtns={show:t=>{const n=$("#action-buttons"),o=n.outerWidth(),i=$(window).width(),e=t.offset(),c=t.outerWidth();e.left+o>i?n.css({top:e.top-n.outerHeight()-2,left:e.left-o+c}):n.css({top:e.top-n.outerHeight()-2,left:e.left}),n.show()},hide:t=>{$("#action-buttons").hide()},createWordActionBtns:(t,n)=>{let o=$("#word-actions-g1")||$(parent.document).find("#word-actions-g1"),i=$("#word-actions-g2")||$(parent.document).find("#word-actions-g2");if(n)return o.hide(),void i.hide();const e=t.filter(".learning, .new, .forgotten, .learned").length;t.filter(".word").length==e?(o.hide(),i.show()):(o.show(),i.hide())},bindDictionaryBtnsOnClick:(t,n)=>{const o=Dictionaries.getURIs(),i=LinkBuilder.forWordInDictionary(o.dictionary,t.text()),e=LinkBuilder.forWordInDictionary(o.img_dictionary,t.text());let c="";switch(n){case"text":c=LinkBuilder.forTranslationInText(o.translator,t);break;case"video":c=LinkBuilder.forTranslationInVideo(o.translator,t);break;case"study":c=LinkBuilder.forTranslationInStudy(o.translator,t);break;default:break}$("#btn-open-dict").off("click").on("click",(function(){openInNewTab(i)})),$("#btn-open-img-dict").off("click").on("click",(function(){openInNewTab(e)})),$("#btn-open-translator").off("click").on("click",(function(){openInNewTab(c)})),$("#btn-open-ai-bot-modal").off("click").on("click",(function(){const n=$("#ask-ai-bot-modal");n.attr("data-word",t.text()),n.modal("show")}))}},TextActionBtns={show:t=>{ActionBtns.createWordActionBtns(t,!1),$("body").disableScroll(),ActionBtns.bindDictionaryBtnsOnClick(t,"text"),ActionBtns.show(t)},hide:()=>{$("body").enableScroll(),ActionBtns.hide()}},VideoActionBtns={show:t=>{$("#text-container").disableScroll(),ActionBtns.createWordActionBtns(t,!1),ActionBtns.bindDictionaryBtnsOnClick(t,"video"),ActionBtns.show(t)},hide:()=>{$("#text-container").enableScroll(),ActionBtns.hide()}},StudyActionBtns={show:t=>{ActionBtns.createWordActionBtns(t,!0),ActionBtns.bindDictionaryBtnsOnClick(t,"study"),ActionBtns.show(t)},hide:()=>{ActionBtns.hide()}}; -------------------------------------------------------------------------------- /src/public/js/addebook.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){function e(e){$("#upload-progress-bar").parent().addClass("d-none"),$("#btn-upload-epub").removeClass("disabled"),$("#btn-save").removeClass("disabled"),e&&($("#title").val(""),$("#author").val(""),$("#url").val(""))}function t(e){const t=ePub(),a=e.target.result;t.open(a),t.loaded.metadata.then((function(e){const t=document.getElementById("title"),a=document.getElementById("author");null!=t&&(t.value=e.title,a.value=e.creator)})),window.addEventListener("unload",(function(){t.destroy()}))}e(!0),$("#btn-upload-epub").on("click",(function(){$("#url").trigger("click")})),$("#url").on("change",(function(){$("#alert-box").addClass("d-none");const a=$(this),o=a[0].files[0].name.split(".").pop().toLowerCase();if(a[0].files[0].size>2097152)showMessage("This file is bigger than the allowed limit (2 MB). Please try again.","alert-danger"),e(!0);else if("epub"!=o)showMessage("Invalid file extension. Only .epub files are allowed.","alert-danger"),e(!0);else if(window.FileReader){const e=new FileReader;e.onload=t,e.readAsArrayBuffer(a[0].files[0])}})),$("#form-addebook").on("submit",(function(t){t.preventDefault();const a=$("#upload-progress-bar"),o=new FormData(document.getElementById("form-addebook")),n=$("#audio-uri").text();$("#alert-box").addClass("d-none"),""==n||function(e){let t;try{t=new URL(e)}catch(e){return!1}return"http:"===t.protocol||"https:"===t.protocol}(n)?(a.parent().removeClass("d-none"),$("#btn-upload-epub").addClass("disabled"),$("#btn-save").addClass("disabled"),a.width("33%"),a.text("Uploading epub file..."),$.ajax({type:"POST",url:"ajax/addtext.php",data:o,dataType:"json",contentType:!1,processData:!1}).done((function(t){null!=t.error_msg?(showMessage(t.error_msg,"alert-danger"),e(!1)):(a.width("100%"),a.text("Upload complete..."),a.parent().delay(1500).fadeOut("slow",(function(){window.location.replace("/texts")})))})).fail((function(t,a,o){showMessage("Oops! There was an unexpected error uploading this text.","alert-danger"),e(!1)}))):showMessage("Invalid audio URL.","alert-danger")})),$("#audio-uri").on("input",(function(){const e=$(this).val(),t=$("#audio-url-helptext");e.includes("drive.google.com")?t.html(' Remember to share this file publicly, allowing access to anyone with the link.'):t.text("Accepts URLs from Google Drive or any standard audio source.")}))})); -------------------------------------------------------------------------------- /src/public/js/addvideo.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){function e(e){t();const a=function(e){if(""==e)return"";if(0===e.lastIndexOf("https://youtu.be/"))return e.slice(17);{const t=new Array("https://www.youtube.com/watch","https://youtube.com/watch","https://m.youtube.com/watch"),a=e.split("?"),o=a[1].split("&");for(const e of t)if(0===a[0].lastIndexOf(e))for(const e of o)if(0===e.lastIndexOf("v="))return e.substring(2)}}(e);if(""!=a){const t="https://www.youtube.com/embed/"+a;$("#btn-fetch-img").removeClass().addClass("spinner-border spinner-border-sm text-warning"),$.ajax({type:"POST",url:"ajax/fetchvideo.php",data:{video_id:a}}).done((function(a){var o;void 0!==a&&(void 0!==a.error_msg?showMessage(a.error_msg,"alert-danger"):($("#yt-video").length&&$("#yt-video").get(0).contentWindow.location.replace(t),$("#title").val((o=a.title).charAt(0).toUpperCase()+o.slice(1).toLowerCase()),$("#author").val(a.author),$("#url").val(e),""==a.text?($("#text").val(""),showMessage("This video does not include valid subtitles.","alert-danger")):($("#text").val(a.text),$("#alert-box").addClass("d-none"))))})).fail((function(e,t,a){showMessage("Oops! There was an unexpected error trying to get that video. Please try again later.","alert-danger")})).always((function(){$("#btn-fetch-img").removeClass().addClass("bi bi-arrow-down-right-square text-warning")}))}else showMessage('Malformed Youtube URL link. It should have the following format: https://www.youtube.com/watch?v=video_id or https://youtu.be/video_id. Remember to replace "video_id" with the corresponding video ID and try again.',"alert-danger")}function t(){0==$("#external_call").length&&($("input").not(":hidden").val(""),$("#text").val(""),$("#yt-video").attr("src","about:blank"))}t(),$("#url").focus(),$("#form-addvideo").on("submit",(function(e){e.preventDefault();const t=$("#form-addvideo").serializeArray();t.push({name:"shared-text",value:!0}),$.ajax({type:"POST",url:"ajax/addtext.php",data:t}).done((function(e){void 0!==e?void 0!==e.error_msg&&showMessage(e.error_msg,"alert-danger"):window.location.replace("/sharedtexts")})).fail((function(e,t,a){showMessage("Oops! There was an unexpected error when uploading this text.","alert-danger")}))})),$("#external_call").length&&e($("#url").val()),$("#btn-fetch").on("click",(function(t){e($("#url").val())})),$("#url").on("paste",(function(t){e(t.originalEvent.clipboardData.getData("text"))}))})); -------------------------------------------------------------------------------- /src/public/js/contact.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | $(document).ready(function() { 21 | /** 22 | * Sends email 23 | * This is triggered when user presses the "Send" button & submits the form 24 | */ 25 | $("#form-support").on("submit", function(e) { 26 | e.preventDefault(); 27 | 28 | showMessage("Sending message to our support team...", "alert-info"); 29 | 30 | const form_data = $(this).serialize(); 31 | 32 | $.ajax({ 33 | type: "POST", 34 | url: "ajax/contact.php", 35 | data: form_data 36 | }) 37 | .done(function(data) { 38 | if (data.error_msg == null) { 39 | showMessage( 40 | "Your message was successfully sent. You shall receive an answer briefly.", 41 | "alert-success" 42 | ); 43 | 44 | setTimeout(resetControls, 3000); 45 | } else { 46 | showMessage(data.error_msg, "alert-danger"); 47 | } 48 | }) 49 | .fail(function(xhr, ajaxOptions, thrownError) { 50 | showMessage( 51 | "Oops! There was an unexpected error trying to send your message. Please try again later.", 52 | "alert-danger" 53 | ); 54 | }); // end of ajax 55 | }); // end #form-support.on.submit 56 | 57 | /** 58 | * Empties form input fields 59 | */ 60 | function resetControls() { 61 | $("#alert-box").addClass("d-none"); 62 | $("#name").val(""); 63 | $("#email").val(""); 64 | $("#message").val(""); 65 | } // end resetControls 66 | }); 67 | -------------------------------------------------------------------------------- /src/public/js/contact.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){function e(){$("#alert-box").addClass("d-none"),$("#name").val(""),$("#email").val(""),$("#message").val("")}$("#form-support").on("submit",(function(s){s.preventDefault(),showMessage("Sending message to our support team...","alert-info");const a=$(this).serialize();$.ajax({type:"POST",url:"ajax/contact.php",data:a}).done((function(s){null==s.error_msg?(showMessage("Your message was successfully sent. You shall receive an answer briefly.","alert-success"),setTimeout(e,3e3)):showMessage(s.error_msg,"alert-danger")})).fail((function(e,s,a){showMessage("Oops! There was an unexpected error trying to send your message. Please try again later.","alert-danger")}))}))})); -------------------------------------------------------------------------------- /src/public/js/cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | /** 21 | * Creates a cookie 22 | * @param {string} name 23 | * @param {string} value 24 | * @param {integer} days 25 | */ 26 | function setCookie(name, value, days) { 27 | let expires; 28 | 29 | if (days) { 30 | let date = new Date(); 31 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 32 | expires = "; expires=" + date.toGMTString(); 33 | } else { 34 | expires = ""; 35 | } 36 | document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; 37 | } 38 | 39 | /** 40 | * Retrieves a cookie value 41 | * @param {string} name 42 | */ 43 | function getCookie(name) { 44 | const nameEQ = encodeURIComponent(name) + "="; 45 | const ca = document.cookie.split(';'); 46 | for (const element of ca) { 47 | let c = element; 48 | while (c.startsWith(' ')) 49 | c = c.substring(1, c.length); 50 | if (c.startsWith(nameEQ)) 51 | return decodeURIComponent(c.substring(nameEQ.length, c.length)); 52 | } 53 | return null; 54 | } 55 | 56 | /** 57 | * Deletes a cookie 58 | * @param {string} name 59 | */ 60 | function deleteCookie(name) { 61 | setCookie(name, "", -1); 62 | } -------------------------------------------------------------------------------- /src/public/js/cookies.min.js: -------------------------------------------------------------------------------- 1 | function setCookie(e,t,o){let n;if(o){let e=new Date;e.setTime(e.getTime()+24*o*60*60*1e3),n="; expires="+e.toGMTString()}else n="";document.cookie=encodeURIComponent(e)+"="+encodeURIComponent(t)+n+"; path=/"}function getCookie(e){const t=encodeURIComponent(e)+"=",o=document.cookie.split(";");for(const e of o){let o=e;for(;o.startsWith(" ");)o=o.substring(1,o.length);if(o.startsWith(t))return decodeURIComponent(o.substring(t.length,o.length))}return null}function deleteCookie(e){setCookie(e,"",-1)} -------------------------------------------------------------------------------- /src/public/js/dictation.min.js: -------------------------------------------------------------------------------- 1 | function toggleDictation(){if(""!=$("#audioplayer").find("source").attr("src")){let t=$("#text").clone(),e=t.find(".word"),a=$(".word");0==$(".dict-answer").length?(e.each((function(t,e){let o=$(this);const n=o.text().length,i=a.eq(t).width(),s=a.eq(t).css("font-size");let r="";o.hasClass("learned")?r="var(--w-learned)":o.hasClass("learning")?r="var(--w-learning)":o.hasClass("new")?r="var(--w-new)":o.hasClass("forgotten")&&(r="var(--w-forgotten)"),o.hide().after('
')})),$("#text").replaceWith(t),scrollToPageTop(),AudioController.playFromBeginning(),$(":text:first").focus()):(e.each((function(t,e){$(this).show().nextAll(":lt(1)").remove()})),$("#text").replaceWith(t),TextProcessor.updateAnchorsList(),scrollToPageTop(),AudioController.stop())}}$(document).ready((function(){$("body").on("blur",".dict",(function(){let t=$(this);t.val().toLowerCase()==t.attr("data-text").toLowerCase()?(t.css("border-color","var(--w-learned)"),t.next("span").not(".d-none").addClass("d-none")):""!=$.trim(t.val())&&(t.css("border-color","var(--w-forgotten)"),t.next("span").removeClass("d-none").addClass("dict-wronganswer").text("[ "+t.attr("data-text")+" ]"))})),$("body").on("keydown",".dict",(function(t){const e=t.keyCode||t.which;if(!t.isComposing&&0!=e&&229!=e&&!$(this).val())if(8==e){const e=$(".dict").index(this)-1;t.preventDefault(),$(".dict").eq(e).focus()}else 32==e&&t.preventDefault()})),$("body").on("input",".dict",(function(t){let e=t.keyCode||t.which;const a=$(this).attr("maxlength"),o=$("#audioplayer")[0].currentTime;switch(0!=e&&229!=e||(e=t.target.value.charAt(t.target.selectionStart-1).charCodeAt()),e){case 8:if(!$(this).val()){const t=$(".dict").index(this)-1;$(".dict").eq(t).focus()}break;case 49:$("#audioplayer")[0].currentTime=o-5;break;case 50:AudioController.togglePlayPause();break;case 51:$("#audioplayer")[0].currentTime=o+5;break;default:break}if($(this).val($(this).val().replace(/\d/gi,"")),a==$(this).val().length&&!t.originalEvent.isComposing){const t=$(".dict").index(this)+1;$(".dict").eq(t).focus()}}))})); -------------------------------------------------------------------------------- /src/public/js/eucookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | $(document).ready(function() { 21 | if (document.cookie.indexOf("accept_cookies") === -1) { 22 | $("#eucookielaw").fadeIn(800, function() { 23 | $(this).show(); 24 | }); 25 | } 26 | 27 | /** 28 | * Triggers when user closes the cookie consent message 29 | */ 30 | $("#removecookie").click(function() { 31 | setCookie("accept_cookies", true, 365 * 10); 32 | $("#eucookielaw").slideDown(1500, function() { 33 | $(this).remove(); 34 | }); 35 | }); // end #removecookie.on.click 36 | }); 37 | -------------------------------------------------------------------------------- /src/public/js/eucookies.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){-1===document.cookie.indexOf("accept_cookies")&&$("#eucookielaw").fadeIn(800,(function(){$(this).show()})),$("#removecookie").click((function(){setCookie("accept_cookies",!0,3650),$("#eucookielaw").slideDown(1500,(function(){$(this).remove()}))}))})); -------------------------------------------------------------------------------- /src/public/js/forgotpassword.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$(document).on("submit","#form_forgot_password",(function(e){e.preventDefault();const s=$("#form_forgot_password").serialize();showMessage("Your request is being processed. Please wait...","alert-info"),$.ajax({type:"POST",url:"ajax/forgotpassword.php",data:s}).done((function(e){null==e.error_msg?showMessage("We've sent you an email to the address you provided. It might take a few minutes to arrive in your inbox, so please be patient. If you don't see it there, be sure to check your spam or junk folder, as sometimes it can end up there. Once you receive it, click on the link provided to create your new password.","alert-success"):showMessage(e.error_msg,"alert-danger")})).fail((function(e,s,a){showMessage("Oops! There was an unexpected error when trying to replace your password. Please try again later.","alert-danger")}))})),$(document).on("submit","#form_create_new_password",(function(e){e.preventDefault();const s=$("#form_create_new_password").serialize();showMessage("Your request is being processed. Please wait...","alert-info"),$("#newpassword").val()===$("#newpassword-confirmation").val()?$.ajax({type:"post",url:"ajax/forgotpassword.php",data:s}).done((function(e){null==e.error_msg?(showMessage("Your new password has been successfully saved! Please wait a moment, and you will be automatically redirected to the home page shortly.","alert-success"),setTimeout((function(){window.location.replace("https://www.aprelendo.com/login.php")}),5e3)):showMessage(e.error_msg,"alert-danger")})).fail((function(e,s,a){showMessage("Oops! There was an unexpected error when trying to save your new password. Please try again later.","alert-danger")})):(showMessage("The passwords you entered are not identical. Please try again.","alert-danger"),$("#newpassword").val(""),$("#newpassword-confirmation").val(""))}))})); -------------------------------------------------------------------------------- /src/public/js/helpers.min.js: -------------------------------------------------------------------------------- 1 | function showMessage(e,t){let i={"alert-success":{title:"Success",image:"bi-check-circle-fill"},"alert-info":{title:"Information",image:"bi-info-circle-fill"},"alert-warning":{title:"Careful",image:"bi-exclamation-triangle-fill"},"alert-danger":{title:"Oops!",image:"bi-exclamation-circle-fill"}},n="",o="";for(const e in i)if(e==t){n=i[e].title,o=i[e].image;break}let r=''+n,l=$("
").addClass("alert-flag fs-5").html(r),s=$("
").addClass("alert-msg").html(e);$("#alert-box").empty().removeAttr("style").removeClass().addClass("alert "+t).append(l,s),$(window).scrollTop(0)}function hideMessage(e){$("#alert-box").hide(e)}function scrollToPageTop(){$("html, body").animate({scrollTop:0},"fast")}function getUniqueElements(e){let t=new Set;return $(e).each((function(){let e=$(this).text().toLowerCase().trim();t.add(e)})),t.size}function getCurrentURIParameters(){const e=new URLSearchParams(window.location.search),t={};for(const[i,n]of e.entries())t[i]=n;return t}function buildQueryString(e){if(0===Object.keys(e).length)return"";const t=new URLSearchParams;for(const[i,n]of Object.entries(e))n&&t.append(i,n);return t.toString()?"?"+t.toString():""}function getCurrentFileName(){const e=new URL(window.location.href).pathname;return e.substring(e.lastIndexOf("/")+1)}function getScrollbarWidth(e){if(e===document.body)return window.innerWidth-document.documentElement.clientWidth;{const e=document.createElement("div");e.style.visibility="hidden",e.style.overflow="scroll",e.style.width="100px",e.style.height="100px",document.body.appendChild(e);const t=document.createElement("div");return t.style.width="100%",e.appendChild(t),e.parentNode.removeChild(e),e.offsetWidth-t.offsetWidth}}function openInNewTab(e){window.open(e,"_blank","noopener,noreferrer")}function isMobileDevice(){const e=/Mobi|Android|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent),t=window.innerWidth<=768,i="ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0;return e||t||i}$.fn.enableScroll=function(){return this.each((function(){this.style.overflow="",this.style.paddingRight="",this.classList.remove("overflow-hidden"),this.classList.add("overflow-auto")}))},$.fn.disableScroll=function(){return this.each((function(){let e;e=this===document.body?window.innerWidth-document.documentElement.clientWidth:this.offsetWidth-this.clientWidth,this.style.overflow="hidden",this.style.paddingRight=`${e}px`,this.classList.remove("overflow-auto"),this.classList.add("overflow-hidden")}))},$.fn.isAfter=function(e){return this.prevUntil(e).length!==this.prevAll().length}; -------------------------------------------------------------------------------- /src/public/js/languages.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$("#form-editlanguage").on("submit",(function(e){e.preventDefault(),$.ajax({type:"post",url:"ajax/editlanguage.php",data:$(this).serialize()}).done((function(e){void 0!==e&&""!==e?showMessage(e.error_msg,"alert-danger"):(showMessage("Your language information was successfully saved. You will soon be redirected to the main page.","alert-success"),setTimeout((()=>{window.location.replace("/texts")}),2e3))})).fail((function(e,t,o){showMessage("Oops! There was an unexpected error trying to edit your language preferences.","alert-danger")}))})),$("#savebtn").on("click",(function(e){const t=$("#dict-uri").val(),o=$("#translator-uri").val();let a=!1;0==t.length?(showMessage("You need to specify the URL of the dictionary you want to use.","alert-danger"),a=!0):-1==t.indexOf("%s")?(showMessage("The dictionary URL needs to include the position of the lookup word or phrase. For this, use '%s' (without quotation marks).","alert-danger"),a=!0):0==o.length?(showMessage("You need to specify the URL of the translator you want to use.","alert-danger"),a=!0):-1==o.indexOf("%s")&&(showMessage("The translator URL needs to include the position of the lookup word or phrase. For this, use '%s' (without quotation marks).","alert-danger"),a=!0),a&&(e.preventDefault(),e.stopPropagation())})),$(".dict-select .dropdown-item").on("click",(function(){let e=$(this).attr("value");$(this).closest(".input-group").find('input[type="url"]').val(e)}))})); -------------------------------------------------------------------------------- /src/public/js/likes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | $(document).ready(function() { 21 | 22 | /** 23 | * Toggles like for text 24 | * Triggers when user clicks on a heart 25 | */ 26 | $("span.bi-heart, span.bi-heart-fill").on("click", function() { 27 | const $like_btn = $(this); 28 | const text_id = $like_btn.attr("data-idText"); 29 | 30 | toggleLike($like_btn); 31 | 32 | $.ajax({ 33 | type: "POST", 34 | url: "ajax/togglelike.php", 35 | data: { id: text_id } 36 | }) 37 | .done(function(data) { 38 | if (data.error_msg) { 39 | console.log("Oops! There was an unexpected error"); 40 | toggleLike($like_btn); 41 | } 42 | }) 43 | .fail(function(xhr, ajaxOptions, thrownError) { 44 | console.log("Oops! There was an unexpected error"); 45 | toggleLike($like_btn); 46 | }); 47 | }); // end span.bi-heart-fill.on.click 48 | 49 | function toggleLike($like_btn) { 50 | $like_btn.toggleClass("bi-heart bi-heart-fill"); 51 | const total_likes = parseInt( 52 | $like_btn.siblings("small").text() 53 | ); 54 | if ($like_btn.hasClass("bi-heart-fill")) { 55 | $like_btn.siblings("small").text(total_likes + 1); 56 | } else { 57 | $like_btn.siblings("small").text(total_likes - 1); 58 | } 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /src/public/js/likes.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){function t(t){t.toggleClass("bi-heart bi-heart-fill");const a=parseInt(t.siblings("small").text());t.hasClass("bi-heart-fill")?t.siblings("small").text(a+1):t.siblings("small").text(a-1)}$("span.bi-heart, span.bi-heart-fill").on("click",(function(){const a=$(this),i=a.attr("data-idText");t(a),$.ajax({type:"POST",url:"ajax/togglelike.php",data:{id:i}}).done((function(i){i.error_msg&&t(a)})).fail((function(i,l,n){t(a)}))}))})); -------------------------------------------------------------------------------- /src/public/js/listtexts.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){function e(){const e=getCurrentFileName(),t=getCurrentURIParameters(),a={p:t.p?t.p:"1",ft:$(".ft.active").data("value")||0,fl:$(".fl.active").data("value")||0,s:$("#s").val().trim(),sa:$(".sa").hasClass("active")?"1":"0",o:$(".o.active").data("value")||0},i=buildQueryString(a);window.location.replace(e+i)}function t(){0===$("input[type=checkbox]:checked").length?$("#actions-menu").addClass("disabled"):$("#actions-menu").removeClass("disabled")}$("#search").focus(),$("input:checkbox").prop("checked",!1),$("#modal-achievements").length&&$("#modal-achievements").modal("show"),$("form").submit((function(t){t.preventDefault(),e()})),$("#mDelete").on("click",(function(){if(confirm("Really delete?")){let t=[];$("input.chkbox-selrow:checked").each((function(){t.push($(this).attr("data-idText"))}));const a="1"==getCurrentURIParameters().sa;$.ajax({url:"ajax/removetext.php",type:"POST",data:{textIDs:JSON.stringify(t),is_archived:a?1:0}}).done((function(){e()})).fail((function(){alert("There was an error when trying to delete the selected texts. Refresh the page and try again.")}))}})),$("#mArchive").on("click",(function(){const t="Archive"===$(this).text();let a=[];$("input.chkbox-selrow:checked").each((function(){a.push($(this).attr("data-idText"))})),$.ajax({url:"ajax/archivetext.php",type:"POST",data:{textIDs:JSON.stringify(a),archivetext:t}}).done((function(){e()})).fail((function(){alert("There was an error when trying to archive the selected texts. Refresh the page and try again.")}))})),$(document).on("change",".chkbox-selrow",t),$(document).on("click","#chkbox-selall",(function(e){e.stopPropagation();$(".chkbox-selrow").prop("checked",$(this).prop("checked")),t()})),$("#btn-filter + div > a").on("click",(function(){const e=$(this);if(e.is(".sa"))e.toggleClass("active");else{e.addClass("active");let t="";e.is(".ft")?t=".ft":e.is(".fl")&&(t=".fl"),e.siblings(".active"+t).each((function(){$(this).toggleClass("active")}))}})),$("#dropdown-menu-sort .o").on("click",(function(e){const t=getCurrentFileName(),a={ft:$(".ft.active").data("value")||0,fl:$(".fl.active").data("value")||0,s:$("#s").val().trim(),sa:$(".sa").hasClass("active")?"1":"0",o:$(this).data("value")||0},i=buildQueryString(a);window.location.replace(t+i)})),$(document).on("click","li.disabled",(function(){return!1})),$("#welcome-close").on("click",(function(e){e.preventDefault(),setCookie("hide_welcome_msg",!0,3650)}))})); -------------------------------------------------------------------------------- /src/public/js/listwords.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){let e="";function o(){0===$("input[type=checkbox]:checked").length?$("#actions-menu").addClass("disabled"):$("#actions-menu").removeClass("disabled")}$("#search").focus(),$("input:checkbox").prop("checked",!1),$("form").submit((function(e){e.preventDefault();const o={s:$("#s").val().trim(),o:$(".o.active").data("value")||0},t=buildQueryString(o);window.location.replace("words"+t)})),$.ajax({url:"/ajax/getdicuris.php",type:"GET",dataType:"json"}).done((function(o){null==o.error_msg&&(e=o.dictionary_uri)})),$("#mDelete").on("click",(function(){if(confirm("Really delete?")){let e=[];$("input.chkbox-selrow:checked").each((function(){e.push($(this).attr("data-idWord"))})),$.ajax({url:"ajax/removeword.php",type:"POST",data:{wordIDs:JSON.stringify(e)}}).done((function(){window.location.replace("words"+buildQueryString(getCurrentURIParameters()))})).fail((function(e,o,t){alert("There was an error when trying to delete the selected words. Refresh the page and try again.")}))}})),$(document).on("change",".chkbox-selrow",o),$(document).on("click","#chkbox-selall",(function(e){e.stopPropagation();$(".chkbox-selrow").prop("checked",$(this).prop("checked")),o()})),$("#dropdown-menu-sort .o").on("click",(function(e){const o=getCurrentFileName(),t={s:$("#s").val().trim(),o:$(this).data("value")||0},n=buildQueryString(t);window.location.replace(o+n)})),$(".word").on("click",(function(o){const t=$(this),n=LinkBuilder.forWordInDictionary(e,t.text());openInNewTab(n)}))})); -------------------------------------------------------------------------------- /src/public/js/login.min.js: -------------------------------------------------------------------------------- 1 | function googleSignIn(e){const n=decodeJwtResponse(e.credential);$.ajax({type:"POST",data:{id:n.sub,name:n.name,email:n.email,"time-zone":Intl.DateTimeFormat().resolvedOptions().timeZone},url:"ajax/google_oauth.php"}).done((function(e){null==e.error_msg?window.location.replace("/texts"):showMessage(e.error_msg,"alert-danger")})).fail((function(e,n,o){showMessage("Oops! There was an unexpected error when trying to register you. Please try again later.","alert-danger")}))}function decodeJwtResponse(e){let n=e.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),o=decodeURIComponent(atob(n).split("").map((function(e){return"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2)})).join(""));return JSON.parse(o)}$(document).ready((function(){$("#form_login").on("submit",(function(e){e.preventDefault();const n=$("#form_login").serialize();$.ajax({type:"POST",url:"ajax/login.php",data:n}).done((function(e){null==e.error_msg?window.location.replace("/texts"):showMessage(e.error_msg,"alert-danger")})).fail((function(e,n,o){showMessage("Oops! There was an unexpected error when trying to log you in. Please try again later.","alert-danger")}))}))})); -------------------------------------------------------------------------------- /src/public/js/matomo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | let _paq = window._paq = window._paq || []; 21 | /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 22 | _paq.push(['trackPageView']); 23 | _paq.push(['enableLinkTracking']); 24 | (function () { 25 | const u = "//aprelendo.com/matomo/"; 26 | _paq.push(['setTrackerUrl', u + 'matomo.php']); 27 | _paq.push(['setSiteId', '1']); 28 | const d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; 29 | g.async = true; g.src = u + 'matomo.js'; s.parentNode.insertBefore(g, s); 30 | })(); 31 | -------------------------------------------------------------------------------- /src/public/js/matomo.min.js: -------------------------------------------------------------------------------- 1 | let _paq=window._paq=window._paq||[];_paq.push(["trackPageView"]),_paq.push(["enableLinkTracking"]),function(){const e="//aprelendo.com/matomo/";_paq.push(["setTrackerUrl",e+"matomo.php"]),_paq.push(["setSiteId","1"]);const a=document,t=a.createElement("script"),p=a.getElementsByTagName("script")[0];t.async=!0,t.src=e+"matomo.js",p.parentNode.insertBefore(t,p)}(); -------------------------------------------------------------------------------- /src/public/js/password.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){function t(){const t=$("#newpassword"),s=$("#newpassword-confirmation"),e=$("#passwords-match-text");""===t.val()||""===s.val()?(s.css("border-bottom","1px solid #ced4da"),e.text("")):s.val()!==t.val()?(s.css("border-bottom","2px solid red"),e.text("Passwords dont match")):(s.css("border-bottom","2px solid green"),e.text("Passwords match"))}$("#newpassword").on("input",(function(){const s=$(this),e=$("#newpassword-confirmation"),o=$("#password-strength-text");s.val().length<8?(s.css("border-bottom","2px solid red"),o.text("Weak (should be at least 8 characters long)")):s.val().match(/(\d)/)&&s.val().match(/([a-zA-Z])/)&&s.val().match(/([~`!@#$%^&*()\-_+={};:[\]?./,])/)?(s.css("border-bottom","2px solid green"),o.text("Strong")):(s.css("border-bottom","2px solid yellow"),o.text("Medium (should include letters, numbers and special characters)")),""!==e.val()&&t()})),$("#newpassword-confirmation").on("input",(function(){t()})),$(".show-hide-password-btn").on("click",(function(t){t.preventDefault();let s=$(this).parent().find("input"),e=$(this).find("span");"text"===s.attr("type")?(s.attr("type","password"),e.addClass("bi-eye-slash-fill").removeClass("bi-eye-fill")):"password"===s.attr("type")&&(s.attr("type","text"),e.removeClass("bi-eye-slash-fill").addClass("bi-eye-fill"))}))})); -------------------------------------------------------------------------------- /src/public/js/preferences.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | $(document).ready(function() { 21 | /** 22 | * Saves preferences to the database and shows success/failure message 23 | * It is executed when user clicks the submit button 24 | * @param {event object} e Used to prevent reloading of the page 25 | */ 26 | $("#prefs-form").submit(function(e) { 27 | e.preventDefault(); // avoid to execute the actual submit of the form. 28 | 29 | $.ajax({ 30 | url: "ajax/savepreferences.php", 31 | type: "POST", 32 | data: $("#prefs-form").serialize() 33 | }) 34 | .done(function(data) { 35 | if (data.error_msg) { 36 | showMessage(data.error_msg, "alert-danger"); 37 | } else { 38 | showMessage("Your preferences were successfully saved." 39 | + " You will soon be redirected to the main page.", "alert-success"); 40 | 41 | setTimeout(() => { window.location.replace("/texts"); }, 2000); 42 | } 43 | }) 44 | .fail(function(jqXHR, textStatus, errorThrown) { 45 | showMessage("Oops! Something went wrong when trying to save your preferences.", "alert-danger"); 46 | }); 47 | }); // end #prefs-form.submit 48 | }); 49 | -------------------------------------------------------------------------------- /src/public/js/preferences.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$("#prefs-form").submit((function(e){e.preventDefault(),$.ajax({url:"ajax/savepreferences.php",type:"POST",data:$("#prefs-form").serialize()}).done((function(e){e.error_msg?showMessage(e.error_msg,"alert-danger"):(showMessage("Your preferences were successfully saved. You will soon be redirected to the main page.","alert-success"),setTimeout((()=>{window.location.replace("/texts")}),2e3))})).fail((function(e,r,s){showMessage("Oops! Something went wrong when trying to save your preferences.","alert-danger")}))}))})); -------------------------------------------------------------------------------- /src/public/js/register.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$("#form-register").on("submit",(function(e){e.preventDefault();const n=$("#form-register").serializeArray();n.push({name:"time-zone",value:Intl.DateTimeFormat().resolvedOptions().timeZone}),showMessage("We are processing your registration. Please wait a moment while we securely handle your information. This should only take a few seconds.","alert-info"),$.ajax({type:"POST",url:"ajax/register.php",data:n}).done((function(e){null==e.error_msg?showMessage("Registration successful! We've sent an activation email to the address you provided. Please check your inbox to complete the registration process. The email might take a few minutes to arrive. If you don't find it in your inbox, check your spam or junk folder as it might have been filtered there. Once you locate the email, click on the activation link to activate your account and start using our platform.","alert-success"):showMessage(e.error_msg,"alert-danger")})).fail((function(e,n,a){showMessage("Oops! There was an unexpected error when trying to register you. Please try again later.","alert-danger")}))})),$("#learning-lang").on("change",(function(){const e=["Arabic","Bulgarian","Catalan","Chinese","Croatian","Czech","Danish","Dutch","English","French","German","Greek","Hebrew","Hindi","Hungarian","Italian","Japanese","Korean","Norwegian","Polish","Portuguese","Romanian","Russian","Slovak","Slovenian","Spanish","Swedish","Turkish","Vietnamese"],n=$(this).prop("selectedIndex"),a="img/flags/"+["ar","bg","ca","zh","hr","cs","da","nl","en","fr","de","el","he","hi","hu","it","ja","ko","no","pl","pt","ro","ru","sk","sl","es","sv","tr","vi"][n]+".svg";$("h1").text(["أهلا بك!","Добре дошли!","Benvingut!","欢迎!","Dobrodošli!","Vítejte!","Velkommen!","Welkom!","Welcome!","Bienvenue!","Willkommen!","Καλώς ήρθατε!","ברוך הבא!","स्वागत है!","Üdvözöljük!","Benvenuto!","ようこそ!","환영합니다!","Velkommen!","Witaj!","Bem-vindo!","Bun venit!","Добро пожаловать!","Vitajte!","Dobrodošli!","¡Bienvenido!","Välkommen!","Hoş geldiniz!","Chào mừng!"][n]).prepend(''+e[n]+'
'),$("#welcome-msg").text("You are only one step away from learning "+e[n])}))})); -------------------------------------------------------------------------------- /src/public/js/showimportwordsmodal.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){let e=[];$("#words-upload-input").on("change",(function(t){let o=t.target.files[0],n=new FileReader;n.onload=function(t){let o=t.target.result;e=o.split(/\r?\n|\r/),e=[...new Set(e)],e=e.map((e=>e.trim().toLowerCase())).filter((e=>e&&!e.includes(" "))),function(e){let t=document.getElementById("words-table").querySelector("tbody");t.innerHTML="",e.forEach((e=>{t.insertRow().insertCell(0).textContent=e}))}(e)},n.readAsText(o),document.getElementById("words-upload-wrap").classList.toggle("d-none"),document.getElementById("words-table-wrap").classList.toggle("d-none"),document.getElementById("btn-import-words").disabled=!1,document.getElementById("words-upload-input").value=""})),$("#import-words-modal").on("hidden.bs.modal",(function(){document.getElementById("words-table-wrap").classList.add("d-none"),document.getElementById("words-upload-wrap").classList.remove("d-none"),document.getElementById("words-table").querySelector("tbody").innerHTML="",document.getElementById("btn-import-words").disabled=!0})),$("#btn-import-words").on("click",(function(){$.ajax({type:"POST",url:"/ajax/addword.php",data:{words:e}}).done((function(e){location.reload()})).fail((function(e,t,o){alert("Oops! There was an unexpected error.")}))})),$("#words-upload-wrap").on("click",(function(e){e.preventDefault(),$("#words-upload-input").trigger("click")})),$("#words-upload-wrap").on("dragover",(function(e){e.preventDefault(),e.stopPropagation(),$(this).addClass("words-dropping")})),$("#words-upload-wrap").on("dragleave",(function(e){e.preventDefault(),e.stopPropagation(),$(this).removeClass("words-dropping")})),$("#words-upload-wrap").on("drop",(function(e){e.preventDefault(),e.stopPropagation(),$(this).removeClass("words-dropping");let t=e.originalEvent.dataTransfer.files;t.length>0&&($("#words-upload-input").prop("files",t),$("#words-upload-input").trigger("change"))}))})); -------------------------------------------------------------------------------- /src/public/js/showreadersettingsmodal.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$("#btn-save-reader-prefs").on("click",(function(){const a=$("#mode").val()+"mode";let e=$(parent.document.body),s=$("#text-container");e.removeClass().addClass(a),s.css({"font-family":$("#fontfamily").val(),"font-size":$("#fontsize").val(),"text-align":$("#alignment").val(),"line-height":$("#lineheight").val()});let n=e.find("#audioplayer-container");n.hasClass("d-none")?n.removeClass().addClass(a).addClass("py-3 d-none"):n.removeClass().addClass(a+" py-3"),e.find(".offcanvas").length&&("darkmode"==a?(e.find(".offcanvas").addClass("text-bg-dark"),e.find("#close-offcanvas").addClass("btn-close-white")):(e.find(".offcanvas").removeClass("text-bg-dark"),e.find("#close-offcanvas").removeClass("btn-close-white"))),$.ajax({url:"/ajax/savepreferences.php",type:"POST",data:$("#prefs-modal-form").serialize()})}))})); -------------------------------------------------------------------------------- /src/public/js/showreporttextmodal.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$("#btn-report-text").on("click",(function(){const e=$(this),r=$("#text-container").attr("data-idtext"),t=$('input[name="report-reason"]:checked').val();if(!t)throw showMessage("Please select a reason for reporting.","alert-danger"),new Error("User forgot to complete reason for reporting");showMessage("Sending... please wait.","alert-info"),e.prop("disabled",!0),$.ajax({type:"POST",url:"ajax/reporttext.php",data:{text_id:r,reason:t}}).done((function(r){if(r.error_msg)throw showMessage(r.error_msg,"alert-danger"),new Error(r.error_msg);showMessage("Thank you! Your report has been submitted. Together, we're making our community safer and more enjoyable for everyone.","alert-success"),setTimeout((function(){$("#report-text-modal").modal("hide"),e.prop("disabled",!1)}),3e3)})).fail((function(e,r,t){throw showMessage("Oops! There was an server error trying to report that text. Try again later.","alert-danger"),new Error("Really unexpected error while reporting this content. Probably a problem with the server.")}))})),$("#report-text-modal").on("hidden.bs.modal",(function(){hideMessage()}))})); -------------------------------------------------------------------------------- /src/public/js/stats.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){!function(){function t(t){const e=t.raw,n=(e/t.dataset.data.reduce(((t,e)=>t+e),0)*100).toFixed(1);return`${t.label}: ${e} (${n}%)`}$.ajax({type:"GET",url:"ajax/getstats.php",data:{type:"words"},dataType:"json"}).done((function(e){const n=parseInt(e[0]),a=parseInt(e[1]),o=parseInt(e[2]),r=parseInt(e[3]),i=parseInt(e[4]),l=document.getElementById("total-stats-canvas").getContext("2d"),s={id:"noDataPlugin",afterDraw:t=>{if(0===i){const e=t.ctx,{width:n,height:a}=t;e.save(),e.textAlign="center",e.textBaseline="middle",e.font="16px Arial",e.fillStyle="gray",e.fillText("No Data Available",n/2,a/2),e.restore()}}};new Chart(l,{type:"doughnut",data:{labels:["Learned","Learning","New","Forgotten"],datasets:[{data:[n,a,o,r],backgroundColor:["#3cb371","#ffa500","#1e90ff","#E0115F"],hoverOffset:4}]},options:{responsive:!0,plugins:{legend:{display:i,position:"right"},tooltip:{callbacks:{label:t}}}},plugins:[s]}),i&&($("#learned-count").text(n),$("#learned-percentage").text((n/i*100).toFixed(2).toLocaleString("en-US")),$("#learning-count").text(a),$("#learning-percentage").text((a/i*100).toFixed(2).toLocaleString("en-US")),$("#new-count").text(o),$("#new-percentage").text((o/i*100).toFixed(2).toLocaleString("en-US")),$("#forgotten-count").text(r),$("#forgotten-percentage").text((r/i*100).toFixed(2).toLocaleString("en-US")),$("#total-count").text(n+a+o+r))})).fail((function(){$("#total-stats-canvas").replaceWith("

Error: no data to display

")}))}()})); -------------------------------------------------------------------------------- /src/public/js/subtitles-parser/subtitles.parser.min.js: -------------------------------------------------------------------------------- 1 | let parser=function(){let e={fromSrt:function(e,r){let n=!!r;(e=(e=e.replace(/\r/g,"")).split(/(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})/g)).shift();let i=[];for(let r=0;r]*>?/gm,"").trim()});return i},toSrt:function(e){if(!(e instanceof Array))return"";let t="";for(const n of e){let e=n;isNaN(e.startTime)||isNaN(e.endTime)||(e.startTime=r(parseInt(e.startTime,10)),e.endTime=r(parseInt(e.endTime,10))),t+=e.id+"\r\n",t+=e.startTime+" --\x3e "+e.endTime+"\r\n",t+=e.text.replace("\n","\r\n")+"\r\n\r\n"}return t}},t=function(e){let t=/(\d+):(\d{2}):(\d{2}),(\d{3})/.exec(e);if(null===t)return 0;for(let e=1;e<5;e++)t[e]=parseInt(t[e],10),isNaN(t[e])&&(t[e]=0);return parseInt(36e5*t[1]+6e4*t[2]+1e3*t[3]+t[4])},r=function(e){let t=[36e5,6e4,1e3],r=[];for(let n in t){let i=(e/t[n]>>0).toString();i.length<2&&(i="0"+i),e%=t[n],r.push(i)}let n=e.toString();if(n.length<3)for(let e=0;e<=3-n.length;e++)n="0"+n;return r.join(":")+","+n};return e}();"object"==typeof exports&&(module.exports=parser); -------------------------------------------------------------------------------- /src/public/js/tooltips.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Pablo Castagnino 3 | * 4 | * This file is part of aprelendo. 5 | * 6 | * aprelendo is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * aprelendo is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with aprelendo. If not, see . 18 | */ 19 | 20 | function setNewTooltip(elem, title) { 21 | if (isMobileDevice()) { 22 | return; 23 | } 24 | 25 | // hide old tooltip 26 | let old_tooltip = bootstrap.Tooltip.getInstance(elem); 27 | old_tooltip.hide(); 28 | 29 | // create new tooltip 30 | elem.setAttribute('data-bs-title', title); 31 | let tooltip = new bootstrap.Tooltip(elem, { 32 | trigger: 'hover' 33 | }); 34 | 35 | return tooltip; 36 | } 37 | 38 | $(document).ready(function () { 39 | // Use Bootstrap tooltips only on desktop 40 | if (!isMobileDevice()) { 41 | const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); 42 | const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => 43 | new bootstrap.Tooltip(tooltipTriggerEl, { 44 | trigger: 'hover' 45 | }) 46 | ); 47 | } 48 | }); -------------------------------------------------------------------------------- /src/public/js/tooltips.min.js: -------------------------------------------------------------------------------- 1 | function setNewTooltip(t,e){if(isMobileDevice())return;return bootstrap.Tooltip.getInstance(t).hide(),t.setAttribute("data-bs-title",e),new bootstrap.Tooltip(t,{trigger:"hover"})}$(document).ready((function(){if(!isMobileDevice()){[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map((t=>new bootstrap.Tooltip(t,{trigger:"hover"})))}})); -------------------------------------------------------------------------------- /src/public/js/userprofile.min.js: -------------------------------------------------------------------------------- 1 | $(document).ready((function(){$("#userprofile-form").submit((function(e){e.preventDefault(),$.ajax({url:"ajax/saveuserprofile.php",type:"POST",data:$("#userprofile-form").serialize()}).done((function(e){e.error_msg?showMessage(e.error_msg,"alert-danger"):(showMessage("Your user profile information was successfully saved. You will soon be redirected to the main page.","alert-success"),setTimeout((()=>{window.location.replace("/texts")}),2e3))})).fail((function(){showMessage("Oops! Something went wrong when trying to save your user profile information.","alert-danger")})).always((function(){$("#password, #newpassword, #newpassword-confirmation").val("")}))})),$("#btn-delete-account").on("click",(function(){$(document).find("#delete-account-modal").modal("show")})),$("#btn-confirm-delete-account").on("click",(function(){$.ajax({url:"ajax/deleteaccount.php",type:"POST",data:$("#userprofile-form").serialize()}).done((function(e){e.error_msg?showMessage(e.error_msg,"alert-danger"):window.location.replace("/index")})).fail((function(){showMessage("Oops! Something went wrong when trying to delete your user account.","alert-danger")}))}))})); -------------------------------------------------------------------------------- /src/public/js/videoplayer.min.js: -------------------------------------------------------------------------------- 1 | const VideoController=(()=>{const e=document.getElementById("videoplayer");let t=!1,n=()=>{},a=()=>{},r=()=>{},i=()=>{},o=()=>{};if(e){n=()=>e.play(),a=()=>{e.pause(),e.currentTime=0},r=n=>{t=!(e.paused&&!t)&&n,e.pause()},i=()=>{t&&(n(),t=!1)},o=()=>{e.pause(),e.currentTime=0,n()};const l=()=>{const e=1e3*document.getElementById("videoplayer").currentTime,t=document.querySelectorAll("#text span");let n=null;t.forEach((t=>{parseFloat(t.getAttribute("data-start"))e.classList.remove("video-reading-line"))),n.classList.add("video-reading-line"),n.scrollIntoView({behavior:"auto",block:"center",inline:"center"}))};e.addEventListener("loadedmetadata",l),e.addEventListener("timeupdate",(()=>{l()}))}return{play:n,stop:a,pause:r,resume:i,playFromBeginning:o}})(); -------------------------------------------------------------------------------- /src/public/js/wordselection.min.js: -------------------------------------------------------------------------------- 1 | const WordSelection=(()=>{let t=null;return{setupEvents:function({actionBtns:e,controller:n,linkBuilder:o}){let i=!1,r=-1,s=-1,c=null,u=null,l=null;const a=$(parent.document);function g(t){i=!1,c=t.pageX,u=t.pageY,l=setTimeout((function(){i=!0,n.pause(!0)}),500)}function h(){clearTimeout(l),c=null,u=null}function d(t){(function(t){if(null==c||null==u)return!1;const e=t.pageX-c,n=t.pageY-u;return Math.abs(e)>10||Math.abs(n)>10})(t)&&h(),i&&function(t){if(!i||r<0)return;const e=document.elementFromPoint(t.clientX,t.clientY),n=$(e).closest("a");n.length&&(s=TextProcessor.getAnchorIndex(n),TextHighlighter.removeAll(),TextHighlighter.addSelection(r,s))}(t)}function f(){if(i&&r>=0&&s>=0){TextProcessor.getAnchorsList().eq(r).parent()[0]===TextProcessor.getAnchorsList().eq(s).parent()[0]&&(t=TextHighlighter.getSelection(r,s),e.show(t))}h(),i=!1,r=-1,s=-1}a.on("contextmenu",(function(e){if(e.preventDefault(),e.stopPropagation(),$(e.target).is("#text .word")&&"mouse"===e.pointerType){n.pause(!1),t=$(e.target);const i=Dictionaries.getURIs();openInNewTab(o(i.translator,t)),h()}return!1})),a.on("click","#text .word",(function(o){i||(t=$(this),TextHighlighter.removeAll(),t.addClass("highlighted"),e.hide(),n.pause(!0),e.show(t))})),a.on("mousedown touchstart","#text .word",(function(t){r=TextProcessor.getAnchorIndex($(this))})),a.on("mousedown","#text .word",(function(t){g(t)})),a.on("mousemove","#text .word",(function(t){d(t)})),a.on("mouseup","#text .word",(function(t){f()})),a.on("touchstart","#text .word",(function(t){g(t.originalEvent.touches[0])})),a.on("touchmove","#text .word",(function(t){d(t.originalEvent.touches[0])})),a.on("touchend","#text .word",(function(t){f()})),a.on("mouseup touchend",(function(t){if($("#action-buttons").is(":visible")){const o=$(t.target).is(".word"),i=$(t.target).closest(".btn").length>0,r=$(t.target).closest(".offcanvas").length>0,s=$(t.target).closest(".modal").length>0;o||i||r||s||(t.stopPropagation(),TextHighlighter.removeAll(),e.hide(),n.resume())}}))},get:()=>t}})(); -------------------------------------------------------------------------------- /src/public/js/ytvideoplayer.min.js: -------------------------------------------------------------------------------- 1 | const tag=document.createElement("script"),video_element=document.getElementById("videoplayer"),ytId=video_element.dataset.ytid;tag.src="https://www.youtube.com/iframe_api";const first_script_tag=document.getElementsByTagName("script")[0];first_script_tag.parentNode.insertBefore(tag,first_script_tag);let VideoController={};function onPlayerStateChange(e){const o=Array.from(document.querySelectorAll("#text span"));let t=0;e.data===YT.PlayerState.PLAYING?VideoController.timer_id=setInterval((()=>{t=VideoController.instance.getCurrentTime();const e=o.filter((e=>parseFloat(e.dataset.start)e.classList.remove("video-reading-line"))),e.classList.add("video-reading-line"),e.scrollIntoView({behavior:"auto",block:"center",inline:"center"}))}),500):null!==VideoController.timer_id&&(clearInterval(VideoController.timer_id),VideoController.timer_id=null)}window.onYouTubeIframeAPIReady=function(){VideoController.instance=new YT.Player("videoplayer",{playerVars:{loop:0,controls:2,fs:0,showinfo:0,autohide:1,modestbranding:1,playsinline:1},videoId:ytId,events:{onStateChange:onPlayerStateChange}}),VideoController.resume_video=!1,VideoController.timer_id=null,VideoController.play=function(){VideoController.instance.playVideo(),VideoController.resume_video=!1},VideoController.pause=function(e){VideoController.isPaused()||(VideoController.instance.pauseVideo(),VideoController.resume_video=e)},VideoController.isPaused=function(){return VideoController.instance.getPlayerState()!==YT.PlayerState.PLAYING},VideoController.resume=function(){VideoController.resume_video&&(VideoController.play(),VideoController.resume_video=!1)}}; -------------------------------------------------------------------------------- /src/public/logout.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../Includes/dbinit.php'; // connect to database 22 | 23 | use Aprelendo\User; 24 | use Aprelendo\UserAuth; 25 | 26 | $user = new User($pdo); 27 | $user_auth = new UserAuth($user); 28 | 29 | $user_auth->logout(false); 30 | -------------------------------------------------------------------------------- /src/public/simpleheader.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | ?> 21 |
22 |
23 | 46 |
47 | -------------------------------------------------------------------------------- /src/public/sources.php: -------------------------------------------------------------------------------- 1 | . 19 | */ 20 | 21 | require_once '../Includes/dbinit.php'; // connect to database 22 | require_once APP_ROOT . 'Includes/checklogin.php'; // check if logged in and set $user 23 | require_once PUBLIC_PATH . 'head.php'; 24 | require_once PUBLIC_PATH . 'header.php'; 25 | ?> 26 | 27 |
28 |
29 |
30 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 | 52 | --------------------------------------------------------------------------------