Skip to content

Commit

Permalink
New extension WordHighlighter - for highlighting words. (#238)
Browse files Browse the repository at this point in the history
* init commit for new extension WordHighlighter - for highlighting words.

* use `json` for storing configuration, add more configuration options (enable_in_article, enable_logs, case_sensitive, separate_word_search)  and refactored & simplified code

* fix: try to fix test errors

* fix: try to fix tests

* fix unnecessary casting to string

* fix: try to fix PHPStan errors, mostly: Cannot access offset '...' on mixed. And add missing types.

* fix: try to fix "Cannot access offset '...' on mixed."

* fix typo in comment

* refactor: remove $lineSeparator and replace `explode` with `preg_split`

* i18n: fr

---------

Co-authored-by: Lukáš Melega <[email protected]>
Co-authored-by: Alexandre Alapetite <[email protected]>
  • Loading branch information
3 people authored Aug 1, 2024
1 parent f845a6d commit 31bbdb5
Show file tree
Hide file tree
Showing 15 changed files with 1,053 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@ There are some FreshRSS extensions out there, developed by community members:
### By [@kalvn](https://github.com/kalvn)

* [Mark Previous as Read](https://github.com/kalvn/freshrss-mark-previous-as-read): Adds a button in the footer of each entry. Clicking this button will mark all previous entries belonging to the current feed, as read.

### By [@lukasMega](https://github.com/lukasMega)

* [Word Highlighter](https://github.com/lukasMega/Extensions-FreshRSS-): Gives you ability to highlight user-defined words (using [mark.js](https://github.com/julkue/mark.js))
11 changes: 11 additions & 0 deletions extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,17 @@
"method": "git",
"directory": "."
},
{
"name": "Word Highlighter",
"author": "Lukas Melega",
"description": "Highlighting user-defined words",
"version": "0.0.2",
"entrypoint": "WordHighlighter",
"type": "user",
"url": "https://github.com/FreshRSS/Extensions",
"method": "git",
"directory": "xExtension-WordHighlighter"
},
{
"name": "White List",
"author": "Alexis Degrugillier",
Expand Down
662 changes: 662 additions & 0 deletions xExtension-WordHighlighter/LICENSE

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions xExtension-WordHighlighter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# WordHighlighter extension

A FreshRSS extension which give ability to highlight user-defined words.

## Usage

To use it, upload this directory in your `./extensions` directory and enable it on the extension panel in FreshRSS. You can add words to be highlighted by clicking on the manage button ⚙️.
See also official docs at freshrss.github.io/FreshRSS/en/admins/15_extensions.html

## Preview

Light theme:

![snapshot](./snapshot.png)

<!-- markdownlint-disable -->
<details>
<summary>click to see example screenshot in dark theme</summary>

![snapshot-dark-theme](./snapshot-dark.png)

</details>
<!-- markdownlint-enable -->

## Changelog

- 0.0.2 use `json` for storing configuration, add more configuration options
(enable_in_article, enable_logs, case_sensitive, separate_word_search)
and refactored & simplified code
- 0.0.1 initial version (as a proper FreshRSS extension)
88 changes: 88 additions & 0 deletions xExtension-WordHighlighter/configure.phtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/** @var WordHighlighterExtension $this */
?>
<form action="<?= _url('extension', 'configure', 'e', urlencode($this->getName())) ?>" method="post">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />

<div class="form-group">
<label class="group-name" for="words_list">
<?= _t('ext.word_highlighter.write_words') ?> <br />
<?= _t('ext.word_highlighter.write_words_more') ?>
</label>
<div class="group-controls">
<textarea name="words_list"
id="words_list"><?= $this->word_highlighter_conf ?></textarea>
</div>
</div>

<div class="form-group">
<label for="enable-in-article" class="group-name">
<?= _t('ext.word_highlighter.enable_in_article') ?>
</label>
<div class="group-controls">
<?php if ($this->enable_in_article == '') { ?>
<input type="checkbox" name="enable-in-article" id="enable-in-article"></input>
<?php } else { ?>
<input type="checkbox" name="enable-in-article" id="enable-in-article" checked="true"></input>
<?php } ?>
<i><?= _t('ext.word_highlighter.enable_in_article_more') ?></i>
</div>
</div>

<details>
<summary>Click to see more advanced options</summary>

<div class="form-group">
<label for="case_sensitive" class="group-name">
<?= _t('ext.word_highlighter.case_sensitive') ?>
</label>
<div class="group-controls">
<?php if ($this->case_sensitive == '') { ?>
<input type="checkbox" name="case_sensitive" id="case_sensitive"></input>
<?php } else { ?>
<input type="checkbox" name="case_sensitive" id="case_sensitive" checked="true"></input>
<?php } ?>
</div>
</div>

<div class="form-group">
<label for="separate_word_search" class="group-name">
<?= _t('ext.word_highlighter.separate_word_search') ?>
</label>
<div class="group-controls">
<?php if ($this->separate_word_search == '') { ?>
<input type="checkbox" name="separate_word_search" id="separate_word_search"></input>
<?php } else { ?>
<input type="checkbox" name="separate_word_search" id="separate_word_search" checked="true"></input>
<?php } ?>
</div>
</div>

<div class="form-group">
<label for="enable_logs" class="group-name">
<?= _t('ext.word_highlighter.enable_logs') ?>
</label>
<div class="group-controls">
<?php if ($this->enable_logs == '') { ?>
<input type="checkbox" name="enable_logs" id="enable_logs"></input>
<?php } else { ?>
<input type="checkbox" name="enable_logs" id="enable_logs" checked="true"></input>
<?php } ?>
</div>

</div>
</details>

<div class="form-group form-actions">
<?php if ($this->permission_problem !== '') { ?>
<p class="alert alert-error"><?= _t('ext.word_highlighter.permission_problem', $this->permission_problem) ?></p>
<?php } else { ?>
<div class="group-controls">
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
<?php } ?>
</div>

</form>
102 changes: 102 additions & 0 deletions xExtension-WordHighlighter/extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

final class WordHighlighterExtension extends Minz_Extension
{
const JSON_ENCODE_CONF = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR;

public string $word_highlighter_conf = 'test';
public string $permission_problem = '';
public bool $enable_in_article = false;
public bool $enable_logs = false;
public bool $case_sensitive = false;
public bool $separate_word_search = false;

#[\Override]
public function init(): void
{
$this->registerTranslates();

// register CSS for WordHighlighter:
Minz_View::appendStyle($this->getFileUrl('style.css', 'css'));

Minz_View::appendScript($this->getFileUrl('mark.min.js', 'js'), false, false, false);

$current_user = Minz_Session::paramString('currentUser');

$staticPath = join_path($this->getPath(), 'static');
$configFileJs = join_path($staticPath, ('config.' . $current_user . '.js'));

if (file_exists($configFileJs)) {
Minz_View::appendScript($this->getFileUrl(('config.' . $current_user . '.js'), 'js'));
}

Minz_View::appendScript($this->getFileUrl('word-highlighter.js', 'js'));
}

#[\Override]
public function handleConfigureAction(): void
{
$this->registerTranslates();

$current_user = Minz_Session::paramString('currentUser');
$staticPath = join_path($this->getPath(), 'static');

$configFileJson = join_path($staticPath, ('config.' . $current_user . '.json'));

if (!file_exists($configFileJson) && !is_writable($staticPath)) {
$tmpPath = explode(EXTENSIONS_PATH . '/', $staticPath);
$this->permission_problem = $tmpPath[1] . '/';

} elseif (file_exists($configFileJson) && !is_writable($configFileJson)) {
$tmpPath = explode(EXTENSIONS_PATH . '/', $configFileJson);
$this->permission_problem = $tmpPath[1];

} elseif (Minz_Request::isPost()) {
$configWordList = html_entity_decode(Minz_Request::paramString('words_list'));

$this->word_highlighter_conf = $configWordList;
$this->enable_in_article = (bool) Minz_Request::paramString('enable-in-article');
$this->enable_logs = (bool) Minz_Request::paramString('enable_logs');
$this->case_sensitive = (bool) Minz_Request::paramString('case_sensitive');
$this->separate_word_search = (bool) Minz_Request::paramString('separate_word_search');

$configObj = [
'enable_in_article' => $this->enable_in_article,
'enable_logs' => $this->enable_logs,
'case_sensitive' => $this->case_sensitive,
'separate_word_search' => $this->separate_word_search,
'words' => preg_split("/\r\n|\n|\r/", $configWordList),
];
$configJson = json_encode($configObj, WordHighlighterExtension::JSON_ENCODE_CONF);
file_put_contents(join_path($staticPath, ('config.' . $current_user . '.json')), $configJson . PHP_EOL);
file_put_contents(join_path($staticPath, ('config.' . $current_user . '.js')), $this->jsonToJs($configJson) . PHP_EOL);
}

if (file_exists($configFileJson)) {
try {
$confJson = json_decode(file_get_contents($configFileJson) ?: '', true, 8, JSON_THROW_ON_ERROR);
if (json_last_error() !== JSON_ERROR_NONE || !is_array($confJson)) {
return;
}
$this->enable_in_article = (bool) ($confJson['enable_in_article'] ?? false);
$this->enable_logs = (bool) ($confJson['enable_logs'] ?? false);
$this->case_sensitive = (bool) ($confJson['case_sensitive'] ?? false);
$this->separate_word_search = (bool) ($confJson['separate_word_search'] ?? false);
$this->word_highlighter_conf = implode("\n", (array) ($confJson['words'] ?? []));

} catch (Exception $exception) {
// probably nothing to do needed
}
}
}

private function jsonToJs(string $jsonStr): string
{
$js = "window.WordHighlighterConf = " .
$jsonStr . ";\n" .
"window.WordHighlighterConf.enable_logs && console.log('WordHighlighter: loaded user config:', window.WordHighlighterConf);";
return $js;
}
}
15 changes: 15 additions & 0 deletions xExtension-WordHighlighter/i18n/en/ext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

return array(
'word_highlighter' => array(
'write_words' => 'Words to highlight',
'write_words_more' => '(separated by newline)',
'enable_in_article' => 'Enable highlighting also in article',
'enable_in_article_more' => '(⚠️ may be slower with a lot of words)',
'enable_logs' => 'Enable logs',
'case_sensitive' => 'Case sensitive',
'separate_word_search' => 'Separate word search',
'test_highlighting_word' => 'highlight',
'permission_problem' => 'Your config file is not writable, please change the file permissions for %s',
),
);
15 changes: 15 additions & 0 deletions xExtension-WordHighlighter/i18n/fr/ext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

return array(
'word_highlighter' => array(
'write_words' => 'Mots à surligner',
'write_words_more' => '(séparés par une nouvelle ligne)',
'enable_in_article' => 'Activer la mise en évidence également dans l’article',
'enable_in_article_more' => '(⚠️ peut être plus lent avec beaucoup de mots)',
'enable_logs' => 'Activer les journaux',
'case_sensitive' => 'Sensible à la casse',
'separate_word_search' => 'Recherche de mots séparés',
'test_highlighting_word' => 'surligner',
'permission_problem' => 'Votre fichier de configuration n’est pas accessible en écriture, veuillez modifier les permissions du fichier %s',
),
);
8 changes: 8 additions & 0 deletions xExtension-WordHighlighter/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "Word highlighter",
"author": "Lukas Melega",
"description": "Highlight specific words",
"version": "0.0.2",
"entrypoint": "WordHighlighter",
"type": "user"
}
Binary file added xExtension-WordHighlighter/snapshot-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added xExtension-WordHighlighter/snapshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions xExtension-WordHighlighter/static/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
config-words.*.js
config-words.*.txt
Loading

0 comments on commit 31bbdb5

Please sign in to comment.