diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9f20426f..44bc1759 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,4 +3,10 @@ FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} RUN npm install -g pnpm +RUN apt-get update \ + && apt-get -y install --no-install-recommends \ + python3-pip \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + ENV PATH="${PATH}:./node_modules/.bin" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 15d1b9f3..06e7f6ee 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,27 +1,26 @@ { - "name": "homepage", - "build": { - "dockerfile": "Dockerfile", - "args": { - "VARIANT": "18-bullseye" - } - }, - "customizations": { - "vscode": { - "extensions": [ - "dbaeumer.vscode-eslint", - "mhutchie.git-graph", - "streetsidesoftware.code-spell-checker", - ], - "settings": { - "eslint.format.enable": true, - "eslint.lintTask.enable": true, - "eslint.packageManager": "pnpm" - } - } - }, - "postCreateCommand": ".devcontainer/setup.sh", - "forwardPorts": [ - 3000 - ] + "name": "homepage", + "build": { + "dockerfile": "Dockerfile", + "args": { + "VARIANT": "18-bullseye", + }, + }, + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", + "esbenp.prettier-vscode", + ], + "settings": { + "eslint.format.enable": true, + "eslint.lintTask.enable": true, + "eslint.packageManager": "pnpm", + }, + }, + }, + "postCreateCommand": ".devcontainer/setup.sh", + "forwardPorts": [3000], } diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 70bf96cf..ea5d2fe9 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -3,6 +3,8 @@ # Install Node packages pnpm install +python3 -m pip install -r requirements.txt + # Copy in skeleton configuration if there is no existing configuration if [ ! -d "config/" ]; then echo "Adding skeleton config" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba1b1fb0..e68f9e4f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,3 +13,7 @@ updates: directory: "/" schedule: interval: "monthly" + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/repo-maintenance.yml b/.github/workflows/repo-maintenance.yml index d590cf91..d1f7e4fd 100644 --- a/.github/workflows/repo-maintenance.yml +++ b/.github/workflows/repo-maintenance.yml @@ -27,7 +27,7 @@ jobs: stale-issue-message: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + for your contributions. See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details. lock-threads: name: 'Lock Old Threads' runs-on: ubuntu-latest @@ -42,14 +42,17 @@ jobs: This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. + See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details. pr-comment: > This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. + See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details. discussion-comment: > This discussion has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. + See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details. close-answered-discussions: name: 'Close Answered Discussions' runs-on: ubuntu-latest @@ -89,7 +92,7 @@ jobs: }`; const commentVariables = { discussion: discussion.id, - body: 'This discussion has been automatically closed because it was marked as answered.', + body: 'This discussion has been automatically closed because it was marked as answered. See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.', } await github.graphql(addCommentMutation, commentVariables) @@ -179,7 +182,85 @@ jobs: }`; const commentVariables = { discussion: discussion.id, - body: 'This discussion has been automatically closed due to inactivity.', + body: 'This discussion has been automatically closed due to inactivity. See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.', + } + await github.graphql(addCommentMutation, commentVariables); + + const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) { + closeDiscussion(input:{discussionId:$discussion, reason:$reason}) { + clientMutationId + } + }`; + const closeVariables = { + discussion: discussion.id, + reason: "OUTDATED", + } + await github.graphql(closeDiscussionMutation, closeVariables); + + await sleep(1000); + } + } + close-unsupported-feature-requests: + name: 'Close Unsupported Feature Requests' + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + script: | + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + const CUTOFF_1_DAYS = 180; + const CUTOFF_1_COUNT = 5; + const CUTOFF_2_DAYS = 365; + const CUTOFF_2_COUNT = 10; + + const cutoff1Date = new Date(); + cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS); + const cutoff2Date = new Date(); + cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS); + + const query = `query( + $owner:String!, + $name:String!, + $featureRequestsCategory:ID!, + ) { + repository(owner:$owner, name:$name){ + discussions( + categoryId:$featureRequestsCategory, + last:100, + states:[OPEN], + ) { + nodes { + id, + number, + updatedAt, + upvoteCount, + } + }, + } + }`; + const variables = { + owner: context.repo.owner, + name: context.repo.repo, + featureRequestsCategory: "DIC_kwDOH31rQM4CRErS" + } + const result = await github.graphql(query, variables); + + for (const discussion of result.repository.discussions.nodes) { + const discussionDate = new Date(discussion.updatedAt); + if ((discussionDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) || + (discussionDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT)) { + console.log(`Closing discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt} with votes ${discussion.upvoteCount}`); + const addCommentMutation = `mutation($discussion:ID!, $body:String!) { + addDiscussionComment(input:{discussionId:$discussion, body:$body}) { + clientMutationId + } + }`; + const commentVariables = { + discussion: discussion.id, + body: 'This discussion has been automatically closed due to lack of community support. See our [contributing guidelines](https://github.com/gethomepage/homepage/blob/main/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.', } await github.graphql(addCommentMutation, commentVariables); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51ca1f83..f2361c43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,3 +51,18 @@ By contributing, you agree that your contributions will be licensed under its GN ## References This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/main/CONTRIBUTING.md) + +# Automatic Respoistory Maintenance + +The homepage team appreciates all effort and interest from the community in filing bug reports, creating feature requests, sharing ideas and helping other community members. That said, in an effort to keep the repository organized and managebale the project uses automatic handling of certain areas: + +- Issues that cannot be reproduced will be marked 'stale' after 7 days of inactivity and closed after 14 further days of inactivity. +- Issues, pull requests and discussions that are closed will be locked after 30 days of inactivity. +- Discussions with a marked answer will be automatically closed. +- Discussions in the 'General' or 'Support' categories will be closed after 180 days of inactivity. +- Feature requests that do not meet the following thresholds will be closed: 5 "up-votes" after 180 days of inactivity or 10 "up-votes" after 365 days. + +In all cases, threads can be re-opened by project maintainers and, of course, users can always create a new discussion for related concerns. +Finally, remember that all information remains searchable and 'closed' feature requests can still serve as inspiration for new features. + +Thank you all for your contributions. diff --git a/docs/configs/services.md b/docs/configs/services.md index 010950eb..490356fc 100644 --- a/docs/configs/services.md +++ b/docs/configs/services.md @@ -101,7 +101,7 @@ To use a local icon, first create a Docker mount to `/app/public/icons` and then ## Ping -Services may have an optional `ping` property that allows you to monitor the availability of an external host. As of v0.8.0, the ping feature attempts to use a true (ICMP) ping command on the underlying host. +Services may have an optional `ping` property that allows you to monitor the availability of an external host. As of v0.8.0, the ping feature attempts to use a true (ICMP) ping command on the underlying host. Currently, only IPv4 is supported. ```yaml - Group A: diff --git a/docs/configs/settings.md b/docs/configs/settings.md index d3e9a837..9ee86a85 100644 --- a/docs/configs/settings.md +++ b/docs/configs/settings.md @@ -229,6 +229,26 @@ disableCollapse: true By default the feature is enabled. +### Initially collapsed sections + +You can initially collapse sections by adding the `initiallyCollapsed` option to the layout group. + +```yaml +layout: + Section A: + initiallyCollapsed: true +``` + +This can also be set globaly using the `groupsInitiallyCollapsed` option. + +```yaml +groupsInitiallyCollapsed: true +``` + +The value set on a group will overwrite the global setting. + +By default the feature is disabled. + ### Use Equal Height Cards You can enable equal height cards for groups of services, this will make all cards in a row the same height. diff --git a/docs/more/development.md b/docs/more/development.md index b1252112..8e3fac13 100644 --- a/docs/more/development.md +++ b/docs/more/development.md @@ -51,6 +51,7 @@ To ensure cohesiveness of various widgets, the following should be used as a gui - Please only submit widgets that have been requested and have at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users. - Widgets should be only one row of blocks -- Widgets should be no more than 4 blocks wide +- Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets - Minimize the number of API calls - Avoid the use of custom proxy unless absolutely necessary +- Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself. diff --git a/docs/widgets/info/glances.md b/docs/widgets/info/glances.md index e6fc2a61..b7fd7efd 100644 --- a/docs/widgets/info/glances.md +++ b/docs/widgets/info/glances.md @@ -17,6 +17,7 @@ The Glances widget allows you to monitor the resources (CPU, memory, storage, te cputemp: true # disabled by default uptime: true # disabled by default disk: / # disabled by default, use mount point of disk(s) in glances. Can also be a list (see below) + diskUnits: bytes # optional, bytes (default) or bbytes. Only applies to disk expanded: true # show the expanded view label: MyMachine # optional ``` diff --git a/docs/widgets/info/resources.md b/docs/widgets/info/resources.md index 35f2177b..b4f85d69 100644 --- a/docs/widgets/info/resources.md +++ b/docs/widgets/info/resources.md @@ -22,6 +22,7 @@ _Note: unfortunately, the package used for getting CPU temp ([systeminformation] uptime: true units: imperial # only used by cpu temp refresh: 3000 # optional, in ms + diskUnits: bytes # optional, bytes (default) or bbytes. Only applies to disk ``` You can also pass a `label` option, which allows you to group resources under named sections, diff --git a/docs/widgets/services/customapi.md b/docs/widgets/services/customapi.md index d392f0a9..7f26f80f 100644 --- a/docs/widgets/services/customapi.md +++ b/docs/widgets/services/customapi.md @@ -16,6 +16,8 @@ widget: password: password # auth - optional method: GET # optional, e.g. POST headers: # optional, must be object, see below + requestBody: # optional, can be string or object, see below + display: # optional, default to block, see below mappings: - field: key # needs to be YAML string or object label: Field 1 @@ -43,6 +45,15 @@ widget: locale: nl # optional style: short # optional - defaults to "long". Allowed values: `["long", "short", "narrow"]`. numeric: auto # optional - defaults to "always". Allowed values `["always", "auto"]`. + - field: key + label: Field 6 + format: text + additionalField: # optional + field: + hourly: + time: other key + color: theme # optional - defaults to "". Allowed values: `["theme", "adaptive", "black", "white"]`. + format: date # optional ``` Supported formats for the values are `text`, `number`, `float`, `percent`, `bytes`, `bitrate`, `date` and `relativeDate`. @@ -93,7 +104,7 @@ mappings: ## Data Transformation -You can manipulate data with the following tools `remap`, `scale` and `suffix`, for example: +You can manipulate data with the following tools `remap`, `scale`, `prefix` and `suffix`, for example: ```yaml - field: key4 @@ -110,7 +121,42 @@ You can manipulate data with the following tools `remap`, `scale` and `suffix`, label: Power format: float scale: 0.001 # can be number or string e.g. 1/16 - suffix: kW + suffix: "kW" +- field: key6 + label: Price + format: float + prefix: "$" +``` + +## List View + +You can change the default block view to a list view by setting the `display` option to `list`. + +The list view can optionally display an additional field next to the primary field. + +`additionalField`: Similar to `field`, but only used in list view. Displays additional information for the mapping object on the right. + +`field`: Defined the same way as other custom api widget fields. + +`color`: Allowed options: `"theme", "adaptive", "black", "white"`. The option `adaptive` will apply a color using the value of the `additionalField`, green for positive numbers, red for negative numbers. + +```yaml +- field: key + label: Field + format: text + remap: + - value: 0 + to: None + - value: 1 + to: Connected + - any: true # will map all other values + to: Unknown + additionalField: + field: + hourly: + time: key + color: theme + format: date ``` ## Custom Headers @@ -121,3 +167,16 @@ Pass custom headers using the `headers` option, for example: headers: X-API-Token: token ``` + +## Custom Request Body + +Pass custom request body using the `requestBody` option in either a string or object format. Objects will automatically be converted to a JSON string. + +```yaml +requestBody: + foo: bar +# or +requestBody: "{\"foo\":\"bar\"}" +``` + +Both formats result in `{"foo":"bar"}` being sent as the request body. Don't forget to set your `Content-Type` headers! diff --git a/docs/widgets/services/gitea.md b/docs/widgets/services/gitea.md new file mode 100644 index 00000000..bf75aa69 --- /dev/null +++ b/docs/widgets/services/gitea.md @@ -0,0 +1,17 @@ +--- +title: Gitea +description: Gitea Widget Configuration +--- + +Learn more about [Gitea](https://gitea.com). + +API token requires `notifications` and `repository` permissions. See the [gitea documentation](https://docs.gitea.com/development/api-usage#generating-and-listing-api-tokens) for details on generating tokens. + +Allowed fields: ["notifications", "issues", "pulls"] + +```yaml +widget: + type: gitea + url: http://gitea.host.or.ip:port + key: giteaapitoken +``` diff --git a/docs/widgets/services/glances.md b/docs/widgets/services/glances.md index d8f9e9ca..134dcb5f 100644 --- a/docs/widgets/services/glances.md +++ b/docs/widgets/services/glances.md @@ -18,6 +18,7 @@ widget: username: user # optional if auth enabled in Glances password: pass # optional if auth enabled in Glances metric: cpu + diskUnits: bytes # optional, bytes (default) or bbytes. Only applies to disk ``` _Please note, this widget does not need an `href`, `icon` or `description` on its parent service. To achieve the same effect as the examples above, see as an example:_ diff --git a/docs/widgets/services/moonraker.md b/docs/widgets/services/moonraker.md index 6de62ec6..2ee1a4e2 100644 --- a/docs/widgets/services/moonraker.md +++ b/docs/widgets/services/moonraker.md @@ -12,3 +12,12 @@ widget: type: moonraker url: http://moonraker.host.or.ip:port ``` + +If your moonraker instance has an active authorization and your homepage ip isn't whitelisted you need to add your api key ([Authorization Documentation](https://moonraker.readthedocs.io/en/latest/web_api/#authorization)). + +```yaml +widget: + type: moonraker + url: http://moonraker.host.or.ip:port + key: api_keymoonraker +``` diff --git a/docs/widgets/services/planit.md b/docs/widgets/services/planit.md new file mode 100644 index 00000000..d1cebfaa --- /dev/null +++ b/docs/widgets/services/planit.md @@ -0,0 +1,15 @@ +--- +title: Plant-it +description: Plant-it Widget Configuration +--- + +Learn more about [Plantit](https://github.com/MDeLuise/plant-it). + +API key can be created from the REST API. + +```yaml +widget: + type: plantit + url: http://plant-it.host.or.ip:port # api port + key: plantit-api-key +``` diff --git a/docs/widgets/services/truenas.md b/docs/widgets/services/truenas.md index 6d747ef1..24350490 100644 --- a/docs/widgets/services/truenas.md +++ b/docs/widgets/services/truenas.md @@ -9,6 +9,8 @@ Allowed fields: `["load", "uptime", "alerts"]`. To create an API Key, follow [the official TrueNAS documentation](https://www.truenas.com/docs/scale/scaletutorials/toptoolbar/managingapikeys/). +A detailed pool listing is disabled by default, but can be enabled with the `enablePools` option. + ```yaml widget: type: truenas @@ -16,4 +18,5 @@ widget: username: user # not required if using api key password: pass # not required if using api key key: yourtruenasapikey # not required if using username / password + enablePools: true # optional, defaults to false ``` diff --git a/mkdocs.yml b/mkdocs.yml index 572d36b4..c6ecfda9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,6 +57,7 @@ nav: - widgets/services/gamedig.md - widgets/services/gatus.md - widgets/services/ghostfolio.md + - widgets/services/gitea.md - widgets/services/glances.md - widgets/services/gluetun.md - widgets/services/gotify.md @@ -103,6 +104,7 @@ nav: - widgets/services/photoprism.md - widgets/services/pialert.md - widgets/services/pihole.md + - widgets/services/plantit.md - widgets/services/plex-tautulli.md - widgets/services/plex.md - widgets/services/portainer.md diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 0d67c3f9..7081ca7a 100755 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -853,5 +853,16 @@ "wled": { "deviceName": "Device Name", "deviceState": "Device State" + }, + "plantit": { + "events": "Events", + "plants": "Plants", + "photos": "Photos", + "species": "Species" + }, + "gitea": { + "notifications": "Notifications", + "issues": "Issues", + "pulls": "Pull Requests" } } \ No newline at end of file diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 6f10baf7..e61fff8b 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -803,5 +803,11 @@ "netdata": { "warnings": "Warnings", "criticals": "Criticals" + }, + "plantit": { + "events": "Eventi", + "plants": "Piante", + "species": "Specie", + "images": "Immagini" } } diff --git a/src/components/bookmarks/group.jsx b/src/components/bookmarks/group.jsx index c5e6a2f1..b13aeac1 100644 --- a/src/components/bookmarks/group.jsx +++ b/src/components/bookmarks/group.jsx @@ -1,4 +1,4 @@ -import { useRef } from "react"; +import { useRef, useEffect } from "react"; import classNames from "classnames"; import { Disclosure, Transition } from "@headlessui/react"; import { MdKeyboardArrowDown } from "react-icons/md"; @@ -7,8 +7,13 @@ import ErrorBoundary from "components/errorboundry"; import List from "components/bookmarks/list"; import ResolvedIcon from "components/resolvedicon"; -export default function BookmarksGroup({ bookmarks, layout, disableCollapse }) { +export default function BookmarksGroup({ bookmarks, layout, disableCollapse, groupsInitiallyCollapsed }) { const panel = useRef(); + + useEffect(() => { + if (layout?.initiallyCollapsed ?? groupsInitiallyCollapsed) panel.current.style.height = `0`; + }, [layout, groupsInitiallyCollapsed]); + return (
- + {({ open }) => ( <> {layout?.header !== false && ( diff --git a/src/components/filecontent.jsx b/src/components/filecontent.jsx deleted file mode 100644 index 1dd6266a..00000000 --- a/src/components/filecontent.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import useSWR from "swr"; - -export default function FileContent({ path, loadingValue, errorValue, emptyValue = "" }) { - const fetcher = (url) => fetch(url).then((res) => res.text()); - const { data, error, isLoading } = useSWR(`/api/config/${path}`, fetcher); - - if (error) return errorValue; - if (isLoading) return loadingValue; - return data || emptyValue; -} diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx index 23f7cef9..aaa40493 100644 --- a/src/components/quicklaunch.jsx +++ b/src/components/quicklaunch.jsx @@ -120,7 +120,7 @@ export default function QuickLaunch({ }); if (showSearchSuggestions && searchProvider.suggestionUrl) { - if (searchString.trim() !== searchSuggestions[0]) { + if (searchString.trim() !== searchSuggestions[0]?.trim()) { fetch( `/api/search/searchSuggestion?query=${encodeURIComponent(searchString)}&providerName=${ searchProvider.name ?? "Custom" diff --git a/src/components/services/group.jsx b/src/components/services/group.jsx index bcc3ce5d..cdbb89f3 100644 --- a/src/components/services/group.jsx +++ b/src/components/services/group.jsx @@ -1,4 +1,4 @@ -import { useRef } from "react"; +import { useRef, useEffect } from "react"; import classNames from "classnames"; import { Disclosure, Transition } from "@headlessui/react"; import { MdKeyboardArrowDown } from "react-icons/md"; @@ -6,9 +6,21 @@ import { MdKeyboardArrowDown } from "react-icons/md"; import List from "components/services/list"; import ResolvedIcon from "components/resolvedicon"; -export default function ServicesGroup({ group, services, layout, fiveColumns, disableCollapse, useEqualHeights }) { +export default function ServicesGroup({ + group, + services, + layout, + fiveColumns, + disableCollapse, + useEqualHeights, + groupsInitiallyCollapsed, +}) { const panel = useRef(); + useEffect(() => { + if (layout?.initiallyCollapsed ?? groupsInitiallyCollapsed) panel.current.style.height = `0`; + }, [layout, groupsInitiallyCollapsed]); + return (
- + {({ open }) => ( <> {layout?.header !== false && ( diff --git a/src/components/widgets/glances/glances.jsx b/src/components/widgets/glances/glances.jsx index 0834b775..905a179a 100644 --- a/src/components/widgets/glances/glances.jsx +++ b/src/components/widgets/glances/glances.jsx @@ -21,6 +21,7 @@ function convertToFahrenheit(t) { export default function Widget({ options }) { const { t, i18n } = useTranslation(); const { settings } = useContext(SettingsContext); + const diskUnits = options.diskUnits === "bbytes" ? "common.bbytes" : "common.bytes"; const { data, error } = useSWR( `/api/widgets/glances?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`, @@ -132,9 +133,9 @@ export default function Widget({ options }) { } {options.memory && } {Array.isArray(options.disk) - ? options.disk.map((disk) => ) - : options.disk && } + ? options.disk.map((disk) => ( + + )) + : options.disk && } {options.cputemp && } {options.uptime && }
diff --git a/src/components/widgets/search/search.jsx b/src/components/widgets/search/search.jsx index 6a634308..c9391d35 100644 --- a/src/components/widgets/search/search.jsx +++ b/src/components/widgets/search/search.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, Fragment } from "react"; +import { useState, useEffect, Fragment } from "react"; import { useTranslation } from "next-i18next"; import { FiSearch } from "react-icons/fi"; import { SiDuckduckgo, SiMicrosoftbing, SiGoogle, SiBaidu, SiBrave } from "react-icons/si"; @@ -119,20 +119,27 @@ export default function Search({ options }) { }; }, [selectedProvider, options, query, searchSuggestions]); - const submitCallback = useCallback( - (value) => { - const q = encodeURIComponent(value); - const { url } = selectedProvider; - if (url) { - window.open(`${url}${q}`, options.target || "_blank"); - } else { - window.open(`${options.url}${q}`, options.target || "_blank"); - } + let currentSuggestion; - setQuery(""); - }, - [selectedProvider, options.url, options.target], - ); + function doSearch(value) { + const q = encodeURIComponent(value); + const { url } = selectedProvider; + if (url) { + window.open(`${url}${q}`, options.target || "_blank"); + } else { + window.open(`${options.url}${q}`, options.target || "_blank"); + } + + setQuery(""); + currentSuggestion = null; + } + + const handleSearchKeyDown = (event) => { + const useSuggestion = searchSuggestions.length && currentSuggestion; + if (event.key === "Enter") { + doSearch(useSuggestion ? currentSuggestion : event.target.value); + } + }; if (!availableProviderIds) { return null; @@ -148,7 +155,7 @@ export default function Search({ options }) {
- + setQuery(event.target.value)} + onChange={(event) => { + setQuery(event.target.value); + }} required autoCapitalize="off" autoCorrect="off" autoComplete="off" // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={options.focus} + onBlur={(e) => e.preventDefault()} + onKeyDown={handleSearchKeyDown} /> {searchSuggestions[1].map((suggestion) => ( - - {({ active }) => ( -
- {suggestion.indexOf(query) === 0 ? query : ""} - - {suggestion.indexOf(query) === 0 ? suggestion.substring(query.length) : suggestion} - -
- )} + { + doSearch(suggestion); + }} + className="flex w-full" + > + {({ active }) => { + if (active) currentSuggestion = suggestion; + return ( +
+ {suggestion.indexOf(query) === 0 ? query : ""} + + {suggestion.indexOf(query) === 0 ? suggestion.substring(query.length) : suggestion} + +
+ ); + }}
))}
diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 59a2ad12..39ac6cf2 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-array-index-key */ import useSWR, { SWRConfig } from "swr"; import Head from "next/head"; +import Script from "next/script"; import dynamic from "next/dynamic"; import classNames from "classnames"; import { useTranslation } from "next-i18next"; @@ -10,7 +11,6 @@ import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { useRouter } from "next/router"; import Tab, { slugify } from "components/tab"; -import FileContent from "components/filecontent"; import ServicesGroup from "components/services/group"; import BookmarksGroup from "components/bookmarks/group"; import Widget from "components/widgets/widget"; @@ -311,6 +311,7 @@ function Home({ initialSettings }) { fiveColumns={settings.fiveColumns} disableCollapse={settings.disableCollapse} useEqualHeights={settings.useEqualHeights} + groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed} /> ) : ( ), )} @@ -333,6 +335,7 @@ function Home({ initialSettings }) { layout={settings.layout?.[group.name]} fiveColumns={settings.fiveColumns} disableCollapse={settings.disableCollapse} + groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed} /> ))}
@@ -345,6 +348,7 @@ function Home({ initialSettings }) { bookmarks={group} layout={settings.layout?.[group.name]} disableCollapse={settings.disableCollapse} + groupsInitiallyCollapsed={settings.groupsInitiallyCollapsed} /> ))}
@@ -361,6 +365,7 @@ function Home({ initialSettings }) { settings.disableCollapse, settings.useEqualHeights, settings.cardBlur, + settings.groupsInitiallyCollapsed, initialSettings.layout, ]); @@ -385,19 +390,11 @@ function Home({ initialSettings }) { )} + + {/* eslint-disable-line @next/next/no-css-tags */} - - - -