From 7591b10219222f818d523f457fa6d01e1891260b Mon Sep 17 00:00:00 2001 From: sysadminstory Date: Tue, 22 Aug 2023 20:44:36 +0200 Subject: [PATCH] [Core] New feature : User Interface to "Detect" Feed from an URL (#3436) * [Core] New feature : User Interface to "Detect" Feed from an URL Detect Action has been expanded to support returning a Feed in a JSON format instead of a Redirect. Existing usage of the Detect action will keep working as usual. Frontpage template has now a section to display the Feed detection result, and a button to start the Feed Detection. A new JS file contains the necessary JS (Ajax and Event management) to fill the Feed Detection section. * Coding policy fixes * [Core] New feature : User Interface to "Detect" Feed from an URL - Switch from old school XMLHttpRequest to fetch - Enhance UX of search results - Revert to it's original content - Switch to a new Action : FindfeedAction.php - Switch to template literals instead of string concatenation - FindFeed action could retrun multiple feeds - Results are sent with an absolute URL - Switch to Json::encode() helper function * [Core] New feature : User Interface to "Detect" Feed from an URL - Move specific JS code to rss-bridge.js - Change HTML tag for the button to have a consistant style with th rest of the page * [Core] New feature : User Interface to "Detect" Feed from an URL - If no context is sent, assume there is only one unnamed context - Find parameter name in global and currect context * fix * remove typo --------- Co-authored-by: Dag --- actions/FindfeedAction.php | 89 ++++++++++++++++++++++++++++++++++++ static/rss-bridge.js | 79 ++++++++++++++++++++++++++++++++ static/style.css | 35 ++++++++++++++ templates/frontpage.html.php | 9 ++++ 4 files changed, 212 insertions(+) create mode 100644 actions/FindfeedAction.php diff --git a/actions/FindfeedAction.php b/actions/FindfeedAction.php new file mode 100644 index 00000000..25fe4714 --- /dev/null +++ b/actions/FindfeedAction.php @@ -0,0 +1,89 @@ +getBridgeClassNames() as $bridgeClassName) { + if (!$bridgeFactory->isEnabled($bridgeClassName)) { + continue; + } + + $bridge = $bridgeFactory->create($bridgeClassName); + + $bridgeParams = $bridge->detectParameters($targetURL); + + if ($bridgeParams === null) { + continue; + } + + // It's allowed to have no 'context' in a bridge (only a default context without any name) + // In this case, the reference to the parameters are found in the first element of the PARAMETERS array + + $context = $bridgeParams['context'] ?? 0; + + $bridgeData = []; + // Construct the array of parameters + foreach ($bridgeParams as $key => $value) { + // 'context' is a special case : it's a bridge parameters, there is no "name" for this parameter + if ($key == 'context') { + $bridgeData[$key]['name'] = 'Context'; + $bridgeData[$key]['value'] = $value; + } else { + $bridgeData[$key]['name'] = $this->getParameterName($bridge, $context, $key); + $bridgeData[$key]['value'] = $value; + } + } + + $bridgeParams['bridge'] = $bridgeClassName; + $bridgeParams['format'] = $format; + $content = [ + 'url' => get_home_page_url() . '?action=display&' . http_build_query($bridgeParams), + 'bridgeParams' => $bridgeParams, + 'bridgeData' => $bridgeData, + 'bridgeMeta' => [ + 'name' => $bridge::NAME, + 'description' => $bridge::DESCRIPTION, + 'parameters' => $bridge::PARAMETERS, + 'icon' => $bridge->getIcon(), + ], + ]; + $results[] = $content; + } + if ($results === []) { + return new Response(Json::encode(['message' => 'No bridge found for given url']), 404, ['content-type' => 'application/json']); + } + return new Response(Json::encode($results), 200, ['content-type' => 'application/json']); + } + + // Get parameter name in the actual context, or in the global parameter + private function getParameterName($bridge, $context, $key) + { + if (isset($bridge::PARAMETERS[$context][$key]['name'])) { + $name = $bridge::PARAMETERS[$context][$key]['name']; + } else if (isset($bridge::PARAMETERS['global'][$key]['name'])) { + $name = $bridge::PARAMETERS['global'][$key]['name']; + } else { + $name = 'Variable "' . $key . '" (No name provided)'; + } + return $name; + } +} diff --git a/static/rss-bridge.js b/static/rss-bridge.js index 498acd37..82069d8c 100644 --- a/static/rss-bridge.js +++ b/static/rss-bridge.js @@ -47,3 +47,82 @@ function rssbridge_toggle_bridge(){ bridge.getElementsByClassName('showmore-box')[0].checked = true; } } + +var rssbridge_feed_finder = (function() { + /* + * Code for "Find feed by URL" feature + */ + + // Start the Feed search + async function rssbridge_feed_search(event) { + const input = document.getElementById('searchfield'); + let content = input.value; + if (content) { + const findfeedresults = document.getElementById('findfeedresults'); + findfeedresults.innerHTML = 'Searching for matching feeds ...'; + let baseurl = window.location.protocol + window.location.pathname; + let url = baseurl + '?action=findfeed&format=Html&url=' + content; + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + rss_bridge_feed_display_found_feed(data); + } else { + rss_bridge_feed_display_feed_search_fail(); + } + } else { + rss_bridge_feed_display_find_feed_empty(); + } + } + + // Display the found feeds + function rss_bridge_feed_display_found_feed(obj) { + const findfeedresults = document.getElementById('findfeedresults'); + + let content = 'Found Feed(s) :'; + + // Let's go throug every Feed found + for (const element of obj) { + content += `
+
+ +
+
+

${element.bridgeMeta.name}

+

+ ${element.bridgeMeta.description} +

+
+
    `; + + // Now display every Feed parameter + for (const param in element.bridgeData) { + content += `
  • ${element.bridgeData[param].name} : ${element.bridgeData[param].value}
  • `; + } + content += `
+
+
`; + } + content += '

'; + findfeedresults.innerHTML = content; + } + + // Display an error if no feed were found + function rss_bridge_feed_display_feed_search_fail() { + const findfeedresults = document.getElementById('findfeedresults'); + findfeedresults.innerHTML = 'No Feed found !'; + } + + // Empty the Found Feed section + function rss_bridge_feed_display_find_feed_empty() { + const findfeedresults = document.getElementById('findfeedresults'); + findfeedresults.innerHTML = ''; + } + + // Add Event to 'Detect Feed" button + var rssbridge_feed_finder = function() { + const button = document.getElementById('findfeed'); + button.addEventListener("click", rssbridge_feed_search); + button.addEventListener("keyup", rssbridge_feed_search); + }; + return rssbridge_feed_finder; +}()); diff --git a/static/style.css b/static/style.css index a83e25e4..a9d5933a 100644 --- a/static/style.css +++ b/static/style.css @@ -453,3 +453,38 @@ button { color: #d8d3cb; } } + +/* find-feed */ +.search-result { + background-color: #f0f0f0; + border-radius: 5px; + padding: 15px; + display: flex; + position: relative; + text-align: left; +} +@media (prefers-color-scheme: dark) { + .search-result { + background-color: #202325; + } +} +.search-result h2 { + color: #288cfc; +} + +.search-result a { + text-decoration: none; + color: #248afa; +} +.search-result .icon { + margin: 0 15px 0 0; +} +.search-result span { + margin-right: 10px; +} +.search-result .description { + font-size: 110%; + margin-right: 0 !important; + margin-top: 5px !important; +} +/* end find-feed */ diff --git a/templates/frontpage.html.php b/templates/frontpage.html.php index 63f4a2ab..99e2ffd9 100644 --- a/templates/frontpage.html.php +++ b/templates/frontpage.html.php @@ -2,6 +2,7 @@