From 49766ab01f24d1be5743c353b9a6d4d09f55f5b8 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Thu, 27 Mar 2025 22:10:16 +0300 Subject: filter test dialog improvements: - properly return results for filter rules matching specific feeds or categories - fix test results never returned for Uncategorized - show tooltip with specific word match and matched rule on resulting item hover --- js/CommonFilters.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'js') diff --git a/js/CommonFilters.js b/js/CommonFilters.js index b7bb69a15..83bc4cae9 100644 --- a/js/CommonFilters.js +++ b/js/CommonFilters.js @@ -19,6 +19,9 @@ const Filters = { ACTION_REMOVE_TAG: 10, PARAM_ACTIONS: [4, 6, 7, 9, 10], filter_info: {}, + formatMatchedRules: function(rules) { + return rules.map((r) => r.regexp_matches[0] + ' - ' + r.reg_exp).join('\n'); + }, test: function() { const test_dialog = new fox.SingleUseDialog({ title: "Test Filter", @@ -43,8 +46,8 @@ const Filters = { .replace("%f", test_dialog.results) .replace("%d", offset); - results_list.innerHTML += result.items.reduce((current, item) => current + `
  • ${item.title}
    - ${item.feed_title}, ${item.date} + results_list.innerHTML += result.items.reduce((current, item) => current + `
  • ${item.title} + — ${item.feed_title}, ${item.date}
    ${item.content_preview}
  • `, ''); // get the next batch if there may be more available and testing limits haven't been reached -- cgit v1.2.3-54-g00ecf From 8986a3e7ee4118b7fae9d93c09c870e72de0e49d Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 28 Mar 2025 07:59:46 +0300 Subject: add limited highlighting of filter test results based on matched rules --- classes/Pref_Filters.php | 36 ++++++++++++++++++-- classes/Sanitizer.php | 89 +++++++++++++++++++++++++++++++++--------------- js/CommonFilters.js | 2 +- phpstan.neon | 1 + 4 files changed, 97 insertions(+), 31 deletions(-) (limited to 'js') diff --git a/classes/Pref_Filters.php b/classes/Pref_Filters.php index 1ca542285..a7528865a 100644 --- a/classes/Pref_Filters.php +++ b/classes/Pref_Filters.php @@ -176,11 +176,43 @@ class Pref_Filters extends Handler_Protected { }, $entry, $excerpt_length); + $matches = []; + + $content_preview = $entry["content_preview"]; + $content_title = $entry["title"]; + + // is it even possible to have multiple matched rules here? + foreach ($matched_rules as $rule) { + $can_highlight_content = false; + $can_highlight_title = false; + + $matches[] = $rule['regexp_matches'][0]; + + switch ($rule['type']) { + case "both": + $can_highlight_title = true; + $can_highlight_content = true; + break; + case "title": + $can_highlight_title = true; + break; + case "content": + $can_highlight_content = true; + break; + } + + if ($can_highlight_content) + $content_preview = Sanitizer::highlight_words_str($content_preview, $matches); + + if ($can_highlight_title) + $content_title = Sanitizer::highlight_words_str($content_title, $matches); + } + $rv['items'][] = [ - 'title' => $entry['title'], + 'title' => $content_title, 'feed_title' => $entry['feed_title'], 'date' => mb_substr($entry['date_entered'], 0, 16), - 'content_preview' => $entry['content_preview'], + 'content_preview' => $content_preview, 'matched_rules' => $matched_rules, ]; } diff --git a/classes/Sanitizer.php b/classes/Sanitizer.php index 94d6fe621..2ae07d8d3 100644 --- a/classes/Sanitizer.php +++ b/classes/Sanitizer.php @@ -59,6 +59,65 @@ class Sanitizer { return parse_url(Config::get_self_url(), PHP_URL_SCHEME) == 'https'; } + /** @param array $words */ + public static function highlight_words_str(string $str, array $words) : string { + $doc = new DOMDocument(); + + if ($doc->loadHTML('' . $str . '')) { + $xpath = new DOMXPath($doc); + + if (self::highlight_words($doc, $xpath, $words)) { + $res = $doc->saveHTML(); + + /* strip everything outside of ... */ + $res_frag = array(); + + if (preg_match('/(.*)<\/body>/is', $res, $res_frag)) { + return $res_frag[1]; + } else { + return $res; + } + } + } + + return $str; + } + + /** @param array $words */ + public static function highlight_words(DOMDocument &$doc, DOMXPath $xpath, array $words) : bool { + $rv = false; + + foreach ($words as $word) { + + // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph + $elements = $xpath->query("//*/text()"); + + foreach ($elements as $child) { + + $fragment = $doc->createDocumentFragment(); + $text = $child->textContent; + + while (($pos = mb_stripos($text, $word)) !== false) { + $fragment->appendChild(new DOMText(mb_substr($text, 0, (int)$pos))); + $word = mb_substr($text, (int)$pos, mb_strlen($word)); + $highlight = $doc->createElement('span'); + $highlight->appendChild(new DOMText($word)); + $highlight->setAttribute('class', 'highlight'); + $fragment->appendChild($highlight); + $text = mb_substr($text, $pos + mb_strlen($word)); + } + + if (!empty($text)) $fragment->appendChild(new DOMText($text)); + + $child->parentNode->replaceChild($fragment, $child); + + $rv = true; + } + } + + return $rv; + } + /** * @param array|null $highlight_words Words to highlight in the HTML output. * @@ -197,34 +256,8 @@ class Sanitizer { $div->appendChild($entry); } - if (is_array($highlight_words)) { - foreach ($highlight_words as $word) { - - // http://stackoverflow.com/questions/4081372/highlight-keywords-in-a-paragraph - - $elements = $xpath->query("//*/text()"); - - foreach ($elements as $child) { - - $fragment = $doc->createDocumentFragment(); - $text = $child->textContent; - - while (($pos = mb_stripos($text, $word)) !== false) { - $fragment->appendChild(new DOMText(mb_substr($text, 0, (int)$pos))); - $word = mb_substr($text, (int)$pos, mb_strlen($word)); - $highlight = $doc->createElement('span'); - $highlight->appendChild(new DOMText($word)); - $highlight->setAttribute('class', 'highlight'); - $fragment->appendChild($highlight); - $text = mb_substr($text, $pos + mb_strlen($word)); - } - - if (!empty($text)) $fragment->appendChild(new DOMText($text)); - - $child->parentNode->replaceChild($fragment, $child); - } - } - } + if (is_array($highlight_words)) + self::highlight_words($doc, $xpath, $highlight_words); $res = $doc->saveHTML(); diff --git a/js/CommonFilters.js b/js/CommonFilters.js index 83bc4cae9..f1f0e9e0c 100644 --- a/js/CommonFilters.js +++ b/js/CommonFilters.js @@ -20,7 +20,7 @@ const Filters = { PARAM_ACTIONS: [4, 6, 7, 9, 10], filter_info: {}, formatMatchedRules: function(rules) { - return rules.map((r) => r.regexp_matches[0] + ' - ' + r.reg_exp).join('\n'); + return rules.map((r) => r.regexp_matches[0] + ' - ' + r.reg_exp + ' (' + r.type + ')').join('\n'); }, test: function() { const test_dialog = new fox.SingleUseDialog({ diff --git a/phpstan.neon b/phpstan.neon index 74daee0ce..5bd18c58d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -32,6 +32,7 @@ parameters: - plugins.local/**/tests/* - plugins.local/*/vendor/intervention/* - plugins.local/*/vendor/psr/log/* + - plugins.local/af_readability/* - plugins.local/cache_s3/vendor/* - plugins/**/test/* - plugins/**/Test/* -- cgit v1.2.3-54-g00ecf From b4962b670d02f7c5d4da3cafe99cffffe059e8a1 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 30 Mar 2025 20:21:06 +0300 Subject: stop sending matched content twice for the tooltip, use smaller objects containing only regular expression and rule type --- classes/Pref_Filters.php | 8 +++++++- js/CommonFilters.js | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'js') diff --git a/classes/Pref_Filters.php b/classes/Pref_Filters.php index e30a1468c..d5844b902 100644 --- a/classes/Pref_Filters.php +++ b/classes/Pref_Filters.php @@ -171,6 +171,7 @@ class Pref_Filters extends Handler_Protected { $content_preview = ""; $matches = []; + $rules = []; $entry_title = $entry["title"]; @@ -184,6 +185,11 @@ class Pref_Filters extends Handler_Protected { $matches[] = $rule_regexp_match; + $rules[] = [ + 'reg_exp' => $rule['reg_exp'], + 'type' => $rule['type'] + ]; + if (in_array($rule['type'], ['content', 'both'])) { // also stripping [\r\n\t] to match what's done for content in RSSUtils#get_article_filters() $entry_content_text = strip_tags(preg_replace("/[\r\n\t]/", "", $entry["content"])); @@ -232,7 +238,7 @@ class Pref_Filters extends Handler_Protected { 'feed_title' => $entry['feed_title'], 'date' => mb_substr($entry['date_entered'], 0, 16), 'content_preview' => $content_preview, - 'matched_rules' => $matched_rules, // TODO shorten object passed to frontend + 'rules' => $rules ]; } } diff --git a/js/CommonFilters.js b/js/CommonFilters.js index f1f0e9e0c..ce871a2af 100644 --- a/js/CommonFilters.js +++ b/js/CommonFilters.js @@ -20,7 +20,7 @@ const Filters = { PARAM_ACTIONS: [4, 6, 7, 9, 10], filter_info: {}, formatMatchedRules: function(rules) { - return rules.map((r) => r.regexp_matches[0] + ' - ' + r.reg_exp + ' (' + r.type + ')').join('\n'); + return rules.map((r) => r.reg_exp + ' (' + r.type + ')').join('\n'); }, test: function() { const test_dialog = new fox.SingleUseDialog({ @@ -46,7 +46,7 @@ const Filters = { .replace("%f", test_dialog.results) .replace("%d", offset); - results_list.innerHTML += result.items.reduce((current, item) => current + `
  • ${item.title} + results_list.innerHTML += result.items.reduce((current, item) => current + `
  • ${item.title}${item.feed_title}, ${item.date}
    ${item.content_preview}
  • `, ''); -- cgit v1.2.3-54-g00ecf From 2d041f7d28ce555bffaf1b99eea5d0199e8a3c29 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 30 Mar 2025 20:41:50 +0300 Subject: use server-side localized formatting for matching rule to display as a tooltip (for now) --- classes/Pref_Filters.php | 20 ++++++++++---------- js/CommonFilters.js | 5 +---- 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'js') diff --git a/classes/Pref_Filters.php b/classes/Pref_Filters.php index d5844b902..a6063d898 100644 --- a/classes/Pref_Filters.php +++ b/classes/Pref_Filters.php @@ -95,7 +95,6 @@ class Pref_Filters extends Handler_Protected { if (is_array($rule)) { $rule['type'] = $filter_types[$rule['filter_type']]; - unset($rule['filter_type']); array_push($filter['rules'], $rule); $scope_inner_qparts = []; @@ -185,10 +184,7 @@ class Pref_Filters extends Handler_Protected { $matches[] = $rule_regexp_match; - $rules[] = [ - 'reg_exp' => $rule['reg_exp'], - 'type' => $rule['type'] - ]; + $rules[] = self::_get_rule_name($rule, ''); if (in_array($rule['type'], ['content', 'both'])) { // also stripping [\r\n\t] to match what's done for content in RSSUtils#get_article_filters() @@ -452,7 +448,7 @@ class Pref_Filters extends Handler_Protected { /** * @param array|null $rule */ - private function _get_rule_name(?array $rule = null): string { + private function _get_rule_name(?array $rule = null, string $format = 'html'): string { if (!$rule) $rule = json_decode(clean($_REQUEST["rule"]), true); $feeds = $rule["feed_id"]; @@ -487,10 +483,14 @@ class Pref_Filters extends Handler_Protected { $inverse = isset($rule["inverse"]) ? "inverse" : ""; - return "" . - T_sprintf("%s on %s in %s %s", htmlspecialchars($rule["reg_exp"]), - "$filter_type", "$feed", isset($rule["inverse"]) ? __("(inverse)") : "") . ""; - } + if ($format === 'html') + return "" . + T_sprintf("%s on %s in %s %s", htmlspecialchars($rule["reg_exp"]), + "$filter_type", "$feed", isset($rule["inverse"]) ? __("(inverse)") : "") . ""; + else + return T_sprintf("%s on %s in %s %s", $rule["reg_exp"], + $filter_type, $feed, isset($rule["inverse"]) ? __("(inverse)") : ""); + } function printRuleName(): void { print $this->_get_rule_name(json_decode(clean($_REQUEST["rule"]), true)); diff --git a/js/CommonFilters.js b/js/CommonFilters.js index ce871a2af..9c0fc5cce 100644 --- a/js/CommonFilters.js +++ b/js/CommonFilters.js @@ -19,9 +19,6 @@ const Filters = { ACTION_REMOVE_TAG: 10, PARAM_ACTIONS: [4, 6, 7, 9, 10], filter_info: {}, - formatMatchedRules: function(rules) { - return rules.map((r) => r.reg_exp + ' (' + r.type + ')').join('\n'); - }, test: function() { const test_dialog = new fox.SingleUseDialog({ title: "Test Filter", @@ -46,7 +43,7 @@ const Filters = { .replace("%f", test_dialog.results) .replace("%d", offset); - results_list.innerHTML += result.items.reduce((current, item) => current + `
  • ${item.title} + results_list.innerHTML += result.items.reduce((current, item) => current + `
  • ${item.title}${item.feed_title}, ${item.date}
    ${item.content_preview}
  • `, ''); -- cgit v1.2.3-54-g00ecf